Express was the default Node.js framework for over 15 years. It earned that position by being simple, flexible, and well-documented. But the backend landscape looks different in 2026. TypeScript is the norm, not the exception. Teams expect built-in validation, not a pile of middleware. And managing infrastructure alongside application code has become a real bottleneck as systems grow.
If you're building a new project or migrating an existing Express app, it's worth looking at what's available now. This guide covers five Express alternatives, each with a different philosophy about how much a framework should do for you.
For a broader comparison, see our Best TypeScript Backend Frameworks guide.
| Feature | Encore.ts | Fastify | NestJS | Hono | Koa |
|---|---|---|---|---|---|
| Primary Use Case | Distributed systems | Schema-validated APIs | Large team backends | Edge & serverless | Lightweight APIs |
| Type Safety | Native TS types | JSON Schema + TypeBox | Decorators | Built-in | Manual setup |
| Built-in Validation | Yes | Yes (JSON Schema) | Yes (decorators) | Via middleware | No |
| Infrastructure from Code | Yes | No | No | No | No |
| Built-in Tracing | Yes | No | No | No | No |
| Auto API Documentation | Yes | Yes (via plugins) | Yes (via plugins) | No | No |
| Service Discovery | Yes | No | No | No | No |
| Learning Curve | Low | Medium | High | Low | Low |
| AI Agent Compatibility | Built-in infrastructure awareness | Manual configuration needed | Manual configuration needed | Manual configuration needed | Manual configuration needed |
Encore.ts is designed for the problems that Express doesn't address: type-safe APIs, infrastructure management, service communication, and observability. Where Express gives you a blank canvas and lets you figure out validation, database connections, tracing, and deployment on your own, Encore provides these as built-in primitives.
The framework runs on a Rust-based runtime that handles HTTP parsing, validation, and I/O outside the Node.js event loop. In benchmarks, Encore.ts handles significantly more requests per second than Express. But performance isn't the main reason to switch. The real benefit is how much less code and configuration you need to get a production-quality backend running.
import { api } from "encore.dev/api";
import { SQLDatabase } from "encore.dev/storage/sqldb";
const db = new SQLDatabase("users", { migrations: "./migrations" });
interface User {
id: number;
name: string;
email: string;
}
export const getUser = api(
{ method: "GET", path: "/users/:id", expose: true },
async ({ id }: { id: number }): Promise<User> => {
return await db.queryRow<User>`SELECT * FROM users WHERE id = ${id}`;
}
);
Compare this with Express, where you'd need to install and configure a validation library, set up a database connection pool, write error handling middleware, and add tracing instrumentation. In Encore, the TypeScript types handle validation, the database is provisioned automatically, and tracing works out of the box. The difference becomes more pronounced as your application grows. Each new Express endpoint requires manual validation setup, error handling, and middleware wiring. Each new Encore endpoint just needs a function signature with TypeScript types.
For Express users, the biggest shift is going from "configure everything yourself" to "declare what you need." You write new SQLDatabase("users", { migrations: "./migrations" }) and a PostgreSQL database is provisioned automatically when you run your app locally. No Docker Compose files, no connection string management, no setup scripts.
Service-to-service communication is another area where Encore pulls ahead. In Express, calling another service means making HTTP requests with manually constructed URLs. In Encore, you import a function from another service and call it. The framework handles service discovery, serialization, and tracing automatically.
Built-in distributed tracing shows you how requests flow through your system without configuring OpenTelemetry, Jaeger, or any external tools. This is something Express teams typically spend significant time setting up and maintaining. Encore also generates a service catalog with auto-generated API documentation that stays in sync with your code, so you never have to maintain separate OpenAPI specs or Swagger files.

