Building modern web applications often requires combining different frameworks to leverage their unique strengths. In this article, I'll walk you through an elegant solution for integrating a Next.js frontend with a NestJS backend using intelligent routing configuration that creates a seamless development and production experience.
1. The Architecture Challenge
When building full-stack applications with separate frontend and backend services, developers typically face several challenges:
- Authentication state management across different domains
- Development workflow complexity with multiple servers
- API URL management in different environments
Traditional approaches often involve deploying services separately, leading to authentication headaches and environment-specific configuration nightmares.
2. Our Solution: Unified API Surface
Our architecture leverages Next.js's powerful routing capabilities to create a unified API surface that seamlessly proxies requests to our NestJS backend. Here's how the magic happens:
Smart Routing Configuration
The heart of our solution lies in the Next.js configuration file (next.config.ts
):
const nextConfig: NextConfig = {
async rewrites() {
return [
{
source: '/api/auth/:path*',
destination: '/api/auth/:path*',
},
{
source: '/api/:path*',
destination: process.env.NEST_API_URL + '/:path*',
},
];
},
};
This configuration implements a two-tier routing strategy:
- NextAuth.js Preservation: Routes matching
/api/auth/:path*
remain within Next.js for authentication handling - Backend Proxy: All other API routes (
/api/:path*
) are proxied to our NestJS backend via theNEST_API_URL
environment variable
3. Request Flow Overview
sequenceDiagram
participant Browser
box rgba(230, 245, 255, 0.8) Next.js Frontend
participant NextJS as Next.js App<br/>(Port 3000)
participant Middleware as Next.js<br/>middleware.ts
participant Auth as NextAuth
end
box rgba(240, 248, 235, 0.8) NestJS Backend
participant NestJS as NestJS Backend<br/>(Port 8080)
participant DB as Database
end
Note over Browser,DB: Sign In Flow
Browser->>NextJS: POST /api/auth/signin
NextJS->>Auth: Verify credentials
Auth->>DB: Update User
Auth-->>Browser: Session cookie set
Note over Browser,DB: API Request Flow
Browser->>NextJS: GET /api/workspaces<br/>(with session cookie)
Note over NextJS,Auth: 🔐 AUTHENTICATION Layer<br/>(Who are you?)
NextJS->>Middleware: Intercept API request
Middleware->>Auth: Extract user session
Auth-->>Middleware: Return user data {sub, email}
Middleware->>NestJS: Proxy request with JWT
Note over NestJS,DB: 🛡️ AUTHORIZATION Layer<br/>(What can you do?)
NestJS->>NestJS: Apply Guards & Controllers
NestJS->>DB: Execute business logic
DB-->>NestJS: Return data
NestJS-->>NextJS: HTTP Response
NextJS-->>Browser: Forward response
Authentication Layer
Our Next.js middleware (middleware.ts
) acts as the bridge between frontend authentication and backend authorization:
export async function middleware(request: NextRequest) {
if (request.nextUrl.pathname.startsWith('/api/auth/')) return NextResponse.next();
const session = await auth();
const requestHeaders = new Headers(request.headers);
if (session?.user?.jwt) {
const { sub, email } = session.user.jwt;
const token = await generateApiToken({ sub, email });
requestHeaders.set('Authorization', `Bearer ${token}`);
}
return NextResponse.next({ request: { headers: requestHeaders } });
}
The middleware:
- Skips NextAuth.js routes to avoid interference
- Extracts user information from the active session
- Generates short-lived JWT tokens
- Injects Authorization headers transparently
Authorization Layer
Our NestJS backend is configured to validate these tokens:
The backend uses custom guards and decorators to protect endpoints and extract user information from the JWT tokens.
4. Development Experience Benefits
This architecture provides several key advantages:
Single Development Server
Developers only need to run one command to start both frontend and backend services, with automatic proxy configuration.
Hot Reloading
Both applications benefit from hot reloading during development, maintaining productivity.
5. Production Considerations
Environment Variables
NEST_API_URL
: Points to your deployed NestJS backendAPI_SECRET
: Shared secret for JWT token signing/verification
Performance
- Next.js acts as a lightweight proxy
- No additional latency beyond standard HTTP forwarding
- Can leverage Next.js caching strategies for API responses
6. When to Use This Approach
This architecture is ideal when you:
- Want to maintain separation between frontend and backend codebases
- Need unified authentication across services
- Prefer simplified development workflows
- Plan to deploy services independently but want unified API access
- Value type safety and modern development practices
7. Conclusion
By leveraging Next.js's routing capabilities as an intelligent proxy layer, we've created an architecture that provides the benefits of microservices while maintaining the simplicity of a monolithic API surface. This approach eliminates common pain points around authentication and development complexity while maintaining the flexibility to scale and deploy services independently.
The combination of smart routing and middleware-powered authentication creates a robust foundation for full-stack applications that can grow with your needs while keeping the developer experience smooth and productive.