A secure proxy backend that acts as a bridge between your Next.js frontend and Express backend in a TurboRepo monorepo setup. This proxy provides an additional layer of security by hiding your actual backend URL and implementing JWT-based authentication for protected routes.
βββββββββββββββββββ ββββββββββββββββββββ βββββββββββββββββββ
β Next.js FE βββββΆβ Proxy Backend βββββΆβ Express BE β
β (Frontend) β β (This App) β β (Hidden) β
βββββββββββββββββββ ββββββββββββββββββββ βββββββββββββββββββ
- URL Hiding: Your Express backend URL is completely hidden from the frontend
- JWT Authentication: Internal JWT tokens for secure communication between proxy and Express backend
- NextAuth Integration: Seamless integration with NextAuth.js for user authentication
- Public Routes: Support for public endpoints that don't require authentication
- Session Validation: Automatic session validation for protected routes
app/
βββ api/
βββ proxy/
βββ [...path]/
βββ route.ts # Main proxy implementation
- NextAuth.js Setup: Install and configure NextAuth.js using Boltgate CLI
- Express Backend: Your Express backend should be running and accessible
- Environment Variables: Configure the required environment variables
- Install dependencies:
npm install
# or
pnpm install- Set up NextAuth.js using Boltgate:
npx boltgate- Configure environment variables in
.env.local:
# Express Backend URL (hidden from frontend)
EXPRESS_URL=http://localhost:8000
# JWT Secret for internal communication
INTERNAL_JWT_SECRET=your-super-secret-jwt-key
# NextAuth.js configuration (generated by Boltgate)
NEXTAUTH_SECRET=your-nextauth-secret
NEXTAUTH_URL=http://localhost:3000| Variable | Description | Required |
|---|---|---|
EXPRESS_URL |
Your Express backend base URL | β |
INTERNAL_JWT_SECRET |
Secret key for internal JWT signing | β |
NEXTAUTH_SECRET |
NextAuth.js secret (from Boltgate) | β |
NEXTAUTH_URL |
Your Next.js app URL | β |
Edit the PUBLIC_ROUTES array in app/api/proxy/[...path]/route.ts:
const PUBLIC_ROUTES: string[] = [
"/api/proxy/v1/form/getForm", // Example public route
"/api/proxy/v1/public/data", // Another public route
"/api/proxy/v1/health", // Health check endpoint
];Note: Public routes don't require authentication and won't include JWT tokens in the request to your Express backend.
All requests to your Express backend should be made through the proxy:
// β Don't do this (exposes your backend URL)
fetch("http://localhost:8000/api/users");
// β
Do this instead
fetch("/api/proxy/v1/users");// Frontend request
const response = await fetch("/api/proxy/v1/public/data", {
method: "GET",
});// Frontend request (user must be authenticated via NextAuth)
const response = await fetch("/api/proxy/v1/users/profile", {
method: "GET",
headers: {
"Content-Type": "application/json",
},
});const response = await fetch("/api/proxy/v1/users", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
name: "John Doe",
email: "john@example.com",
}),
});Your Express backend needs to verify the JWT tokens sent by the proxy:
// middleware/auth.ts
import jwt from "jsonwebtoken";
import { Request, Response, NextFunction } from "express";
interface AuthenticatedRequest extends Request {
userId?: string;
}
const verifyInternalJWT = (
req: AuthenticatedRequest,
res: Response,
next: NextFunction
) => {
const authHeader = req.headers.authorization;
if (!authHeader || !authHeader.startsWith("Bearer ")) {
return res.status(401).json({ message: "No token provided" });
}
const token = authHeader.split(" ")[1];
try {
const decoded = jwt.verify(token, process.env.INTERNAL_JWT_SECRET!) as {
userId: string;
};
req.userId = decoded.userId;
next();
} catch (error) {
return res.status(401).json({ message: "Invalid token" });
}
};
export { verifyInternalJWT };// routes/protected.ts
import express, { Request, Response } from "express";
import { verifyInternalJWT } from "../middleware/auth";
const router = express.Router();
// Protected route (requires JWT)
router.get(
"/users/profile",
verifyInternalJWT,
(req: Request, res: Response) => {
res.json({ userId: (req as any).userId, message: "Protected data" });
}
);
// Public route (no middleware needed)
router.get("/public/data", (req: Request, res: Response) => {
res.json({ message: "Public data" });
});
export default router;npm run dev
# or
pnpm devThe proxy will be available at http://localhost:3000/api/proxy/
- Test Public Route:
curl http://localhost:3000/api/proxy/v1/public/data- Test Protected Route (requires authentication):
# This will return 401 if not authenticated
curl http://localhost:3000/api/proxy/v1/users/profile- Frontend Request: Client makes request to
/api/proxy/your-endpoint - Route Parsing: Proxy extracts the path after
/api/proxy/ - Authentication Check:
- If route is public β Skip authentication
- If route is protected β Validate NextAuth session
- JWT Signing: For authenticated requests, sign internal JWT with user ID
- Backend Request: Forward request to Express backend with JWT header
- Response Forwarding: Return Express backend response to frontend
- URL Hiding: Express backend URL is never exposed to the frontend
- JWT Protection: Internal JWT tokens expire in 5 minutes for security
- Session Validation: Only authenticated users can access protected routes
- Error Handling: Graceful error handling and response forwarding
const PUBLIC_ROUTES: string[] = [
"/api/proxy/v1/form/getForm",
"/api/proxy/v1/public/data",
"/api/proxy/v1/health", // Add new public routes here
"/api/proxy/v1/analytics/track", // Example: analytics tracking
];// In the signInternalJwt function
return jwt.sign({ userId }, process.env.INTERNAL_JWT_SECRET, {
expiresIn: "10m", // Change from 5m to 10m
});// In the proxyRequest function
const headers: Record<string, string> = {
"Content-Type": req.headers.get("content-type") || "application/json",
"X-Custom-Header": "your-value", // Add custom headers
};- 401 Session Error: Make sure NextAuth.js is properly configured
- Backend Connection Failed: Verify
EXPRESS_URLis correct and backend is running - JWT Verification Failed: Ensure
INTERNAL_JWT_SECRETmatches between proxy and Express backend - CORS Issues: Configure CORS in your Express backend to allow requests from your Next.js domain
Enable detailed logging by adding console.log statements in the proxy function:
console.log("Request URL:", backendUrl);
console.log("Headers:", headers);
console.log("User ID:", userId);- NextAuth.js Documentation
- Boltgate CLI Tool
- JWT.io - JWT token debugging
- Next.js API Routes
- Fork the repository
- Create a feature branch
- Make your changes
- Test thoroughly
- Submit a pull request
This project is licensed under the MIT License.
Made with β€οΈ by Jay Shende