NestJS vs Next.js
|
This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production. The reference material consulted while preparing these pages is listed in the section bibliography. |
NestJS and Next.js are different tools that share a similar name and a language. Both are written in TypeScript and run on Node.js, so they are easy to confuse. This page is written against NestJS 12.1 (Express 5 by default, Fastify 5 optional, TypeScript 6, Node.js 20.19+ or 22.12+) and Next.js 16.3 (React 19, Node.js 20.9+), with the versions checked on 2026-10-02. In one line: NestJS builds the backend (an API), Next.js builds the web front end (pages) that may call one.
The one-minute answer
-
NestJS is a framework for server-side Node.js applications. Its architecture is inspired by Angular (modules, controllers, providers, decorators, dependency injection) and it runs on top of an HTTP server library, Express by default or Fastify. It produces REST and GraphQL APIs, WebSocket gateways and microservices. It has no view layer of its own and never renders React components.
-
Next.js is Vercel’s React framework. It renders React components on the server (SSR), at build time (SSG) or as React Server Components, hydrates them in the browser, and provides file-based routing, route handlers and server actions so one project can hold both the pages and some backend code.
If the question is "which one is my backend?", the answer is NestJS. If it is "which one produces my React pages?", it is Next.js. The generic ideas behind Next.js rendering modes are explained on Server Rendering, SSG and React Server Components, and this site has a dedicated Next.js section.
Side by side
| NestJS | Next.js | |
|---|---|---|
Purpose |
Server-side application framework: APIs, real-time gateways, background workers, microservices. |
Full-stack React framework: pages, layouts and a thin backend layer for the same app. |
Rendering |
None. It returns JSON, text, streams or files. Template engines can be wired in, but UI is not its job. |
SSR, SSG, incremental regeneration and React Server Components, plus client components that hydrate in the browser. |
Routing |
Explicit: |
File-system based: folders under |
Dependency injection |
Built in and central: providers are registered in modules and injected through constructors. |
None built in. Code imports modules directly; sharing is done with plain functions and modules. |
Data layer |
Bring your own, wired through providers: TypeORM, Prisma, Drizzle, Mongoose and others. |
Bring your own, called directly from server components, route handlers and server actions, with caching options for |
Runs where |
Always on a Node.js server (or a serverless wrapper). Express or Fastify underneath. |
A Node.js server, or static files for static export. Server components never run in the browser. |
Typical deployment |
A long-lived container or process behind a load balancer, scaled horizontally, often next to queues and databases. |
A Node.js server or Docker container, a platform that supports Next.js, or a static export. |
Cross-cutting features |
Pipes, guards, interceptors, filters and middleware around every handler. |
|
Because NestJS is organized around architecture and Next.js around the page, it is normal to see both in the same product: Next.js for what the browser shows, NestJS for the domain logic behind it. NestJS is also the framework used by the rest of this section; start with Introduction and architecture.
The two frameworks in code
The same "list books" feature looks very different. In NestJS a controller class declares the route and returns data.
import { Body, Controller, Get, Post } from '@nestjs/common';
import { ApiCreatedResponse, ApiOkResponse, ApiProperty } from '@nestjs/swagger';
import { IsString, MinLength } from 'class-validator';
export class CreateBookDto {
@ApiProperty()
@IsString()
@MinLength(1)
title: string;
}
export class BookDto {
@ApiProperty()
id: number;
@ApiProperty()
title: string;
}
@Controller('books')
export class BooksController {
private readonly books: BookDto[] = [{ id: 1, title: 'Dune' }];
@Get()
@ApiOkResponse({ type: [BookDto] })
findAll(): BookDto[] {
return this.books;
}
@Post()
@ApiCreatedResponse({ type: BookDto })
create(@Body() dto: CreateBookDto): BookDto {
const book = { id: this.books.length + 1, title: dto.title };
this.books.push(book);
return book;
}
}
CreateBookDto uses class-validator decorators, which ValidationPipe enforces (shown next), and @ApiProperty()
exposes the shape to the OpenAPI document. A request without a title is rejected with 400 Bad Request before the
handler runs.
In Next.js the folder structure is the router, and a page is a React component. A page is not part of the NestJS example above and cannot be compared line by line; the following server component shows how Next.js fetches data on the server and returns markup instead of JSON. See Next.js Routing Fundamentals for how folders map to routes.
import { getBooks } from '@/lib/books-api';
export default async function BooksPage() {
const books = await getBooks();
return (
<ul>
{books.map((book) => (
<li key={book.id}>{book.title}</li>
))}
</ul>
);
}
The component runs on the server, awaits the data and sends HTML. getBooks is defined in the next section.
How they are combined
The most common arrangement is a Next.js application that renders the UI and calls a separate NestJS API. When Next.js acts as the single entry point that the browser talks to and forwards or shapes calls to other services, it is playing the backend for frontend (BFF) role. Next.js documents this pattern in How to use Next.js as a backend for your frontend; see also Route Handlers and Backend for Frontend.
Three pieces make the combination work.
A typed API client in the Next.js project
Next.js server code reads the API address from an environment variable and keeps every call in one module, so pages and route handlers never hard-code a URL.
// lib/books-api.ts in the Next.js project: the only place that knows the API URL.
export interface Book {
id: number;
title: string;
}
const API_URL = process.env.API_URL ?? 'http://localhost:3000';
export async function getBooks(): Promise<Book[]> {
const res = await fetch(`${API_URL}/books`);
if (!res.ok) throw new Error(`Books API answered ${res.status}`);
return (await res.json()) as Book[];
}
A server component, a route handler or a server action can all call getBooks(). Because it runs on the server,
API_URL can be an internal network address that the browser never sees.
Route handlers and server actions as the BFF layer
A route handler turns the NestJS answer into whatever shape the UI needs, while a server action sends a form submission to the API (see Server Actions and Mutations).
import { getBooks } from '@/lib/books-api';
export async function GET(): Promise<Response> {
const books = await getBooks();
return Response.json(books.map(({ id, title }) => ({ id, title })));
}
'use server';
const API_URL = process.env.API_URL ?? 'http://localhost:3000';
export async function addBook(formData: FormData): Promise<void> {
const title = formData.get('title');
if (typeof title !== 'string' || title.length === 0) {
throw new Error('title is required');
}
const res = await fetch(`${API_URL}/books`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ title }),
});
if (!res.ok) throw new Error(`Books API answered ${res.status}`);
}
Server actions can be invoked with a direct POST request and not just from the UI, so a real application checks
authentication and authorization inside each one, as the Next.js documentation points out. The NestJS API must also
enforce its own authorization; never rely on the Next.js layer alone, because anyone who can reach the NestJS port can
bypass it.
Letting the browser call NestJS directly with CORS
When client components call the NestJS API from the browser instead of going through the BFF, the API must allow the
Next.js origin. NestJS exposes this through enableCors(). The allowed origin is read from configuration, so each
environment can set its own.
import { ValidationPipe, type INestApplication } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
export function configure(app: INestApplication): void {
const config = app.get(ConfigService);
app.useGlobalPipes(new ValidationPipe({ whitelist: true }));
// The Next.js origin(s) allowed to call this API from a browser.
app.enableCors({
origin: config.get<string>('WEB_ORIGIN', 'http://localhost:3001'),
});
const document = SwaggerModule.createDocument(
app,
new DocumentBuilder().setTitle('Bookshelf API').setVersion('1.0').build(),
);
// Serves Swagger UI at /api and the JSON document at /api-json.
SwaggerModule.setup('api', app, document);
}
Source: https://docs.nestjs.com/security/cors, https://docs.nestjs.com/application/validation and https://docs.nestjs.com/openapi/introduction
import { NestFactory } from '@nestjs/core';
import { BooksModule } from './books.module.js';
import { configure } from './configure.js';
const app = await NestFactory.create(BooksModule);
configure(app);
await app.listen(process.env.PORT ?? 3000);
Starting the API and requesting a book from an allowed origin shows the CORS header:
PORT=3000 node dist/main.js &
curl -i http://localhost:3000/books -H 'Origin: http://localhost:3001'
The response includes the allowed origin, followed by the JSON body:
HTTP/1.1 200 OK
Access-Control-Allow-Origin: http://localhost:3001
Content-Type: application/json; charset=utf-8
[{"id":1,"title":"Dune"}]
Sharing types through OpenAPI
Next.js and NestJS live in different packages (often different repositories), so the shared contract is the API’s
description rather than a shared class. NestJS generates an OpenAPI document with @nestjs/swagger, served as JSON at
/api-json in the configuration above; see OpenAPI and Swagger for the
full feature set. The Next.js project then generates TypeScript types from that document with
openapi-typescript:
npx openapi-typescript http://localhost:3000/api-json -o src/api.d.ts
The generated file contains a components interface with one entry per DTO that the document describes. A schema only
appears when the controller declares it, which is why the controller above uses @ApiOkResponse({ type: [BookDto] })
and @ApiCreatedResponse({ type: BookDto }). Without them (or the Nest CLI plugin, described in the OpenAPI page) the
document lists the request body but not the response shape, and the generated client has no Book type.
import type { components } from './api.js';
export type Book = components['schemas']['BookDto'];
export type CreateBook = components['schemas']['CreateBookDto'];
export async function createBook(apiUrl: string, body: CreateBook): Promise<Book> {
const res = await fetch(`${apiUrl}/books`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body),
});
return (await res.json()) as Book;
}
Source: https://openapi-ts.dev/
When the API is a GraphQL API instead, the same idea applies with a schema-driven code generator; see GraphQL and NestJS GraphQL. In a single monorepo where both apps are TypeScript, the two can also import the same package of types, at the cost of coupling their release cycles.
When Next.js is enough and when a separate NestJS backend pays off
A Next.js project can already contain backend code: server components read data, route handlers expose HTTP endpoints and server actions handle mutations. The Next.js documentation itself says these capabilities are not a full backend replacement. They form a public, HTTP-based API layer.
| Route handlers and server actions are enough when | A separate NestJS backend pays off when |
|---|---|
One web app consumes the data and no other client exists. |
Web, mobile, partners and internal tools all consume the same API. |
Logic is mostly "read from a database, render, save a form". |
There is substantial domain logic that benefits from modules, dependency injection and unit-testable providers. |
The team is a small full-stack React team. |
Separate backend and front-end teams need an explicit contract (OpenAPI or GraphQL) and independent releases. |
Request/response is the only interaction. |
You need WebSocket gateways, microservice transports, queues, scheduled jobs or a standalone application with no HTTP at all. |
Deployment is a single Next.js app. |
The API must scale, fail over or be secured independently of the front end. |
A pragmatic path is to start with route handlers and server actions, and move logic into NestJS once a second client,
a worker or a team boundary appears. Because the Next.js code from the earlier sections only reads API_URL, switching
the data source from "in process" to "NestJS over HTTP" touches lib/books-api.ts and not the pages.
Common confusions
-
"NextJS" and "NestJS" are not interchangeable in job titles or search results. The spelling
NestJShas ansand means the Angular-style backend framework;Next.jsorNextJShas anxand means the React framework. -
NestJS cannot render a React page by itself. It can serve a built single-page app’s static files, for example with the
@nestjs/serve-staticpackage, but that is file hosting and not server-side rendering. -
Next.js is not an API framework with controllers or dependency injection. Route handlers are plain functions over the Web
RequestandResponseobjects. -
Running both is common, and they need different ports (for example Next.js on 3001 and NestJS on 3000 locally) which is exactly why CORS and an
API_URLsetting appear above. -
Each has its own request pipeline. NestJS has middleware, guards, interceptors and pipes around controllers; Next.js has
proxy.ts(see below, and Redirects, Rewrites and Proxy) plus layouts and route conventions.
What changed recently and where older material is outdated
-
NestJS 12 (released 2026-08-27) made its core packages ESM-only, and its generated project now defaults to ESM with Vitest. Older tutorials and the NestJS book predate this and show the CommonJS and Jest setup; both module systems remain available through the
nest newprompt, and the examples above use the ESM form (.jsimport extensions, top-levelawait). -
Older Next.js material is built on the
pages/router andgetServerSideProps. Current Next.js documentation is written for theapp/router, where server components fetch data androute.tsreplaces API routes, which the Next.js docs call the equivalent of API Routes in thepagesdirectory. -
Next.js documents
proxy.tsas the request proxy file convention; older sources call the same idea middleware. NestJS keeps the term middleware for its own request pipeline. -
The NestJS docs were reorganized in September 2026 into groups such as Application, Data, Security and HTTP; links in older articles may still point to the former
/techniques/paths.