Skip to main content
โšก Calmops

Fastify: The Fast Node.js Web Framework

Fastify is a fast and low overhead web framework for Node.js that focuses on developer experience and performance. This comprehensive guide covers everything you need to know about building applications with Fastify.

What is Fastify?

Fastify is a web framework heavily inspired by Hapi and Express but with a focus on performance and developer experience.

const fastify = require('fastify')({ logger: true });

fastify.get('/', async (request, reply) => {
  return { hello: 'world' };
});

fastify.listen({ port: 3000 }, (err, address) => {
  if (err) throw err;
  console.log(`Server listening on ${address}`);
});

Key Features

  • High Performance - Up to 3x faster than Express
    • Schema-based - JSON Schema for validation and serialization
  • Extensible - Plugin-based architecture
  • TypeScript Support - Built-in TypeScript support
  • Logger - Built-in Pino logger
  • Hooks - Lifecycle hooks for customization
  • Decorators - Extend Fastify instance

Basic Routing

GET Requests

const fastify = require('fastify')();

fastify.get('/users', async (request, reply) => {
  return [
    { id: 1, name: 'John' },
    { id: 2, name: 'Jane' }
  ];
});

fastify.get('/users/:id', {
  schema: {
    params: {
      type: 'object',
      properties: {
        id: { type: 'integer' }
      }
    }
  }
}, async (request, reply) => {
  const { id } = request.params;
  return { id, name: `User ${id}` };
});

POST Requests

fastify.post('/users', {
  schema: {
    body: {
      type: 'object',
      required: ['name', 'email'],
      properties: {
        name: { type: 'string', minLength: 1 },
        email: { type: 'string', format: 'email' }
      }
    }
  }
}, async (request, reply) => {
  const { name, email } = request.body;
  
  // Create user...
  return { id: 3, name, email }, 201;
});

All HTTP Methods

fastify.get('/route', handler);
fastify.post('/route', handler);
fastify.put('/route', handler);
fastify.patch('/route', handler);
fastify.delete('/route', handler);
fastify.options('/route', handler);
fastify.head('/route', handler);

Route Options

fastify.get('/users', {
  schema: { /* validation schemas */ },
  preHandler: fastify.auth([authenticate]),
  config: { rateLimit: { max: 100 } },
  preValidation: async (request, reply) => {
    // Pre-validation hook
  }
}, async (request, reply) => {
  return { users: [] };
});

Schema Validation

Request Validation

// Query string validation
fastify.get('/search', {
  schema: {
    querystring: {
      type: 'object',
      properties: {
        q: { type: 'string' },
        limit: { type: 'integer', default: 10 },
        offset: { type: 'integer', default: 0 }
      }
    }
  }
}, async (request) => {
  const { q, limit, offset } = request.query;
  return { q, limit, offset };
});

// Headers validation
fastify.get('/secure', {
  schema: {
    headers: {
      type: 'object',
      required: ['authorization'],
      properties: {
        authorization: { type: 'string' }
      }
    }
  }
}, handler);

Response Validation

// Response schema for serialization
fastify.get('/user/:id', {
  schema: {
    params: {
      type: 'object',
      properties: {
        id: { type: 'integer' }
      }
    },
    response: {
      200: {
        type: 'object',
        properties: {
          id: { type: 'integer' },
          name: { type: 'string' },
          email: { type: 'string' }
        }
      }
    }
  }
}, async (request) => {
  return { id: 1, name: 'John', email: '[email protected]' };
});

Ajv Options

const fastify = require('fastify')({
  ajv: {
    customOptions: {
      removeAdditional: 'all',
      useDefaults: true,
      coerceTypes: true
    }
  }
});

Plugins

Creating Plugins

// my-plugin.js
async function myPlugin(fastify, options) {
  // Decorate fastify instance
  fastify.decorate('utility', {
    greet: (name) => `Hello, ${name}!`
  });
  
  // Add a route
  fastify.get('/plugin-route', async () => 'Plugin route');
  
  // Add hooks
  fastify.addHook('onRequest', async (request) => {
    console.log('Plugin hook called');
  });
}

