Fastify Overview
Fastify is a low-overhead, high-performance Node.js web framework designed for rapid route handling and minimal runtime overhead. Created by Nicolò Ribaudo, who also authored the PostgreSQL Node.js driver pg, Fastify is built to be user-friendly while prioritizing performance.
History and Features
First released in 2016, Fastify has matured into a robust framework used in production by many developers and organizations. Key features include:
- High Performance: Fastify minimizes middleware overhead and optimizes internal processes for speed.
- Plugin System: Supports plugins to add functionality without compromising core performance.
- Type Safety: Compatible with TypeScript, offering strong type definitions for maintainable and scalable applications.
- Concise API: Designed to be intuitive and easy to use.
- Flexibility: Provides multiple ways to register routes and middleware for tailored application development.
Comparison with Express and Koa
- Express: The most popular Node.js web framework with a rich ecosystem but higher runtime overhead due to its flexibility.
- Koa: A successor to Express, leveraging ES6 async/await for cleaner asynchronous code, but generally less performant than Fastify.
- Fastify: Focuses on performance and low overhead, often outperforming Express and Koa in benchmarks.
Installing Fastify and Initializing a Project
Ensure Node.js and npm are installed, then create a new project and install Fastify:
mkdir fastify-app
cd fastify-app
npm init -y
npm install fastifyProject Structure
A basic Fastify application structure might look like:
fastify-app/
├── server.js
└── package.jsonserver.js serves as the main entry point.
Writing Your First Fastify Application
In server.js, set up a Fastify application:
const fastify = require('fastify')({ logger: true });
// Register a simple GET route
fastify.get('/', function (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();This code creates a Fastify instance, registers a GET route that returns a JSON object at the root path, and listens on port 3000.
Handling HTTP Requests
The reply object provides methods to handle HTTP responses, such as sending JSON, text, or redirects:
fastify.get('/json', function (request, reply) {
reply.send({ message: 'Hello in JSON' });
});
fastify.get('/text', function (request, reply) {
reply.type('text/plain').send('Hello in plain text');
});
fastify.get('/redirect', function (request, reply) {
reply.redirect(302, '/');
});These examples demonstrate Fastify’s flexibility in handling various HTTP response types.
Fastify Basic Usage
Registering Routes and Middleware
Registering routes and middleware in Fastify is straightforward. Here are some examples:
Registering Routes
const fastify = require('fastify')();
fastify.get('/', async (request, reply) => {
return { hello: 'world' };
});
fastify.post('/post', async (request, reply) => {
const body = request.body;
return { received: body };
});This registers a GET route for the root path and a POST route for /post.
Registering Middleware
Middleware in Fastify is called “pre-processors” and can be applied globally or to specific routes:
fastify.addHook('onRequest', async function (request, reply) {
console.log('Handling request');
});
fastify.addHook('preHandler', async function (request, reply) {
console.log('Before handler');
});
fastify.addHook('onSend', async function (request, reply) {
console.log('About to send response');
});These hooks execute at request arrival, before handling, and before sending the response, respectively.
Error Handling
Fastify supports global and local error handling.
Global Error Handling
Register a global error handler to catch unhandled errors:
fastify.setErrorHandler((error, request, reply) => {
reply.status(500).send({ error: 'Something bad happened!' });
});Local Error Handling
For specific routes, register error handlers:
fastify.get('/error', async (request, reply) => {
throw new Error('An error occurred');
}, (error, request, reply) => {
reply.status(500).send({ error: 'An error occurred on this route' });
});If the /error route throws an error, the registered error handler is invoked.
Fastify Routing
Route Registration and Handling
Fastify supports route registration for HTTP methods like GET, POST, PUT, DELETE, etc. Example of a GET route:
const fastify = require('fastify')();
fastify.get('/hello', async (request, reply) => {
return { greeting: 'Hello from Fastify!' };
});
fastify.listen({ port: 3000 }, (err) => {
if (err) {
fastify.log.error(err);
process.exit(1);
}
fastify.log.info(`Server listening on ${fastify.server.address().port}`);
});The fastify.get method takes a path and a callback that receives request (containing request details) and reply (for constructing responses).
Using Middleware for Request and Response Handling
Fastify’s middleware, referred to as “pre-processors” and “post-processors,” operate at different request stages:
onRequest: Before request processing.preValidation: Before request validation.preHandler: Before request handling.onSend: Before response sending.onResponse: After response sending.
Example of registering and using middleware:
const fastify = require('fastify')();
fastify.addHook('onRequest', (request, reply, done) => {
console.log('Request received');
done();
});
fastify.addHook('preHandler', (request, reply, done) => {
console.log('Handling request');
done();
});
fastify.addHook('onSend', (request, reply, payload, done) => {
console.log('About to send response');
done(null, payload);
});
fastify.get('/hello', async (request, reply) => {
return { greeting: 'Hello from Fastify!' };
});
fastify.listen({ port: 3000 }, (err) => {
if (err) {
fastify.log.error(err);
process.exit(1);
}
fastify.log.info(`Server listening on ${fastify.server.address().port}`);
});This registers onRequest, preHandler, and onSend middleware, each with a done callback to signal completion.
Advanced Routing and Middleware Usage
Fastify supports complex routing and middleware patterns:
- Nested Routes: Register sub-routes under a parent route.
- Conditional Routes: Dynamically register or unregister routes based on conditions.
- Decorators: Inject additional functionality into route handlers, such as logging or authentication.
Example using a decorator:
fastify.decorateReply('log', function (message) {
console.log(message);
});
fastify.get('/hello', { onRequest: [fastify.log] }, async (request, reply) => {
reply.log('Request handled');
return { greeting: 'Hello from Fastify!' };
});Here, decorateReply adds a log method to the reply object, used in the route handler.
Fastify Middleware
Using Middleware
Registering Middleware:
fastify.register((instance, options, next) => {
instance.addHook('onRequest', (request, reply, done) => {
console.log('Middleware running');
done();
});
next();
});Adding Middleware:
fastify.use((request, reply, next) => {
console.log('This is a middleware');
next();
});Example Code
Global Middleware:
fastify.use((request, reply, next) => {
console.log('Global middleware');
next();
});Route-Level Middleware:
fastify.get('/users', { preHandler: [middleware] }, (request, reply) => {
return { users: ['Alice', 'Bob'] };
});Custom Middleware
Defining Middleware:
function myMiddleware(request, reply, next) {
// Do something
next();
}Using Custom Middleware:
fastify.use(myMiddleware);Error Handling
Error Handling Middleware:
fastify.use((request, reply, next) => {
throw new Error('Something went wrong');
});
fastify.setErrorHandler((error, request, reply) => {
console.error(error);
reply.code(500).send({ error: 'Internal server error' });
});Testing Middleware
Writing Unit Tests:
const { expect } = require('chai');
const fastify = require('fastify')();
describe('Middleware', () => {
it('should run the middleware', (done) => {
fastify.use((request, reply, next) => {
next();
});
fastify.inject({
method: 'GET',
url: '/'
}, (err, res) => {
expect(res.statusCode).to.equal(200);
done();
});
});
});Fastify Logging
Quick Start
Fastify includes a built-in logging system for recording application runtime information. Log levels include trace, debug, info, warn, error, and fatal.
Configuring Logging
Installing Fastify:
npm install fastifyCreating a Basic Application:
const fastify = require('fastify')({ logger: true });
fastify.get('/', async (request, reply) => {
return { hello: 'world' };
});
fastify.listen({ port: 3000 });Using Built-In Logging
Log Output:
fastify.log.trace('This is a trace message');
fastify.log.debug('This is a debug message');
fastify.log.info('This is an info message');
fastify.log.warn('This is a warning message');
fastify.log.error('This is an error message');
fastify.log.fatal('This is a fatal message');Custom Logging Configuration
Modifying Log Level:
const fastify = require('fastify')({ logger: { level: 'info' } });Using a Custom Logging Library:
const pino = require('pino');
const fastify = require('fastify')({ logger: pino() });Example Code
Basic Logging:
fastify.get('/', async (request, reply) => {
fastify.log.info('Handling GET / request');
return { hello: 'world' };
});Logging Errors:
fastify.get('/error', async (request, reply) => {
try {
throw new Error('Simulated error');
} catch (error) {
fastify.log.error(error, 'Error occurred');
reply.code(500).send({ error: 'Internal server error' });
}
});Log Formatting
JSON Format:
fastify.log.info({ user: 'admin' }, 'User logged in');Text Format:
fastify.log.info('User logged in');Testing Logging
Writing Unit Tests:
const { expect } = require('chai');
const fastify = require('fastify')({ logger: true });
describe('Logging', () => {
it('should log info messages', (done) => {
fastify.log.info('Test info message');
// Use a mock to verify logging
done();
});
});



