Fastify TypeScript Setup: High-Performance Node.js API Framework
Fastify is one of the fastest Node.js web frameworks, designed for maximum performance with minimal overhead. This comprehensive guide covers setting up Fastify v5.6.0 with TypeScript for production-ready API development.
Why Choose Fastify?
- Lightning Fast: Up to 30% faster than Express.js
- TypeScript Native: First-class TypeScript support
- Schema Validation: Built-in JSON Schema validation
- Plugin Architecture: Extensible plugin ecosystem
- HTTP/2 Support: Native HTTP/2 and HTTPS support
Step 1: Project Setup
Initialize your Fastify TypeScript project:
# Create project directory
mkdir fastify-typescript-api
cd fastify-typescript-api
# Initialize package.json
npm init -y
# Install Fastify v5.6.0 and essential plugins
npm install fastify@5.6.0 @fastify/cors @fastify/helmet @fastify/rate-limit
npm install @fastify/jwt @fastify/cookie @fastify/multipart
# Install TypeScript development dependencies
npm install -D typescript@5.7.2 @types/node@22.10.2 tsx@4.19.2 nodemon@3.1.9
npm install -D @typescript-eslint/eslint-plugin@8.18.1 @typescript-eslint/parser@8.18.1
npm install -D prettier@3.4.2 eslint@9.17.0
Step 2: TypeScript Configuration
Configure TypeScript for optimal development experience:
{
"compilerOptions": {
"target": "ES2022",
"lib": ["ES2022"],
"module": "CommonJS",
"moduleResolution": "node",
"allowSyntheticDefaultImports": true,
"esModuleInterop": true,
"allowJs": true,
"outDir": "./dist",
"rootDir": "./src",
"removeComments": true,
"strict": true,
"noImplicitAny": true,
"strictNullChecks": true,
"strictFunctionTypes": true,
"noImplicitThis": true,
"alwaysStrict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"baseUrl": ".",
"paths": {
"@/*": ["src/*"],
"@/types/*": ["src/types/*"],
"@/routes/*": ["src/routes/*"],
"@/plugins/*": ["src/plugins/*"],
"@/utils/*": ["src/utils/*"]
}
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}
tsconfig.json
Step 3: Application Structure
Create the main application file:
import Fastify, { FastifyInstance } from "fastify";
import cors from "@fastify/cors";
import helmet from "@fastify/helmet";
import rateLimit from "@fastify/rate-limit";
// Import routes
import userRoutes from "./routes/users";
import authRoutes from "./routes/auth";
export async function createServer(): Promise<FastifyInstance> {
const server = Fastify({
logger: {
level: process.env.LOG_LEVEL || "info",
transport:
process.env.NODE_ENV === "development"
? {
target: "pino-pretty",
options: {
colorize: true,
},
}
: undefined,
},
});
// Register security plugins
await server.register(helmet, {
global: true,
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
styleSrc: ["'self'", "'unsafe-inline'"],
scriptSrc: ["'self'"],
imgSrc: ["'self'", "data:", "https:"],
},
},
});
await server.register(cors, {
origin:
process.env.NODE_ENV === "production" ? ["https://yourdomain.com"] : true,
credentials: true,
});
await server.register(rateLimit, {
global: true,
max: 100,
timeWindow: "1 minute",
addHeaders: {
"x-ratelimit-limit": true,
"x-ratelimit-remaining": true,
"x-ratelimit-reset": true,
"retry-after": true,
},
});
// Register routes
await server.register(userRoutes, { prefix: "/api/users" });
await server.register(authRoutes, { prefix: "/api/auth" });
// Health check endpoint
server.get("/health", async (request, reply) => {
return { status: "ok", timestamp: new Date().toISOString() };
});
return server;
}
src/app.ts
Step 4: Route Handlers with Validation
Create type-safe route handlers:
import {
FastifyInstance,
FastifyPluginOptions,
FastifyReply,
FastifyRequest,
} from "fastify";
interface CreateUserRequest {
Body: {
name: string;
email: string;
age: number;
};
}
interface GetUserRequest {
Params: {
id: string;
};
}
const createUserSchema = {
type: "object",
required: ["name", "email", "age"],
properties: {
name: { type: "string", minLength: 1, maxLength: 100 },
email: { type: "string", format: "email" },
age: { type: "integer", minimum: 0, maximum: 150 },
},
};
const getUserSchema = {
type: "object",
required: ["id"],
properties: {
id: { type: "string", pattern: "^[0-9]+$" },
},
};
async function userRoutes(
server: FastifyInstance,
options: FastifyPluginOptions
) {
// Create user
server.post<CreateUserRequest>(
"/",
{
schema: {
body: createUserSchema,
response: {
201: {
type: "object",
properties: {
id: { type: "string" },
name: { type: "string" },
email: { type: "string" },
age: { type: "number" },
createdAt: { type: "string" },
},
},
},
},
},
async (request: FastifyRequest<CreateUserRequest>, reply: FastifyReply) => {
const { name, email, age } = request.body;
// Simulate user creation
const user = {
id: Math.random().toString(36).substr(2, 9),
name,
email,
age,
createdAt: new Date().toISOString(),
};
reply.status(201).send(user);
}
);
// Get user by ID
server.get<GetUserRequest>(
"/:id",
{
schema: {
params: getUserSchema,
response: {
200: {
type: "object",
properties: {
id: { type: "string" },
name: { type: "string" },
email: { type: "string" },
age: { type: "number" },
createdAt: { type: "string" },
},
},
404: {
type: "object",
properties: {
error: { type: "string" },
message: { type: "string" },
},
},
},
},
},
async (request: FastifyRequest<GetUserRequest>, reply: FastifyReply) => {
const { id } = request.params;
// Simulate user lookup
if (id === "404") {
reply.status(404).send({
error: "Not Found",
message: "User not found",
});
return;
}
const user = {
id,
name: "John Doe",
email: "john@example.com",
age: 30,
createdAt: new Date().toISOString(),
};
reply.send(user);
}
);
// Get all users
server.get("/", async (request, reply) => {
const users = [
{
id: "1",
name: "John Doe",
email: "john@example.com",
age: 30,
createdAt: new Date().toISOString(),
},
{
id: "2",
name: "Jane Smith",
email: "jane@example.com",
age: 25,
createdAt: new Date().toISOString(),
},
];
reply.send({ users, total: users.length });
});
}
export default userRoutes;
src/routes/users.ts
Step 5: Authentication Routes
Implement JWT-based authentication:
import {
FastifyInstance,
FastifyPluginOptions,
FastifyReply,
FastifyRequest,
} from "fastify";
import jwt from "@fastify/jwt";
interface LoginRequest {
Body: {
email: string;
password: string;
};
}
const loginSchema = {
type: "object",
required: ["email", "password"],
properties: {
email: { type: "string", format: "email" },
password: { type: "string", minLength: 6 },
},
};
async function authRoutes(
server: FastifyInstance,
options: FastifyPluginOptions
) {
// Register JWT plugin
await server.register(jwt, {
secret: process.env.JWT_SECRET || "supersecret",
});
// Login endpoint
server.post<LoginRequest>(
"/login",
{
schema: {
body: loginSchema,
response: {
200: {
type: "object",
properties: {
token: { type: "string" },
user: {
type: "object",
properties: {
id: { type: "string" },
email: { type: "string" },
name: { type: "string" },
},
},
},
},
401: {
type: "object",
properties: {
error: { type: "string" },
message: { type: "string" },
},
},
},
},
},
async (request: FastifyRequest<LoginRequest>, reply: FastifyReply) => {
const { email, password } = request.body;
// Simple authentication check
if (email === "admin@example.com" && password === "password123") {
const user = {
id: "1",
email: "admin@example.com",
name: "Admin User",
};
const token = server.jwt.sign({
userId: user.id,
email: user.email,
});
reply.send({ token, user });
} else {
reply.status(401).send({
error: "Unauthorized",
message: "Invalid credentials",
});
}
}
);
// Protected route example
server.get(
"/profile",
{
onRequest: [server.authenticate],
},
async (request, reply) => {
const user = {
id: "1",
email: "admin@example.com",
name: "Admin User",
profile: {
bio: "System Administrator",
avatar: "https://example.com/avatar.jpg",
},
};
reply.send({ user });
}
);
}
export default authRoutes;
src/routes/auth.ts
Step 6: Server Entry Point
Create the main server file:
import { createServer } from "./app";
const start = async () => {
try {
const server = await createServer();
const port = parseInt(process.env.PORT || "3000", 10);
const host = process.env.HOST || "0.0.0.0";
await server.listen({ port, host });
server.log.info(`🚀 Server running at http://${host}:${port}`);
// Graceful shutdown
const signals: NodeJS.Signals[] = ["SIGINT", "SIGTERM"];
signals.forEach(signal => {
process.on(signal, async () => {
server.log.info(`Received ${signal}, shutting down gracefully`);
try {
await server.close();
process.exit(0);
} catch (error) {
server.log.error("Error during shutdown:", error);
process.exit(1);
}
});
});
} catch (error) {
console.error("Error starting server:", error);
process.exit(1);
}
};
start();
src/server.ts
Step 7: Package.json Scripts
Add development and build scripts:
{
"name": "fastify-typescript-api",
"version": "1.0.0",
"description": "High-performance Fastify API with TypeScript",
"main": "dist/server.js",
"scripts": {
"dev": "tsx watch src/server.ts",
"build": "tsc",
"start": "node dist/server.js",
"lint": "eslint src/**/*.ts",
"lint:fix": "eslint src/**/*.ts --fix",
"format": "prettier --write src/**/*.ts",
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": ["fastify", "typescript", "api", "nodejs", "performance"],
"author": "Webtrophy Team",
"license": "MIT",
"engines": {
"node": ">=18.0.0"
}
}
package.json
Step 8: Environment Configuration
Create environment configuration:
# Server Configuration
NODE_ENV=development
PORT=3000
HOST=0.0.0.0
LOG_LEVEL=info
# JWT Configuration
JWT_SECRET=your-super-secret-jwt-key
# Database Configuration (if using)
DATABASE_URL=postgresql://user:password@localhost:5432/fastify_db
# Rate Limiting
RATE_LIMIT_MAX=100
RATE_LIMIT_WINDOW=60000
# CORS Origins
CORS_ORIGINS=http://localhost:3000,https://yourdomain.com
.env
Step 9: Docker Configuration
Create Docker setup for production:
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY tsconfig.json ./
COPY src ./src
RUN npm run build
FROM node:20-alpine AS production
WORKDIR /app
RUN addgroup -g 1001 -S nodejs
RUN adduser -S fastify -u 1001
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
COPY package*.json ./
RUN chown -R fastify:nodejs /app
USER fastify
EXPOSE 3000
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD curl -f http://localhost:3000/health || exit 1
CMD ["node", "dist/server.js"]
Dockerfile
version: "3.8"
services:
api:
build: .
ports:
- "3000:3000"
environment:
- NODE_ENV=production
- PORT=3000
- JWT_SECRET=${JWT_SECRET}
restart: unless-stopped
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 60s
redis:
image: redis:7-alpine
ports:
- "6379:6379"
restart: unless-stopped
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 5s
retries: 3
docker-compose.yml
Step 10: Testing Setup
Create test configuration:
import { createServer } from "../app";
import { FastifyInstance } from "fastify";
describe("Fastify App", () => {
let server: FastifyInstance;
beforeAll(async () => {
server = await createServer();
await server.ready();
});
afterAll(async () => {
await server.close();
});
test("health check should return 200", async () => {
const response = await server.inject({
method: "GET",
url: "/health",
});
expect(response.statusCode).toBe(200);
expect(JSON.parse(response.payload)).toHaveProperty("status", "ok");
});
test("should create user successfully", async () => {
const userData = {
name: "Test User",
email: "test@example.com",
age: 25,
};
const response = await server.inject({
method: "POST",
url: "/api/users",
payload: userData,
});
expect(response.statusCode).toBe(201);
const result = JSON.parse(response.payload);
expect(result).toHaveProperty("name", userData.name);
expect(result).toHaveProperty("email", userData.email);
});
test("should authenticate user", async () => {
const credentials = {
email: "admin@example.com",
password: "password123",
};
const response = await server.inject({
method: "POST",
url: "/api/auth/login",
payload: credentials,
});
expect(response.statusCode).toBe(200);
const result = JSON.parse(response.payload);
expect(result).toHaveProperty("token");
expect(result).toHaveProperty("user");
});
});
src/__tests__/app.test.ts
Performance Optimization Tips
- Use Schema Validation: Fastify’s built-in JSON Schema validation is extremely fast
- Enable Logging: Structured logging helps with debugging and monitoring
- Use Plugins Wisely: Only register plugins you actually need
- HTTP/2 Support: Enable HTTP/2 for better performance
- Connection Pooling: Use connection pools for databases
- Caching: Implement caching strategies for frequently accessed data
- Type Providers: Use TypeBox or JSON Schema to TypeScript for better type safety
Best Practices
- Always validate input data using schemas
- Use TypeScript interfaces for type safety
- Implement proper error handling
- Use environment variables for configuration
- Add comprehensive logging
- Write tests for your endpoints
- Use proper HTTP status codes
- Implement rate limiting and security headers
- Use
fastify-plugin
for reusable plugins - Leverage Fastify’s built-in serialization
Version Information
- Fastify: v5.6.0 (Latest stable)
- TypeScript: v5.7.2 (Latest stable)
- Node.js: v20 LTS (Recommended)
- Last Updated: September 2025
Your Fastify TypeScript application is now ready for high-performance API development with type safety, validation, and production-ready configuration!