Encore uses conventions for APIs and infrastructure that keep projects consistent as they grow. You can use standard npm packages alongside Encore's built-in primitives for anything the framework doesn't cover natively. The conventions mean every service follows the same patterns, which makes onboarding new team members straightforward.
Consider Encore when you're building production backends with multiple services, when you're tired of configuring databases and queues manually for local development, when you want type-safe APIs without adding validation libraries, or when you need observability without the overhead of setting up a tracing stack. It's also a strong fit if you want infrastructure-aware code that AI agents can generate and deploy with guardrails.
For a detailed head-to-head, see our Express.js vs Encore.ts comparison.
You can get started with Encore in minutes:
curl -L https://encore.dev/install.sh | bash
encore app create my-app
cd my-app
encore run
See the Encore.ts documentation for more details, follow the REST API tutorial to build a complete application, or check out Encore AI Integration for using Encore with AI coding agents.
Fastify was built as a direct response to Express's performance and architectural limitations. It uses JSON Schema for request and response validation, which provides both runtime checking and serialization optimization. The plugin architecture encourages clean separation of concerns, with each plugin managing its own dependencies and lifecycle.
For Express users, Fastify feels familiar enough that the migration is straightforward. Routes look similar, middleware translates to plugins, and the async/await support is first-class. The main adjustment is learning the JSON Schema validation layer and the plugin lifecycle. Many teams have migrated from Express to Fastify over the past few years, and the process is well documented with established patterns for converting middleware.
import Fastify from 'fastify';
const fastify = Fastify({ logger: true });
fastify.get('/users/:id', {
schema: {
params: {
type: 'object',
properties: {
id: { type: 'string' }
}
},
response: {
200: {
type: 'object',
properties: {
id: { type: 'string' },
name: { type: 'string' },
email: { type: 'string' }
}
}
}
}
}, async (request) => {
return getUserById(request.params.id);
});
fastify.listen({ port: 3000 });
Fastify gives you more structure than Express without being as opinionated as NestJS. The JSON Schema validation catches bad requests before they reach your handler code, and the same schemas can generate OpenAPI documentation. Performance is better than Express, and the plugin system provides a clean way to organize middleware and shared functionality.
JSON Schema definitions are verbose. For complex request/response types, the schema can be longer than the handler code itself. You can use TypeBox to write schemas in TypeScript, but that's an additional layer. Like Express, Fastify doesn't manage infrastructure, observability, or service discovery. You're still responsible for configuring databases, setting up tracing, and managing deployment.
Consider Fastify when you want a structured upgrade from Express with JSON Schema validation, when you appreciate the plugin architecture for organizing code, or when you're building a single-service API and want better performance than Express offers.
For a deeper comparison, see our Fastify vs Encore.ts article.
NestJS takes the opposite approach from Express. Where Express is minimal and unopinionated, NestJS enforces a strict architecture inspired by Angular. Modules, controllers, providers, guards, interceptors, and pipes give you a comprehensive toolkit for building applications with consistent patterns across the codebase.
The dependency injection system is the core of NestJS's design. Every service is injectable, making unit testing straightforward and encouraging loose coupling. For teams coming from Express who struggled with inconsistent code organization as the project grew, NestJS provides the guardrails that Express deliberately avoids. The framework has a large community and extensive documentation, with solutions for GraphQL, WebSockets, microservices, task scheduling, and dozens of other use cases.
import { Controller, Get, Param } from '@nestjs/common';
import { UsersService } from './users.service';
@Controller('users')
export class UsersController {
constructor(private readonly usersService: UsersService) {}
@Get(':id')
findOne(@Param('id') id: string) {
return this.usersService.findOne(id);
}
}
NestJS scales well organizationally. When you have 10+ developers working on the same backend, the enforced architecture means everyone follows the same patterns. New team members can find their way around the codebase because modules, controllers, and services always look the same. The comprehensive feature set covers GraphQL, WebSockets, task scheduling, and more without reaching for external libraries.
The learning curve is the steepest on this list. Even experienced Express developers need time to internalize modules, custom decorators, guards, interceptors, and the DI container. The decorator-heavy code can obscure what's happening at runtime, making debugging harder. For small teams or simple APIs, the overhead isn't worth it. And despite having microservices support, you still configure infrastructure, tracing, and deployment yourself.
Consider NestJS for large applications with many contributors with many developers, when consistent architecture matters more than simplicity, or when your team has Angular experience and wants familiar patterns on the backend.
For a detailed comparison, see our NestJS vs Encore.ts article.
Hono takes the minimalism that made Express popular and updates it for modern JavaScript runtimes. It runs on Cloudflare Workers, Deno, Bun, and Node.js with the same API. The framework is small, fast, and includes enough built-in middleware to handle common requirements without a sprawling dependency tree.
For Express users who liked the simplicity but want better TypeScript support and runtime flexibility, Hono is the most natural transition. The routing API feels familiar, middleware works similarly, and you can be productive within minutes. Hono has grown rapidly in adoption, particularly among teams deploying to Cloudflare Workers and other edge platforms where Express can't run at all.
import { Hono } from 'hono';
import { zValidator } from '@hono/zod-validator';
import { z } from 'zod';
const app = new Hono();
app.get(
'/users/:id',
zValidator('param', z.object({ id: z.string() })),
async (c) => {
const { id } = c.req.valid('param');
const user = await getUserById(id);
return c.json(user);
}
);
export default app;
Hono is the Express of 2026 in terms of simplicity. The API is clean, the TypeScript support is native, and the runtime portability means you're not locked into Node.js. When deploying to Cloudflare Workers or similar edge platforms, Hono's tiny bundle size translates directly to faster cold starts and lower costs.
Like Express, Hono is focused on HTTP routing and middleware. It doesn't manage infrastructure, provide service discovery, or include built-in observability. For traditional server-side backends with databases and message queues, you'll still need to configure everything yourself. The ecosystem is growing but still smaller than Express's.
Consider Hono when deploying to edge platforms, when you want cross-runtime compatibility, or when building lightweight APIs where simplicity and bundle size matter more than infrastructure automation.
For a deeper comparison, see our Hono vs Encore.ts article.
Koa was created by the original Express team as a "next generation" web framework. It uses async/await natively (Express only added this properly in v5), has a cleaner middleware model based on the "onion" pattern, and provides a smaller, more focused core. If you like Express's philosophy but want a cleaner foundation, Koa is the closest alternative.
The middleware composition model is Koa's defining feature. Each middleware can run code before and after the next middleware, making it straightforward to implement patterns like request timing, error handling, and response transformation. Koa was ahead of its time when it launched, introducing async/await middleware years before Express added proper support. The framework remains a solid choice for developers who want Express's philosophy with cleaner internals.
import Koa from 'koa';
import Router from '@koa/router';
const app = new Koa();
const router = new Router();
router.get('/users/:id', async (ctx) => {
const user = await getUserById(ctx.params.id);
ctx.body = user;
});
app.use(router.routes());
app.use(router.allowedMethods());
app.listen(3000);
Koa's middleware model is more elegant than Express's. The cascading middleware pattern makes it easy to wrap request handling with cross-cutting concerns. The context object is cleaner than Express's separate request and response objects. If you found Express's middleware model confusing, Koa's approach is more predictable.
Koa's ecosystem is a fraction of Express's size. You'll need separate packages for routing, body parsing, and most other features. TypeScript support works but isn't first-class. The framework doesn't provide validation, infrastructure management, or observability. Development activity has slowed compared to other frameworks on this list, and the community is considerably smaller than Express, Fastify, or NestJS.
Consider Koa when you want Express's philosophy with a cleaner middleware model, when you're building a simple API and prefer a minimal foundation, or when the onion middleware pattern fits your architecture.
TypeScript's backend ecosystem has too many valid ways to structure things. AI coding agents pick a different combination of validation library, database access pattern, and project layout on every prompt. Express makes this worse because it has no structure at all - every AI prompt produces a completely different architecture with a different validation library, different ORM, different error handling pattern, and different project layout.
With Encore, the project's API patterns, infrastructure declarations, and service structure are already defined. An agent reads the existing conventions and follows them. There's one way to define an API, one way to declare a database, and one way to call another service.
Encore also provides an MCP server and editor rules (encore llm-rules init) that give agents access to database schemas, distributed traces, and service architecture. This means agents can generate queries that match your actual tables, debug with real request data, and verify their own work. Read more in How AI Agents Want to Write TypeScript.
The right Express alternative depends on what you're building and what frustrated you about Express:
Encore.ts addresses the most common complaints about Express: no built-in validation, no infrastructure management, no observability, and poor TypeScript integration. You get all of these from a single framework, plus significantly better performance. Encore's consistent conventions also mean AI coding agents can follow your project's patterns to generate production-ready code. For teams building production backends, especially with multiple services, Encore eliminates the most time-consuming parts of backend development that Express leaves to you.
Fastify is a good step up from Express if you want JSON Schema validation and a plugin architecture while keeping a similar mental model. It doesn't solve infrastructure or observability, but it adds structure where Express has none.
NestJS fits large teams that need strict architectural conventions. The learning curve is steep, but the consistency pays off when many developers work on the same codebase.
Hono is the right choice for edge computing and serverless. It's Express-like in philosophy but built for modern runtimes and minimal bundle sizes.
Koa is the conservative choice for teams that like Express but want cleaner middleware patterns. It won't change how you manage infrastructure or observability, but the code quality improvement over Express is real.
For most teams moving away from Express, the underlying issue is that Express requires too much manual configuration for production backends. You end up spending more time on glue code, middleware configuration, and infrastructure setup than on application logic. Encore.ts addresses this more comprehensively than any other framework on this list, handling validation, infrastructure, observability, and service communication as built-in capabilities rather than external concerns. It also delivers significantly better performance through its Rust-based runtime, which handles the heavy lifting outside the Node.js event loop.
Evaluating your options? Join our Discord community to discuss framework choices with other TypeScript developers.