Fastify is a high-performance, low-overhead web framework for Node.js. It's designed to be fast, efficient, and developer-friendly.
Why Fastify?
Performance
Fastify is one of the fastest Node.js frameworks.
javascript
// Benchmarks (requests per second)
Express: ~30,000 req/s
Koa: ~40,000 req/s
Hapi: ~35,000 req/s
Fastify: ~65,000 req/s (2x faster than Express!)Key Features
- Fast: Highly optimized for performance
- Schema-Based: Built-in JSON schema validation
- TypeScript: First-class TypeScript support
- Extensible: Plugin architecture
- Logging: Built-in logging with Pino
- Developer Friendly: Easy to learn and use
Getting Started
Installation
bash
npm install fastify
# or
yarn add fastifyBasic Server
javascript
// server.js
const fastify = require('fastify')({ logger: true });
// Declare a route
fastify.get('/', async (request, reply) => {
return { hello: 'world' };
});
// Start the server
const start = async () => {
try {
await fastify.listen({ port: 3000 });
} catch (err) {
fastify.log.error(err);
process.exit(1);
}
};
start();TypeScript Version
typescript
// server.ts
import Fastify from 'fastify';
const fastify = Fastify({
logger: true,
});
fastify.get('/', async (request, reply) => {
return { hello: 'world' };
});
const start = async () => {
try {
await fastify.listen({ port: 3000 });
} catch (err) {
fastify.log.error(err);
process.exit(1);
}
};
start();Routing
Basic Routes
javascript
fastify.get('/', async (request, reply) => {
return { message: 'GET /' };
});
fastify.post('/', async (request, reply) => {
return { message: 'POST /' };
});
fastify.put('/user/:id', async (request, reply) => {
const { id } = request.params;
return { userId: id };
});
fastify.delete('/user/:id', async (request, reply) => {
return { deleted: true };
});Route Parameters
javascript
fastify.get('/user/:id', async (request, reply) => {
const { id } = request.params;
return { userId: id };
});
fastify.get('/posts/:year/:month', async (request, reply) => {
const { year, month } = request.params;
return { year, month };
});Query Strings
javascript
fastify.get('/search', async (request, reply) => {
const { q, limit = 10 } = request.query;
return { query: q, limit };
});Request and Response
Accessing Request Body
javascript
fastify.post('/user', async (request, reply) => {
const { name, email } = request.body;
return { name, email };
});Setting Status Codes
javascript
fastify.post('/user', async (request, reply) => {
// Create user
const user = await createUser(request.body);
reply.code(201).send(user);
});
fastify.delete('/user/:id', async (request, reply) => {
const { id } = request.params;
await deleteUser(id);
reply.code(204).send();
});Headers
javascript
fastify.get('/data', async (request, reply) => {
reply.header('X-Custom-Header', 'value');
reply.header('Content-Type', 'application/json');
return { data: 'example' };
});Validation
JSON Schema Validation
javascript
const fastify = require('fastify')();
fastify.post('/user', {
schema: {
body: {
type: 'object',
required: ['name', 'email'],
properties: {
name: { type: 'string', minLength: 3 },
email: { type: 'string', format: 'email' },
age: { type: 'number', minimum: 0 },
},
},
response: {
201: {
type: 'object',
properties: {
id: { type: 'string' },
name: { type: 'string' },
email: { type: 'string' },
},
},
},
},
}, async (request, reply) => {
const user = await createUser(request.body);
reply.code(201).send(user);
});Validation Errors
javascript
fastify.setErrorHandler((error, request, reply) => {
if (error.validation) {
reply.status(400).send({
error: 'Validation Failed',
details: error.validation,
});
} else {
reply.send(error);
}
});Plugins
Creating a Plugin
javascript
// plugins/db.js
const fp = require('fastify-plugin');
async function dbPlugin(fastify, options) {
const { connection } = options;
fastify.decorate('db', {
async query(sql, params) {
// Database query logic
return results;
},
});
fastify.addHook('onClose', async (instance) => {
// Cleanup
});
}
module.exports = fp(dbPlugin, {
name: 'db',
});Using Plugins
javascript
const fastify = require('fastify')();
// Register plugin
fastify.register(require('./plugins/db'), {
connection: 'postgres://...',
});
// Use in routes
fastify.get('/users', async (request, reply) => {
const users = await fastify.db.query('SELECT * FROM users');
return { users };
});Middleware
Using Middleware
javascript
fastify.use((req, res, next) => {
// Express-style middleware
console.log('Middleware');
next();
});
// Or Fastify hooks
fastify.addHook('onRequest', async (request, reply) => {
console.log('Request received');
});
fastify.addHook('preHandler', async (request, reply) => {
console.log('Before handler');
});
fastify.addHook('onResponse', async (request, reply) => {
console.log('Response sent');
});Authentication
Basic Auth
javascript
const fastify = require('fastify')();
fastify.register(require('@fastify/basic-auth'), {
validate: async (username, password, req, reply) => {
const user = await authenticate(username, password);
if (!user) {
return new Error('Invalid credentials');
}
return user;
},
});
fastify.after(() => {
fastify.route({
method: 'GET',
url: '/secret',
onRequest: fastify.basicAuth,
handler: async (request, reply) => {
return { secret: 'data' };
},
});
});JWT Authentication
javascript
const fastify = require('fastify')();
fastify.register(require('@fastify/jwt'), {
secret: 'supersecret',
});
fastify.post('/login', async (request, reply) => {
const { email, password } = request.body;
const user = await authenticate(email, password);
const token = fastify.jwt.sign({ id: user.id });
return { token };
});
fastify.get('/protected', {
onRequest: async (request, reply) => {
try {
await request.jwtVerify();
} catch (err) {
reply.send(err);
}
},
}, async (request, reply) => {
return { user: request.user };
});Database Integration
PostgreSQL with Prisma
javascript
const fastify = require('fastify')();
const { PrismaClient } = require('@prisma/client');
const prisma = new PrismaClient();
fastify.decorateRequest('prisma', prisma);
fastify.get('/users', async (request, reply) => {
const users = await request.prisma.user.findMany();
return { users };
});
fastify.post('/users', async (request, reply) => {
const { name, email } = request.body;
const user = await request.prisma.user.create({
data: { name, email },
});
reply.code(201).send(user);
});Error Handling
Global Error Handler
javascript
fastify.setErrorHandler((error, request, reply) => {
fastify.log.error(error);
reply.status(error.statusCode || 500).send({
error: {
message: error.message,
code: error.code,
},
});
});Custom Errors
javascript
class AppError extends Error {
constructor(message, statusCode) {
super(message);
this.statusCode = statusCode;
}
}
fastify.get('/user/:id', async (request, reply) => {
const user = await findUser(request.params.id);
if (!user) {
throw new AppError('User not found', 404);
}
return user;
});Testing
With Jest
javascript
const app = require('./app');
const fastify = app.fastify;
test('GET / route', async () => {
const response = await fastify.inject({
method: 'GET',
url: '/',
});
expect(response.statusCode).toBe(200);
expect(response.json()).toEqual({ hello: 'world' });
});
test('POST /user creates user', async () => {
const response = await fastify.inject({
method: 'POST',
url: '/user',
payload: {
name: 'John Doe',
email: 'john@example.com',
},
});
expect(response.statusCode).toBe(201);
expect(response.json()).toHaveProperty('id');
});Best Practices
DOs
- Use schema validation
- Leverage TypeScript
- Use plugins for reusable functionality
- Implement proper error handling
- Use async/await consistently
- Log important events
- Test your routes
DON'Ts
- Skip validation
- Ignore error handling
- Use callbacks (use async/await)
- Mix Express and Fastify patterns
- Forget about security
- Skip testing
- Overcomplicate routes
Performance Tips
Enable Response Validation
javascript
fastify.setReplySerializer((payload, statusCode) => {
// Custom serialization
return JSON.stringify(payload);
});Use TypeScript
typescript
import Fastify, {
FastifyInstance,
FastifyRequest,
FastifyReply,
} from 'fastify';
const fastify: FastifyInstance = Fastify({ logger: true });
interface User {
id: number;
name: string;
email: string;
}
fastify.get<{}>('/users', async (request, reply) => {
const users: User[] = await getUsers();
return { users };
});Conclusion
Fastify offers excellent performance with a great developer experience. Use schema validation, leverage the plugin ecosystem, and take advantage of TypeScript support. Start building fast APIs with Fastify today!