Updated Oct 11, 202611 min read

NestJS vs Express: Which to Choose in 2026

Comparing the two most common Node.js backend frameworks, and a third option worth knowing

Express is the most-used Node.js framework ever written. It's minimal, unopinionated, and has shipped more backends than any other JavaScript tool. NestJS is the heavyweight counterweight: opinionated, TypeScript-first, modeled on Angular, and built for teams that want structure handed to them.

Picking between them comes down to how much framework you want. This guide compares NestJS and Express on the dimensions that actually matter when you're choosing, architecture, TypeScript support, productivity, performance, and ecosystem, with honest tradeoffs and concrete code. We also introduce a third option, Encore, that's worth knowing about because it solves problems both frameworks leave to you.

TL;DR

  • Pick Express if you want minimal abstractions, maximum flexibility, a familiar API, and you're happy to pick your own validation, ORM, auth, and structure.
  • Pick NestJS if you want an opinionated architecture out of the box, your team knows Angular or enterprise patterns, and you're willing to learn its DI system and module conventions.
  • Consider Encore if you want type-safe APIs, managed infrastructure, and built-in observability without assembling them yourself. More on this below.

Quick Comparison

AspectExpressNestJSEncore
First release201020172024 (Encore.ts)
PhilosophyMinimal, unopinionatedOpinionated, Angular-styleTyped APIs and infrastructure declared in code
TypeScriptOptional (typings needed)First-classFirst-class
ArchitectureMiddleware chainModules, controllers, providers, DIServices as folders, APIs as typed functions
ValidationDIY (bring your own)Built-in (class-validator)Built-in, from TypeScript types
TestingDIY setupBuilt-in testing moduleBuilt-in (encore test with real local infrastructure)
Learning curveLowMedium-highLow
Performance benchmark (req/sec, without validation)~16k req/sec~16k req/sec (on Express)~121k req/sec
Community sizeVery largeLarge, growingSmaller, growing (12k+ GitHub stars)
Infrastructure from codeNoNoYes
Built-in tracingNoNoYes
Type-safe service callsNoNoYes
Typical useAPIs, middleware, prototypesEnterprise backends, large teamsMulti-service backends in your own AWS or GCP account

Architecture

Express

Express is a middleware dispatcher. You register middleware functions, and each request flows through them in order. A route handler is just a function with (req, res, next).

import express from "express"; const app = express(); app.use(express.json()); app.get("/users/:id", async (req, res) => { const user = await db.users.findById(req.params.id); if (!user) return res.status(404).json({ error: "not found" }); res.json(user); }); app.listen(3000);

That's the whole app. There's no module system, no DI container, no project structure conventions. You organize files however you want. For small projects this is freeing; for large ones it becomes an archaeology problem.

NestJS

NestJS gives you a strict architecture: modules group related code, controllers handle HTTP, providers hold business logic, and a DI container wires them together.

// users.controller.ts import { Controller, Get, Param, NotFoundException } from "@nestjs/common"; import { UsersService } from "./users.service"; @Controller("users") export class UsersController { constructor(private readonly usersService: UsersService) {} @Get(":id") async findOne(@Param("id") id: string) { const user = await this.usersService.findOne(id); if (!user) throw new NotFoundException(); return user; } } // users.service.ts import { Injectable } from "@nestjs/common"; @Injectable() export class UsersService { async findOne(id: string) { return await this.db.users.findById(id); } } // users.module.ts import { Module } from "@nestjs/common"; import { UsersController } from "./users.controller"; import { UsersService } from "./users.service"; @Module({ controllers: [UsersController], providers: [UsersService], }) export class UsersModule {}

Same endpoint, three files and a module registration. In exchange you get dependency injection, automatic testing setup, consistent project structure across teams, and a convention for every common backend need.

TypeScript Support

Express was written before TypeScript was popular. It has third-party typings (@types/express), but req.body, req.params, and req.query are typed as any by default. You have to add validation middleware (Zod, Joi, class-validator) and manually narrow types in every handler.

NestJS was built TypeScript-first. Validation is built in via class-validator, parameters are typed at the decorator level, and the DI container is fully typed. You get compile-time safety across the whole application.

This matters more than it sounds. In a 50-endpoint Express app, the "what shape is this request body" question gets answered differently by different developers. In NestJS, there's one way to do it.

Productivity

Express is faster for quick starts. You can have a working API in 10 lines. You'll move fast until you need authentication, validation, database access, testing setup, and error handling, at which point you're assembling a framework from npm packages, and every team does it slightly differently.

NestJS is slower to start (more to learn) but more consistent at scale. Everyone writes code the same way. Adding new developers is easier because the conventions are the framework, not tribal knowledge.

A rough heuristic:

  • < 10 endpoints, solo dev: Express wins on velocity.
  • > 30 endpoints, team of 3+: NestJS wins on consistency.
  • In between: toss-up; depends on team preference.

Performance