module.exports = myPlugin;

Registering Plugins

const fastify = require('fastify')();
const myPlugin = require('./my-plugin');

// Register with options
fastify.register(myPlugin, { prefix: '/api' });

// Register multiple
fastify.register(require('./auth-plugin'));
fastify.register(require('./database-plugin'));

Encapsulation

fastify.register(async function (fastify, opts) {
  // This route is in child scope
  fastify.get('/child-route', async () => 'Child scope');
  
  // Decorations here only affect this scope
  fastify.decorate('secret', 'child-secret');
}, { prefix: '/v1' });

Middleware and Hooks

Lifecycle Hooks

// Request hooks
fastify.addHook('onRequest', async (request, reply) => {
  // Called for every request
});

fastify.addHook('preParsing', async (request, reply) => {
  // Before parsing
});

fastify.addHook('preValidation', async (request, reply) => {
  // Before validation
});

fastify.addHook('preHandler', async (request, reply) => {
  // Before handler
});

fastify.addHook('handler', async (request, reply) => {
  // Around the handler (wrap)
});

fastify.addHook('preSerialization', async (request, reply, payload) => {
  // Before serialization
});

fastify.addHook('onSend', async (request, reply, payload) => {
  // Modify payload
  return payload;
});

fastify.addHook('onResponse', async (request, reply, done) => {
  // After response sent
});

fastify.addHook('onTimeout', async (request, reply, done) => {
  // On timeout
});

fastify.addHook('onError', async (request, reply, error) => {
  // On error
});

Error Handling Hooks

fastify.setErrorHandler((error, request, reply) => {
  if (error.validation) {
    return reply.status(400).send({
      error: 'Validation Error',
      message: error.message,
      validation: error.validation
    });
  }
  
  return reply.status(500).send({
    error: 'Internal Server Error'
  });
});

fastify.setNotFoundHandler((request, reply) => {
  reply.status(404).send({
    error: 'Not Found',
    message: `Route ${request.method} ${request.url} not found`
  });
});

Decorators

Adding Decorators

// Decorate instance
fastify.decorate('utils', {
  calculate: (a, b) => a + b
});

// Decorate reply
fastify.decorateReply('sendJson', function (data) {
  this.send(data);
});

// Decorate request
fastify.decorateRequest('session', null);

fastify.addHook('onRequest', async (request) => {
  request.session = { userId: 1 };
});

// Usage
fastify.get('/', async (request, reply) => {
  const result = fastify.utils.calculate(1, 2);
  reply.sendJson({ result });
});

Request and Reply

Request Object

fastify.get('/request-info', async (request, reply) => {
  // Route parameters
  request.params;
  
  // Query string
  request.query;
  
  // Body (parsed)
  request.body;
  
  // Headers
  request.headers;
  
  // Method & URL
  request.method;
  request.url;
  
  // Raw node.js objects
  request.raw;
  
  // Request ID
  request.id;
  
  // Fastify specific
  request.server;
  request.log;
  
  return { info: 'received' };
});

Reply Object

fastify.get('/responses', async (request, reply) => {
  // Send JSON
  return { message: 'hello' };
  
  // Or explicitly
  reply.send({ message: 'hello' });
  
  // Status code
  reply.code(201);
  
  // Headers
  reply.header('Cache-Control', 'no-cache');
  reply.headers({ 'X-Custom': 'value' });
  
  // Type
  reply.type('application/json');
  
  // Redirect
  reply.redirect('/other-route');
  
  // Send with status
  reply.code(201).send({ created: true });
  
  // End response
  reply.raw.end('Done');
});

Error Handling

Sync and Async Errors

// Sync error (throws)
fastify.get('/error-sync', async () => {
  throw new Error('Sync error');
});

// Async error (rejects)
fastify.get('/error-async', async () => {
  await somePromiseThatRejects();
});

// Error in promise
fastify.get('/error-promise', async () => {
  return Promise.reject(new Error('Async error'));
});

// Use try/catch
fastify.get('/safe', async (request, reply) => {
  try {
    return await riskyOperation();
  } catch (error) {
    reply.code(500).send({ error: error.message });
  }
});

