Skip to the solution
MediumBackend7 min read

Fastify vs Express — how do they differ in architecture and performance?

226 views

The problem

Question presented to candidate: "Your team is starting a new Node.js API. Why would you consider Fastify over Express?"

What a strong answer should cover:

  • Express is middleware-first: a chain of (req, res, next) functions, no built-in request or response validation, JSON serialized by the generic JSON.stringify — whatever the object holds goes out.
  • Fastify is schema-first: routes declare a JSON Schema for their params, body, and response, and Fastify compiles a fast serializer from that schema ahead of time rather than calling the generic stringifier per request.
  • The schema is not just documentation — it does real work: request validation (reject malformed input before your handler runs) and response shaping.
  • The precise, worth-knowing behaviour: a response schema strips any field not listed in its properties, by default — no extra configuration needed. That is the opposite of generic JSON.stringify, and it is a genuine, useful allowlist.
  • The real gotcha is the other direction: explicitly setting additionalProperties: true on a response schema reopens that leak, letting an undeclared field back into the response. That is the setting worth watching for in a schema someone else wrote or generated.
  • Plugin encapsulation is Fastify's structural answer to Express's global app.use() — a plugin's decorations and hooks are scoped to it and its children by default, rather than leaking across the whole app.
  • Express's ecosystem size and familiarity are real advantages, not nothing — a large existing team, and the sheer number of Express-specific middleware packages, are legitimate reasons to stay.
  • Frame the choice honestly: Fastify's performance and validation story are real, structural advantages for a new service; migrating an existing large Express app for that reason alone is a much bigger cost-benefit question.

Clarifying questions expected:

  • "Is this greenfield or an existing Express codebase?" — changes the calculus completely.
  • "Does the team need response-shape guarantees, or just request validation?" — decides how much of the schema story actually matters here.

Code / implementation expected: Optional. The response-schema strip-by-default behaviour is the most convincing thing to actually show.

  • fastify
  • express
  • frameworks
  • performance

Try it yourself

Sketch your approach before you read the solution. That is what the interview tests.

Stuck? Get a hint.

A short nudge toward the approach. It will not give the answer away.

Solution

The solution is hidden so you can try first

7 min read · 9 sections

Read the code

JavaScriptThe same handler under three response schemas — default, reopened, and no schema
const Fastify = require("fastify");

// Same handler every time — it always returns a field the schema does not
// declare. What changes is the response schema attached to the route.
const handler = async (req) => ({ id: req.params.id, name: "Ada", secret: "should-not-leak" });

async function run(schema, label) {
  const app = Fastify();
  app.get("/x/:id", schema ? { schema } : {}, handler);
  await app.listen({ port: 0 });
  const { port } = app.server.address();
  const body = await (await fetch(`http://127.0.0.1:${port}/x/42`)).json();
  await app.close();
  console.log(label + ":", JSON.stringify(body));
}

(async () => {
  // ✅ Default behaviour: undeclared fields are stripped, no extra config.
  await run(
    { response: { 200: { type: "object", properties: { id: { type: "string" }, name: { type: "string" } } } } },
    "default (additionalProperties unset)",
  );
  // secret stripped: {"id":"42","name":"Ada"}

  // ❌ additionalProperties:true explicitly REOPENS the leak.
  await run(
    { response: { 200: { type: "object", properties: { id: { type: "string" }, name: { type: "string" } }, additionalProperties: true } } },
    "additionalProperties: true",
  );
  // secret leaks: {"id":"42","name":"Ada","secret":"should-not-leak"}

  // No schema at all: ordinary JSON.stringify, everything goes.
  await run(null, "no schema");
  // {"id":"42","name":"Ada","secret":"should-not-leak"}
})();

Discussion

Other approaches, follow-ups and how it went in real interviews.

Sign in to join the discussion.

No comments yet. Share how you would answer it.

Question 104 of 152 in Node.js

Keep your momentum going.

Mark this question solved to track your progress, then take the next one while it is fresh.

All Node.js questions
Up nextMedium

Explain what 'callback hell' is and how to avoid it.

Continue