Both frameworks are fast enough for almost all workloads. In our performance benchmark (a simple JSON endpoint, 150 concurrent workers):

  • Express: ~16k req/sec
  • Fastify: ~62k req/sec
  • NestJS: ~16k req/sec, since it runs on Express by default (NestJS wasn't benchmarked separately)

NestJS uses Express as its default HTTP layer, so it inherits Express's performance minus a small DI overhead. Swapping to the Fastify adapter makes NestJS faster than raw Express. In practice, your database and network I/O dominate, framework overhead is rarely the bottleneck.

Encore.ts, which runs request handling and validation in a Rust-based runtime outside the Node.js event loop, handled about 121k requests per second in the same benchmark, roughly 9x Express:

Requests/sec

Without validation
With schema validation
121,005
107,018
Encore
v1.38.7
101,611
33,772
Bun (+ Zod)
v1.1.12 / v3.23.8
82,617
35,124
Elysia (+ TypeBox)
v1.1.16 / v0.33.12
71,202
33,150
Hono (+ TypeBox)
v4.6.3 / v0.33.12
62,207
48,397
Fastify (+ Ajv)
v4.27.0 / v8.16.0
15,707
11,878
Express (+ Zod)
v4.19.2 / v3.23.8
Requests per second, with and without schema validation. See the benchmark code on GitHub.

Ecosystem

Express has the largest middleware ecosystem in JavaScript. Any problem you hit has 5 npm packages already solving it. The downside is quality and maintenance vary wildly.

NestJS has a smaller but more curated ecosystem. Its @nestjs/* packages cover common needs (auth, config, throttling, caching, GraphQL, WebSockets, microservices) and are maintained by the core team. Beyond those, you can usually wrap any Express middleware inside a NestJS app.

Validation

Express has no built-in validation. You install Zod, Joi, class-validator, or similar.

// Express with Zod import { z } from "zod"; const CreateUser = z.object({ email: z.string().email(), name: z.string().min(1), }); app.post("/users", (req, res) => { const parsed = CreateUser.safeParse(req.body); if (!parsed.success) return res.status(400).json(parsed.error); // ... use parsed.data });

NestJS has it wired in via class-validator and class-transformer:

// NestJS DTO import { IsEmail, IsString, MinLength } from "class-validator"; export class CreateUserDto { @IsEmail() email: string; @IsString() @MinLength(1) name: string; } @Post() create(@Body() dto: CreateUserDto) { return this.usersService.create(dto); }

The validation happens before your handler runs. Fewer places to forget it.

Testing

Express gives you supertest and a blank page. You write your own test setup, mock your own dependencies, and decide your own conventions.

NestJS ships a @nestjs/testing module that integrates with the DI container. You can override any provider with a mock in a single line:

const module = await Test.createTestingModule({ controllers: [UsersController], providers: [ { provide: UsersService, useValue: { findOne: jest.fn().mockResolvedValue({ id: "1" }) }, }, ], }).compile();

For unit tests, NestJS saves real time. For end-to-end tests, both frameworks converge on supertest-style HTTP testing.

A Third Option: Encore

Encore

Both Express and NestJS stop at the application boundary. They help you write HTTP handlers. Everything outside that (databases, Pub/Sub, cron jobs, deployment, tracing, service-to-service type safety), is left to you. That's fine for small apps and painful for larger systems.

Encore is a newer TypeScript backend framework that takes a different approach: you declare infrastructure as typed objects in your code, and Encore provisions the actual resources (Postgres, Pub/Sub, Cron) in your own AWS or GCP account. The same TypeScript that defines your APIs also defines your infra.

The endpoint that took a controller + service + module in NestJS, or a route handler + custom validation in Express, is one function in Encore:

users/users.ts
import { api } from "encore.dev/api"; import { SQLDatabase } from "encore.dev/storage/sqldb"; // Provisions managed Postgres, locally via Docker, in production on RDS or Cloud SQL. const db = new SQLDatabase("users", { migrations: "./migrations" }); interface User { id: number; email: string; name: string; } export const get = api( { method: "GET", path: "/users/:id", expose: true }, async ({ id }: { id: number }): Promise<User> => { return await db.queryRow`SELECT * FROM users WHERE id = ${id}`; }, );

There are no decorators, no DTOs, no DI container, and no validation middleware. Types are the contract: requests are validated against them automatically, and Encore generates an OpenAPI spec, typed client SDKs, and distributed traces from the same definitions.

What Encore handles that Express and NestJS don't:

Encore is open source with over 12,000 GitHub stars and is used in production across hundreds of companies, including Coinbase (Echo) and Groupon. Besides deploying to AWS or GCP, you can build Docker images and deploy anywhere.

When Encore makes sense

  • You're starting a new backend and don't want to assemble a framework
  • You expect to end up with multiple services eventually
  • You want type safety to extend across services, not stop at HTTP boundaries
  • You'd rather not run Terraform / Kubernetes / OpenTelemetry yourself

When to stick with Express or NestJS

  • You're committed to a specific non-AWS/GCP cloud (Encore deploys to AWS and GCP natively; elsewhere you can self-host its Docker images)
  • You have deep investment in Express or NestJS patterns you don't want to rewrite
  • You need a specific middleware or plugin that only exists in those ecosystems

When to Use Each

Start at the top and follow the first question you can answer with a yes:

Question 1

Do you want infrastructure provisioned from your code, without building a DevOps team internally?

Yes →
EncoreInfrastructure automation in your own AWS or GCP account
Question 2

Are you building several services that share databases, Pub/Sub, or cron jobs?

Yes →
EncoreType-safe service calls and built-in tracing
Question 3

Is your team 3+ developers, wanting enforced structure, DI, and Angular-style patterns?

Yes →
NestJSModules, providers, and a built-in testing module
Question 4

Do you need high request throughput from a Node.js backend?

Yes →
EncoreRust-based runtime with 9x the throughput of Express.js
Question 5

Are you building a small API or prototype where speed to first endpoint matters?

Yes →
ExpressMinimal and unopinionated
ExpressMaximum flexibility to mix and match libraries

In more detail:

Use Express when:

  • You're building a small API or a prototype
  • You're the only developer and speed to first endpoint matters
  • You already have strong opinions about how to structure a backend
  • You need maximum flexibility to mix and match libraries

Use NestJS when:

  • You're building a long-lived enterprise backend
  • Your team is 3+ developers and consistency matters
  • You want validation, testing, and DI out of the box
  • You have Angular background and want similar patterns on the backend

Verdict

For a new TypeScript backend in 2026 where you don't have existing code to preserve, the honest ranking by time-to-productive-system is:

  1. Encore: if you're open to a newer framework. You skip the "which ORM, which validator, which auth library, which deployment story" decisions entirely.
  2. NestJS: if you want established patterns and a large ecosystem, and you're happy with the boilerplate.
  3. Express: if you want full control and are willing to assemble everything yourself.

For an existing Express or NestJS codebase, migration only makes sense if you're hitting real pain (type-safety gaps across services, infrastructure drift, observability blind spots). Otherwise staying put is usually the right call.

Getting Started

# Express npm init -y && npm install express @types/express typescript # NestJS npm install -g @nestjs/cli && nest new project-name

For Encore, install the CLI and create a starter app:

Get started
brew install encoredev/tap/encore &&
encore app create
Copy

Run in your terminal to get started locally.

Frequently asked questions

Is NestJS built on top of Express?

Yes, NestJS uses Express as its default underlying HTTP framework. It wraps Express with an opinionated layer of modules, controllers, and dependency injection, and you can swap the Express adapter for Fastify if you want higher raw throughput.

Is NestJS faster than Express?

No, NestJS is not faster than Express on raw request throughput. Because NestJS runs on top of Express by default and adds abstraction layers, it carries a small overhead, so plain Express is marginally faster in benchmarks. The difference is minor for most applications, and switching NestJS to the Fastify adapter narrows the gap.

Should I choose NestJS or Express?

It depends on how much of the backend you want the framework to handle. If you also want the infrastructure layer managed, Encore is worth a look: you declare the infrastructure your backend needs as typed objects in TypeScript and it provisions the real resources in your own AWS or GCP account. If you only need HTTP handling, Express is the minimal, flexible choice for small services and prototypes, while NestJS fits larger teams that want enforced structure through modules and dependency injection.

Which Node.js backend framework works best with AI coding agents?

Frameworks with strong conventions and infrastructure defined in code work best with AI coding agents, because the agent has fewer open-ended decisions to get wrong. Encore is built for this: services, databases, and Pub/Sub topics are declared as typed objects, so an agent can generate a full backend including its infrastructure without writing Terraform or touching a cloud console, and the type system limits what it can misconfigure. Express leaves those choices open, which gives an agent more room to make inconsistent decisions.

Do I need NestJS to write TypeScript backends?

No, you do not need NestJS to write TypeScript backends. Express works with TypeScript once you add the `@types/express` type definitions, though request bodies and params start out typed as `any`, so you add a validator like Zod. Fastify, Hono, and other frameworks offer stronger type inference without NestJS conventions, so the decorator-and-DI style NestJS uses is one option among several.

What are the main alternatives to NestJS and Express?

The main alternatives include Fastify, Koa, Hapi, and Hono, which offer different tradeoffs on performance and API design. Encore is another option that pairs a backend framework with infrastructure provisioning, letting you declare resources such as databases and cron jobs in code and deploy to your own AWS or GCP account.

Deploy to your cloud on AWS and Google Cloud
without managing infrastructure

Encore lets developers and agents define application infrastructure directly in code, then automatically provisions it from local development to production in your AWS or GCP account.

~/orders — claude
Claude Code
Claude Code v2.1.180Opus 5.5~/orders