Lesson 53-Fastify Basics Introduction

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 fastify

Project Structure

A basic Fastify application structure might look like:

fastify-app/
├── server.js
└── package.json

server.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 fastify

Creating 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();
    });
});
Share your love