Custom Error Handling

fastify.setErrorHandler((error, request, reply) => {
  request.log.error(error);
  
  if (error.code === 'FST_REQ_VALIDATION') {
    return reply.status(400).send({
      error: 'Bad Request',
      message: error.message,
      validation: error.validationContext
    });
  }
  
  if (error.statusCode === 404) {
    return reply.status(404).send({ error: 'Not Found' });
  }
  
  return reply.status(500).send({ error: 'Internal Server Error' });
});

Validation Example

const fastify = require('fastify')({ logger: true });

// Users routes
fastify.register(async function usersRoutes(fastify) {
  
  // GET /users
  fastify.get('/', {
    schema: {
      querystring: {
        type: 'object',
        properties: {
          page: { type: 'integer', default: 1 },
          limit: { type: 'integer', default: 10 }
        }
      }
    }
  }, async (request, reply) => {
    const { page, limit } = request.query;
    // Fetch users with pagination
    return {
      users: [
        { id: 1, name: 'John' },
        { id: 2, name: 'Jane' }
      ],
      page,
      limit
    };
  });
  
  // POST /users
  fastify.post('/', {
    schema: {
      body: {
        type: 'object',
        required: ['name', 'email'],
        properties: {
          name: { type: 'string', minLength: 1, maxLength: 100 },
          email: { type: 'string', format: 'email' },
          age: { type: 'integer', minimum: 0 }
        }
      },
      response: {
        201: {
          type: 'object',
          properties: {
            id: { type: 'integer' },
            name: { type: 'string' },
            email: { type: 'string' }
          }
        }
      }
    }
  }, async (request, reply) => {
    const { name, email, age } = request.body;
    
    // Create user
    const user = {
      id: Math.floor(Math.random() * 1000),
      name,
      email
    };
    
    reply.code(201);
    return user;
  });
  
  // GET /users/:id
  fastify.get('/:id', {
    schema: {
      params: {
        type: 'object',
        properties: {
          id: { type: 'integer' }
        }
      }
    }
  }, async (request, reply) => {
    const { id } = request.params;
    return { id, name: 'John', email: '[email protected]' };
  });
}, { prefix: '/users' });

// Start server
fastify.listen({ port: 3000 }, (err) => {
  if (err) {
    fastify.log.error(err);
    process.exit(1);
  }
});

Performance Tips

Connection Timeout

const fastify = require('fastify')({
  connectionTimeout: 10000,
  keepAliveTimeout: 5000
});

Disable Request Logging

const fastify = require('fastify')({
  logger: false
});

Schema Caching

const fastify = require('fastify')({
  schemaController: {
    compilersFactory: {
      buildValidatorCompiler() {
        return ajvInstance;
      }
    }
  }
});

Testing

const fastify = require('fastify')();

fastify.get('/hello', async (request, reply) => {
  return { hello: 'world' };
});

// Using built-in test client
const t = require('tap');

t.test('routes', async (t) => {
  t.test('hello route', async (t) => {
    const response = await fastify.inject({
      method: 'GET',
      url: '/hello'
    });
    
    t.equal(response.statusCode, 200);
    t.same(response.json(), { hello: 'world' });
  });
});
// Async/await testing
const fastify = require('./app');

async function test() {
  const response = await fastify.inject({
    method: 'GET',
    url: '/users'
  });
  
  console.log(response.statusCode);
  console.log(response.body);
  
  await fastify.close();
}

test();

Comparison with Express

Feature Fastify Express
Performance 3x faster Baseline
Schema Validation Built-in Middleware
TypeScript Native Community
Async/Await Native Polyfill
Plugin System Built-in Connect
Logger Pino Morgan

External Resources

Conclusion

Fastify provides excellent performance and developer experience for Node.js applications. Key points:

  • Use JSON Schema for validation and serialization
  • Leverage the plugin system for modularity
  • Take advantage of TypeScript support
  • Use hooks for request lifecycle control
  • Fastify’s encapsulation prevents plugin conflicts

For high-performance Node.js APIs, Fastify is an excellent choice over Express.

Comments