ADR: Framework Selection
Choosing Express 5, Fastify 5, or NestJS 11 shapes hiring, performance, and test strategy for years. This ADR template scores options against measurable squad constraints - not framework religion.
Recipe
Quick-reference recipe card - copy-paste ready.
# ADR-NNN: HTTP Framework for <Service>
## Status
Proposed | Accepted | Superseded by ADR-XXX
## Context
- Endpoints: N REST (+ GraphQL?)
- Team: M engineers, hiring pipeline
- SLO: p95 latency, RPS peak
- Existing: Express 4 monolith? shared auth package?
## Scoring (1-5, weight in parentheses)
| Criterion | Weight | Express 5 | Fastify 5 | NestJS 11 |
| --- | --- | --- | --- | --- |
| Throughput / latency | 25% | | | |
| Team familiarity | 20% | | | |
| Structure at scale | 20% | | | |
| Testing ergonomics | 15% | | | |
| Ecosystem / middleware | 10% | | | |
| Migration cost | 10% | | | |
| **Weighted total** | | | | |
## Decision
We will use <framework> because <one sentence>.
## Consequences
### Positive
- ...
### Negative
- ...
## Review Trigger
Revisit when endpoints > X or p95 exceeds Y under load test.When to reach for this:
- Greenfield API after proof-of-concept
- Express 4 → 5 or Express → Fastify migration proposal
- "Should we NestJS this?" from new EM or acquirer
- Performance SLO miss blamed on framework without evidence
Working Example
# ADR-003: HTTP Framework for Orders API
## Status
Accepted (2026-07-09)
## Context
- B2B orders API: 42 REST endpoints, webhooks, no GraphQL
- Squad: 6 engineers, 2 familiar with Nest, 4 with Express
- SLO: p95 200ms at 8k RPS peak; error budget 99.9%
- Greenfield service; shared `@acme/auth` Fastify plugin exists
- Node 24.18.0, TypeScript 5.6, Postgres + Prisma
## Scoring
| Criterion | Weight | Express 5 | Fastify 5 | NestJS 11 |
| --- | --- | --- | --- | --- |
| Throughput / latency | 25% | 3 | 5 | 4 |
| Team familiarity | 20% | 4 | 3 | 3 |
| Structure at scale | 20% | 2 | 4 | 5 |
| Testing ergonomics | 15% | 3 | 5 | 4 |
| Ecosystem | 10% | 5 | 4 | 4 |
| Migration cost | 10% | 5 | 4 | 3 |
| **Weighted** | | 3.35 | **4.30** | 3.95 |
## Decision
**Fastify 5** with modular plugins and shared `@acme/auth-fastify`.
NestJS rejected: DI overhead unjustified below 60 endpoints and no microservices split planned.
## Consequences
### Positive
- `inject()` tests without port binding
- JSON schema validation compiled at boot
- Reuse internal Fastify auth plugin
### Negative
- Juniors learn plugin encapsulation curve
- Fewer Stack Overflow answers than Express
- Hiring: spell out Fastify in JD
## Node.js Notes
- Pino logging built-in; align with platform OTel SDK
- Express 5 migration path documented if acquisition forces it (ADR supersede candidate)
## Review Trigger
- Endpoint count > 80 OR second squad owns same deployable
- p95 > 250ms after DB tuning (don't blame framework first)What this demonstrates:
- Numbers force disagreement into criteria, not slogans
- Migration cost weighted even on greenfield (exit strategy)
- Review trigger prevents eternal "Accepted"
Deep Dive
Criterion Definitions
| Criterion | Measure |
|---|---|
| Throughput | autocannon or k6 on hello-world + typical JSON handler |
| Structure | Modules without god app.ts; Nest modules vs Fastify plugins |
| Testing | inject() / supertest speed; DI mock cost in Nest |
| Ecosystem | Passport, rate-limit, OpenAPI generators you need |
| Migration | Person-weeks from current codebase |
Framework Snapshots (Node 24)
| Framework | Sweet spot | Watch out |
|---|---|---|
| Express 5 | Familiarity, vast middleware | Easy to build unmaintainable monolith |
| Fastify 5 | Performance, schema-first | Plugin encapsulation learning curve |
| NestJS 11 | Large teams, modular monolith | Cold start and DI overhead on tiny APIs |
When Express Still Wins
- Acquirer mandates Express for integration
- Team of 2, < 15 endpoints, speed to market over structure
- Critical middleware only exists for Express (verify before assuming)
When Nest Wins
- 3+ squads on one deployable
- Microservices with multiple transporters planned
- Team from Spring/Java expects DI and decorators
Gotchas
- Benchmark hello-world only - Misleading. Fix: Benchmark your largest DTO handler.
- Ignoring shared platform packages - Internal auth plugin picks winner. Fix: Score ecosystem including internal libs.
- ADR without review trigger - Accepted forever. Fix: Endpoint count or SLO threshold.
- Framework as performance band-aid - p95 is DB. Fix: Tune pool before Fastify migration.
- Deleting rejected options - Future hires re-litigate. Fix: Keep scores in ADR appendix.
Alternatives
| Alternative | Use When | Don't Use When |
|---|---|---|
| Hono | Edge + small API | Heavy Nest-style DI needs |
| tRPC | TS monolith front+back | Public REST API contract required |
| Spike only, no ADR | 2-week throwaway | Production service |
FAQs
Express 5 vs staying on 4?
Express 5 is the current major with improved routing and promise rejection handling. New ADRs should cite Express 5 on Node 24.
Can we mix Nest and Fastify?
Nest uses Fastify adapter - valid ADR outcome. Document adapter choice in Node.js Notes.
Who signs the ADR?
Tech lead authors; EM acknowledges delivery impact; platform architect for org-wide alignment.
Related
- Fastify vs Express ADR - migration ADR
- Framework Selection Checklist - quick checklist
- ADR: Monolith vs Services - macro shape
- Tech Lead Basics on Node - triangle ownership
- Technical Leadership Best Practices - leadership checklist
Stack versions: This page was written for Node.js 24.18.0 (Active LTS), npm 10+, TypeScript 5.6+, Express 5, Fastify 5, and NestJS 11.