Skip to content
All essays
WebMarch 27, 202412 min

Introduction to Fastify: High-Performance Node.js Framework

Discover why Fastify is one of the fastest Node.js web frameworks and how to get started.

Ü
Ümit Uz
Mobile & Full Stack Developer

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 fastify

Basic 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!

Enjoyed this essay? Share it.

Related essays

Next essay
Building Fastify Plugins and Middleware