Chapter 45: Node.js Frameworks
Node.js Frameworks — written as if we are sitting together at a table, with VS Code open, terminal running, and I’m explaining every important decision, every trade-off, and every real-world pattern people actually use in 2025–2026.
We will go slowly and honestly:
- Why frameworks exist in Node.js
- What are the real differences between the popular choices today
- When you should pick which one (and when you should not use any framework)
- Typical project structure & first working example for each major option
- Honest pros/cons from production experience (not marketing talk)
- What most serious teams are actually doing right now
Let’s start.
1. Why do people use frameworks on top of plain Node.js / Express?
Plain node:http is very powerful but very low-level.
You quickly realize you need to repeat the same 10–15 things in every project:
- Routing (GET /users, POST /tasks/:id, etc.)
- Parsing JSON bodies
- Handling query strings & path parameters
- Middleware chain (auth, logging, rate-limit, validation…)
- Error handling & proper HTTP responses
- File upload handling
- CORS, security headers, compression
- Request logging & tracing
- TypeScript integration
- Testing helpers
Frameworks save you from writing (and maintaining) this boilerplate again and again.
But they also add opinions, learning curve, dependencies, and sometimes performance cost.
So the real question in 2025–2026 is not “which framework is best?”, but:
“What trade-offs am I willing to accept for my current project/team/size/timeline?”
2. The current realistic landscape (early 2026)
| Framework / Runtime | Approx. popularity (2025–2026) | Main strength | Main weakness | Typical project size / team | Performance rank | TypeScript support | Current momentum |
|---|---|---|---|---|---|---|---|
| Express | ~55–65% | Huge ecosystem, everyone knows it | Very old design, lots of middleware hell | Small → large | Medium | Good | Stable / declining |
| Fastify | ~15–25% | Very fast, great plugin system, good types | Smaller ecosystem than Express | Medium → large | Very high | Excellent | Growing fast |
| NestJS | ~10–20% | Very structured, Angular-like, enterprise | Heavy, steep learning curve | Medium → very large | Medium | Excellent | Very strong |
| Hono | ~5–15% (exploding) | Extremely fast, tiny, works on Node + edge | Still young ecosystem | Small → medium | Extremely high | Very good | Explosive growth |
| Elysia | ~3–10% (fast rising) | Bun-first but excellent on Node, very typesafe | Still young, small community | Small → medium | Extremely high | Outstanding | Very strong |
| Koa | ~2–5% | Clean middleware, async/await native | Smaller ecosystem, less active | Small → medium | High | Good | Declining |
| plain node:http + undici | ~2–5% | Maximum control, minimal overhead | You build everything yourself | Small & performance-critical | Highest | Manual | Niche |
Quick 2025–2026 verdict
- Express — still the king for existing teams & quick prototypes
- Fastify — the pragmatic choice for most new medium/large APIs
- Hono / Elysia — winning the performance & developer joy battle (especially if you like edge/Cloudflare/Bun)
- NestJS — dominant in enterprise / big teams that want Angular-like structure
3. Realistic comparison table (what actually matters in production)
| Criterion | Express | Fastify | NestJS | Hono | Elysia |
|---|---|---|---|---|---|
| Learning curve | Very low | Low | Medium-High | Very low | Low |
| Performance (requests/sec) | Medium (~15–25k) | Very high (~40–70k) | Medium | Extremely high (~80–120k+) | Extremely high (~90–140k+) |
| TypeScript experience | Good (with @types/express) | Excellent | Outstanding | Very good | Outstanding |
| Ecosystem / middleware | Huge | Very good (plugins) | Very good (modules) | Growing fast | Growing fast |
| Startup time | Medium | Fast | Slow (reflection) | Extremely fast | Extremely fast |
| Bundle size (minimal API) | ~200–300 kB | ~50–100 kB | ~1–2 MB+ | ~10–30 kB | ~15–40 kB |
| Typical team size | Any | Medium–large | Large / enterprise | Small–medium | Small–medium |
| Edge runtime support | Poor | Medium | Poor | Excellent | Excellent |
4. Real example – same API built 3 ways (Express, Fastify, Hono)
We will create the same simple CRUD API in each style — so you can feel the differences.
4.1 Express version (classic)
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
// src/index.ts import express, { Request, Response } from 'express' import cors from 'cors' import helmet from 'helmet' import 'dotenv/config' const app = express() app.use(helmet()) app.use(cors()) app.use(express.json()) interface Task { id: string title: string completed: boolean } const tasks: Task[] = [] app.get('/tasks', (req: Request, res: Response) => { res.json(tasks) }) app.post('/tasks', (req: Request<{}, {}, { title: string }>, res: Response) => { const { title } = req.body if (!title || typeof title !== 'string') { return res.status(400).json({ error: 'Title is required' }) } const task: Task = { id: crypto.randomUUID(), title, completed: false } tasks.push(task) res.status(201).json(task) }) const PORT = Number(process.env.PORT) || 5000 app.listen(PORT, () => { console.log(`Express server → http://localhost:${PORT}`) }) |
4.2 Fastify version (faster & more typed)
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
// src/index.ts import Fastify from 'fastify' import crypto from 'node:crypto' const fastify = Fastify({ logger: true }) interface Task { id: string title: string completed: boolean } const tasks: Task[] = [] fastify.get('/tasks', async () => { return tasks }) fastify.post<{ Body: { title: string } }>('/tasks', async (request, reply) => { const { title } = request.body if (!title || typeof title !== 'string') { return reply.code(400).send({ error: 'Title is required' }) } const task: Task = { id: crypto.randomUUID(), title, completed: false } tasks.push(task) return reply.code(201).send(task) }) const start = async () => { try { await fastify.listen({ port: 5000 }) console.log('Fastify server → http://localhost:5000') } catch (err) { fastify.log.error(err) process.exit(1) } } start() |
4.3 Hono version (tiny, fast, edge-ready)
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
// src/index.ts import { Hono } from 'hono' import { logger } from 'hono/logger' import { cors } from 'hono/cors' import { crypto } from 'node:crypto' const app = new Hono() app.use('*', logger()) app.use('*', cors()) interface Task { id: string title: string completed: boolean } const tasks: Task[] = [] app.get('/tasks', (c) => { return c.json(tasks) }) app.post('/tasks', async (c) => { const { title } = await c.req.json<{ title: string }>() if (!title || typeof title !== 'string') { return c.json({ error: 'Title is required' }, 400) } const task: Task = { id: crypto.randomUUID(), title, completed: false } tasks.push(task) return c.json(task, 201) }) export default { port: 5000, fetch: app.fetch } |
Run with bun / node / tsx:
|
0 1 2 3 4 5 6 7 8 |
bun run src/index.ts # or npx tsx src/index.ts |
Which one feels best to you? Most developers who try all three end up preferring Fastify or Hono for new projects in 2025–2026.
5. Summary – Which framework should you choose right now?
| Your situation | Strongest recommendation (2025–2026) | Alternative choices | Why? |
|---|---|---|---|
| First Node.js project / learning | Express | Hono | Lowest learning curve, huge tutorials |
| Performance + developer joy (new project) | Fastify or Hono | Elysia | Very fast, great types, modern feel |
| Edge / serverless / Cloudflare / Vercel | Hono | Elysia | Extremely small & fast on edge runtimes |
| Enterprise / large team / Angular background | NestJS | — | Very structured, dependency injection, modules |
| Need maximum productivity & end-to-end types | tRPC + Hono/Fastify | — | Full type safety from client to server |
| Want minimal dependencies & maximum speed | plain http + undici | Hono | Zero abstraction cost |
My personal recommendation for most people right now (early 2026):
- Learning or small project → Express (fastest to get started)
- Real product / serious backend → Fastify or Hono
- Edge/serverless → Hono
- Large enterprise team → NestJS
Which direction would you like to go next?
- Build the same CRUD API in Fastify + Zod + TypeBox
- Add JWT authentication + middleware in different frameworks
- Compare performance (wrk / autocannon) on the same machine
- Show full production setup (Docker, logging with pino, rate limiting, tracing)
- Explain how to migrate from Express to Fastify/Hono
Just tell me what you want to see — I’ll continue with complete, realistic code and explanations. 😊
