Fastify TypeScript API Development: High-Performance Server Framework
Fastify is a fast and low-overhead Node.js web framework. Combined with TypeScript, it provides a robust platform for building high-performance APIs. This guide covers setup, routing, schema validation, plugin development, and production best practices.
Why Choose Fastify with TypeScript?
- Performance: One of the fastest Node.js frameworks
- Schema Validation: Built-in JSON schema validation
- Extensibility: Plugin-based architecture
- Developer Experience: TypeScript support with auto-generated types
- Production Ready: Logging, metrics, and lifecycle hooks
Step 1: Project Setup and Configuration
Initialize a Fastify project with TypeScript and essential tooling:
# Create project directory
mkdir fastify-ts-api
cd fastify-ts-api
# Initialize package.json
npm init -y
# Install Fastify and TypeScript
npm install fastify fastify-plugin fastify-autoload
npm install -D typescript ts-node-dev @types/node
# Install validation and utilities
npm install fastify-sensible @fastify/jwt @fastify/cors
npm install zod
npm install mercurius
# Install development dependencies
npm install -D eslint @typescript-eslint/parser @typescript-eslint/eslint-plugin
npm install -D prettier husky lint-staged
Configure TypeScript in tsconfig.json
:
{
"compilerOptions": {
"target": "ES2020",
"lib": ["ES2020"],
"module": "CommonJS",
"rootDir": "src",
"outDir": "dist",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true
},
"include": ["src"]
}
Step 2: Fastify Server Setup
Create src/server.ts
:
import Fastify from "fastify";
import sensible from "fastify-sensible";
import cors from "@fastify/cors";
import jwt from "@fastify/jwt";
import autoload from "@fastify/autoload";
import path from "path";
export async function buildServer() {
const fastify = Fastify({
logger: { level: "info" },
});
// Register plugins
fastify.register(sensible);
fastify.register(cors, { origin: true });
fastify.register(jwt, { secret: process.env.JWT_SECRET! });
// Load routes and plugins automatically
fastify.register(autoload, {
dir: path.join(__dirname, "plugins"),
options: { prefix: "/plugins" },
});
fastify.register(autoload, {
dir: path.join(__dirname, "routes"),
options: { prefix: "/api" },
});
return fastify;
}
// If run directly, start server
if (require.main === module) {
buildServer()
.then(fastify => fastify.listen({ port: 3000 }))
.catch(err => {
console.error(err);
process.exit(1);
});
}
Step 3: Route Definition and Validation
Use Zod and JSON schema for route validation. Example src/routes/users.ts
:
import { FastifyPluginAsync } from "fastify";
import { z } from "zod";
const userSchema = z.object({
id: z.string().uuid(),
email: z.string().email(),
name: z.string().min(1),
});
const createUserBody = z.object({
email: z.string().email(),
name: z.string().min(1),
password: z.string().min(8),
});
type CreateUserBody = z.infer<typeof createUserBody>;
const userRoutes: FastifyPluginAsync = async fastify => {
fastify.get<{ Params: { id: string } }>(
"/:id",
{
schema: {
params: {
type: "object",
properties: { id: { type: "string", format: "uuid" } },
required: ["id"],
},
},
},
async (request, reply) => {
const { id } = request.params;
const user = await fastify.prisma.user.findUnique({ where: { id } });
if (!user) {
return reply.notFound("User not found");
}
return user;
}
);
fastify.post<{ Body: CreateUserBody }>(
"/",
{
schema: {
body: createUserBody.toJSON(),
},
},
async (request, reply) => {
const parsed = createUserBody.parse(request.body);
const hash = await fastify.bcrypt.hash(parsed.password);
const user = await fastify.prisma.user.create({
data: { email: parsed.email, name: parsed.name, passwordHash: hash },
});
return reply.code(201).send(user);
}
);
};
export default userRoutes;
Step 4: Plugin Development
Example custom plugin src/plugins/logger.ts
:
import fp from "fastify-plugin";
export default fp(async fastify => {
fastify.addHook("onRequest", async request => {
request.log.info(
{ method: request.method, url: request.url },
"Incoming request"
);
});
fastify.addHook("onResponse", async (request, reply) => {
request.log.info({ statusCode: reply.statusCode }, "Response sent");
});
});
Step 5: Authentication and Authorization
Use Fastify JWT and hooks. Example in src/routes/auth.ts
:
import { FastifyPluginAsync } from "fastify";
import { z } from "zod";
const loginBody = z.object({ email: z.string().email(), password: z.string() });
type LoginBody = z.infer<typeof loginBody>;
const authRoutes: FastifyPluginAsync = async fastify => {
fastify.post<{ Body: LoginBody }>("/login", async (request, reply) => {
const { email, password } = loginBody.parse(request.body);
const user = await fastify.prisma.user.findUnique({ where: { email } });
if (!user) return reply.unauthorized();
const ok = await fastify.bcrypt.compare(password, user.passwordHash);
if (!ok) return reply.unauthorized();
const token = fastify.jwt.sign({ userId: user.id });
return { token };
});
fastify.get(
"/profile",
{ preHandler: [fastify.authenticate] },
async (request, reply) => {
const { userId } = request.user as { userId: string };
const user = await fastify.prisma.user.findUnique({
where: { id: userId },
});
return user;
}
);
};
export default authRoutes;
Step 6: Production Best Practices
- Logging: Use Pino for structured logs
- Monitoring: Integrate Prometheus metrics via
fastify-metrics
- Schema Validation: Use Zod + JSON schema for fast validation
- Cluster Mode: Run behind PM2 or Kubernetes with multiple instances
- Security: Enable HTTPS, helmet, and rate limiting
- CI/CD: Automate lint, test, and build pipelines
- Documentation: Generate OpenAPI docs with
fastify-oas
- Graceful Shutdown: Handle SIGINT/SIGTERM to close DB connections
Development Commands
# Dev with auto reload
npx ts-node-dev --respawn src/server.ts
# Build
npm run build
# Start production server
node dist/server.js
# Run lint and tests
npm run lint
npm test
Your Fastify TypeScript API is now configured for high performance, type safety, and production readiness!