Fastify Basics
10 examples to build your first Fastify 5 API - 7 basic and 3 intermediate.
Search across all documentation pages
10 examples to build your first Fastify 5 API - 7 basic and 3 intermediate.
mkdir fastify-api && cd fastify-api
npm init -y
npm pkg set type=module
npm install fastify@5
npm install -D typescript@5.6 tsxFor schema validation and plugin patterns, see JSON Schema Validation and Fastify Plugins.
import Fastify from "fastify";
const app = Fastify({ logger: true });
app.get("/", async () => {
return { message: "Hello from Fastify 5" };
});
await app.listen({ port: 3000, host: "0.0.0.0" });logger: true enables Pino with sensible defaultsawait app.listen() (Fastify 5 async listen)import Fastify from "fastify";
const app = Fastify();
app.get("/users/:id", {
schema: {
params: {
type: "object",
properties: { id: { type: "string", format: "uuid" } },
required: ["id"],
},
response: {
200: {
type: "object",
properties: { id: { type: "string" }, name: { type: "string" } },
},
},
},
}, async (req) => {
return { id: req.params.id, name: "Ada Lovelace" };
});import Fastify from "fastify";
const app = Fastify();
app.post("/users", {
schema: {
body: {
type: "object",
required: ["name", "email"],
properties: {
name: { type: "string", minLength: 1 },
email: { type: "string", format: "email" },
},
},
},
}, async (req, reply) => {
const { name, email } = req.body as { name: string; email: string };
return reply.status(201).send({ id: "1", name, email });
});reply.status(201).send() for explicit status codesimport Fastify from "fastify";
const app = Fastify();
app.register(async function userRoutes(fastify) {
fastify.get("/", async () => [{ id: 1, name: "Ada" }]);
fastify.get("/:id", async (req) => ({ id: (req.params as { id: string }).id }));
}, { prefix: "/users" });register creates an encapsulated scopeprefix mounts all routes under /usersimport Fastify from "fastify";
const app = Fastify({ logger: true });
app.addHook("onRequest", async (req) => {
req.headers["x-request-start"] = String(Date.now());
});
app.addHook("onResponse", async (req, reply) => {
const start = Number(req.headers["x-request-start"]);
req.log.info({ duration: Date.now() - start, status: reply.statusCode });
});
app.get("/health", async () => ({ ok: true }));onRequest runs before routing; preHandler runs before the handlerimport Fastify from "fastify";
const app = Fastify();
app.setErrorHandler((err, _req, reply) => {
const status = err.statusCode ?? 500;
reply.status(status).send({
error: status === 500 ? "Internal server error" : err.message,
});
});
app.get("/fail", async () => {
throw Object.assign(new Error("Not found"), { statusCode: 404 });
});setErrorHandler is the centralized error handlerstatusCode to errors for operational errorsstatusCode: 400import Fastify from "fastify";
const app = Fastify({
logger: {
level: process.env.LOG_LEVEL ?? "info",
transport: process.env.NODE_ENV === "development"
? { target: "pino-pretty" }
: undefined,
},
});
app.get("/users", async (req) => {
req.log.info({ action: "list_users" });
return [];
});req.log is a child logger with request contextimport Fastify from "fastify";
import fp from "fastify-plugin";
const authPlugin = fp(async (fastify) => {
fastify.decorate("authenticate", async (req: { headers: { authorization?: string } }) => {
if (!req.headers.authorization) throw Object.assign(new Error("Unauthorized"), { statusCode: 401 });
});
});
const app = Fastify();
await app.register(authPlugin);
// authenticate is available in all scopesfastify-plugin, decorators are scoped to the registering pluginfp for shared auth, database connections, and configimport Fastify from "fastify";
import { test } from "node:test";
import assert from "node:assert/strict";
const app = Fastify();
app.get("/health", async () => ({ ok: true }));
test("GET /health", async () => {
const res = await app.inject({ method: "GET", url: "/health" });
assert.equal(res.statusCode, 200);
assert.deepEqual(res.json(), { ok: true });
});inject() simulates HTTP without network I/Oimport Fastify from "fastify";
const app = Fastify();
await app.listen({ port: 3000 });
const server = app.server;
server.keepAliveTimeout = 65_000;
server.headersTimeout = 66_000;
server.requestTimeout = 30_000;app.server after listenVia @fastify/express or middie, but prefer native Fastify plugins. Express middleware loses schema validation benefits.
Fastify for performance and schema-first APIs. Express for maximum ecosystem and team familiarity. See Fastify vs Express ADR.
Yes. Fastify 5 is ESM-first. Use import Fastify from "fastify".
Use fastify-type-provider-zod or @fastify/type-provider-typebox for inferred types from schemas.
Yes. NestFactory.create(AppModule, new FastifyAdapter()). See NestJS Basics.
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.
Reviewed by Chris St. John·Last updated Jul 16, 2026