Fastify was built specifically to fix the two things Express leaves on the table: raw throughput and built-in schema validation, and both show up directly in how you write route handlers.
Fastify is a Node.js web framework designed around JSON schema validation and serialization at the route level, plus a plugin encapsulation model that keeps large applications modular by default rather than by convention. It consistently benchmarks among the fastest Node.js frameworks, and the speed isn't a trick — it comes from compiling your route schemas into fast serializers ahead of time.
Why Fastify Matters (and When to Skip It)
Fastify bakes in what Express leaves to third-party middleware: request/response validation and structured logging (via pino) ship built in, not bolted on. For APIs where request throughput genuinely matters — high-traffic services, latency-sensitive endpoints — the schema-compilation approach measurably outperforms Express's more general-purpose request handling.
Skip Fastify if your team already has deep Express expertise and the project doesn't have a measured performance requirement — rewriting a working Express service for Fastify's throughput gains rarely pays for itself unless you're actually bottlenecked.
Getting Started with Fastify
Schema validation is built into route definitions, not bolted on as separate middleware:
import Fastify from "fastify";
const app = Fastify({ logger: true });
const createUserSchema = {
body: {
type: "object",
required: ["email", "name"],
properties: {
email: { type: "string", format: "email" },
name: { type: "string", minLength: 1 },
},
},
};
app.post("/users", { schema: createUserSchema }, async (request, reply) => {
const { email, name } = request.body as { email: string; name: string };
const user = await db.users.create({ email, name });
return user;
});
app.listen({ port: 3000 });
Requests that don't match createUserSchema are rejected with a 400 before your handler even runs — no manual validation code needed.
Core Fastify Concepts Every Developer Should Know
Schemas validate both requests and responses, and response schemas double as serialization instructions — Fastify uses them to generate a fast JSON serializer, which is a meaningful chunk of its performance advantage over JSON.stringify on arbitrary objects.
const schema = {
response: {
200: {
type: "object",
properties: {
id: { type: "string" },
email: { type: "string" },
},
},
},
};
Plugins provide true encapsulation, not just route grouping. Decorators, hooks, and schemas registered inside a plugin don't leak out unless explicitly exposed — this prevents the "everything is global" problem that large Express apps tend toward:
import fp from "fastify-plugin";
async function usersPlugin(app: FastifyInstance) {
app.decorate("userService", new UserService());
app.get("/users/:id", async (req) => {
return app.userService.findById((req.params as any).id);
});
}
app.register(usersPlugin, { prefix: "/api" });
Hooks replace much of what Express middleware does, but with more granular lifecycle points (onRequest, preValidation, preHandler, onSend) — useful for auth checks that need to run before validation versus after.
Common Fastify Mistakes and How to Fix Them
Mistake 1: skipping schemas "to move faster." Without a schema, you lose both validation and the serialization performance benefit — you're paying Fastify's learning curve without getting its main advantages. Fix: define at least a response schema for every route from the start; body schemas can follow once the shape stabilizes.
Mistake 2: registering plugins without fastify-plugin. Plugins are encapsulated by default, which is usually what you want — but decorators meant to be shared across the whole app (like a database connection) need fastify-plugin to break out of that encapsulation. Fix: wrap shared/global plugins with fp().
Mistake 3: using the wrong lifecycle hook for auth. Running auth checks in preHandler when a route also needs body validation first can let unauthenticated requests trigger validation logic. Fix: use preValidation if auth should run before body validation.
When Should You Use Fastify Instead of Express?
Use Fastify when you're building an API where throughput is measured and matters, or where you want request/response validation as a first-class, enforced part of every route rather than optional middleware. Stick with Express for smaller services or teams where its larger middleware ecosystem and familiarity outweigh the performance difference.
Fastify in Production
Combine Fastify's schema validation with TypeScript using @fastify/type-provider-json-schema-to-ts or a Zod-based type provider — this gets you compile-time types generated directly from the same schema used for runtime validation, so the two never drift apart. Also lean on the built-in pino logger's structured JSON output; it pairs well with log aggregation tools in a way console.log never does.
If you're not already hitting a throughput ceiling with Express, that's fine — but the next high-traffic API you build, benchmark both before committing, since the schema-first workflow alone is often worth the switch regardless of raw speed.