Skip to content

Core Concepts

1. Introduction

Fastify’s core concepts—schemas, validation, hooks, and plugins—form the foundation of its high-performance and modular architecture. Understanding these concepts is essential for leveraging the full power of Fastify in building scalable, maintainable, and efficient microservices. This chapter dives into these core components, explaining how they work, why they are important, and how to use them effectively in your Fastify applications.

2. Schemas and Validation

Schemas are a key feature of Fastify, allowing developers to define the structure of data for requests and responses using JSON Schema. This approach not only ensures data consistency but also enhances performance by automatically validating data at the framework level, reducing the need for manual checks.

  1. Request and Response Schemas

    • Request Schemas: Define the expected structure of incoming data, such as parameters, query strings, headers, and request bodies. By validating the incoming data against these schemas, Fastify ensures that only correctly formatted data reaches your application logic, reducing errors and enhancing security.

    • Response Schemas: Define the structure of data sent back to the client. This helps ensure consistent and predictable responses, making your API easier to understand and consume.

  2. Validation with JSON Schema

    • Fastify uses AJV (Another JSON Validator), a highly efficient JSON Schema validator, to validate request and response data against defined schemas. This process is automatic and occurs before your request handlers are executed, saving time and reducing code complexity.
  3. Example: Defining a Schema for a Route

    Here’s an example of using schemas in a Fastify route:

    const createUserSchema = {
    body: {
    type: "object",
    required: ["name", "email"],
    properties: {
    name: { type: "string" },
    email: { type: "string", format: "email" },
    age: { type: "integer", minimum: 18 },
    },
    },
    response: {
    200: {
    type: "object",
    properties: {
    id: { type: "string" },
    name: { type: "string" },
    email: { type: "string" },
    },
    },
    },
    };
    fastify.post(
    "/users",
    { schema: createUserSchema },
    async (request, reply) => {
    const { name, email, age } = request.body;
    // Business logic to create a user
    reply.send({ id: "123", name, email });
    }
    );

    In this example, the createUserSchema validates that the request body contains a valid name and email, ensuring that incorrect data never reaches the business logic layer.

3. Hooks

Hooks in Fastify are powerful tools that allow you to run custom code at specific points in the request/response lifecycle. They provide a way to extend Fastify’s behavior without altering the main application logic.

  1. Types of Hooks

    • onRequest: Executed before a request is processed. Common uses include logging, authentication, and request modifications.
    • preHandler: Runs before the main request handler. Ideal for input validation, authorization checks, or data enrichment.
    • onSend: Executes just before the response is sent to the client, allowing you to modify the response payload or headers.
    • onResponse: Runs after the response has been sent, useful for logging, cleaning up resources, or metrics collection.
    • onError: Handles errors thrown during request processing, enabling customized error handling and responses.
    • onTimeout: Executed if a request times out. Useful for logging and managing long-running requests.
  2. Using Hooks in Fastify

    Hooks can be registered globally, for specific routes, or encapsulated within certain parts of your application, providing fine-grained control over their behavior.

    Example: Using a preHandler Hook for Authentication

    fastify.addHook("preHandler", async (request, reply) => {
    const { authorization } = request.headers;
    if (!authorization || authorization !== "Bearer valid-token") {
    reply.code(401).send({ error: "Unauthorized" });
    }
    });
    fastify.get("/protected", async (request, reply) => {
    reply.send({ message: "You have access to this protected route!" });
    });

    This example demonstrates a simple authentication mechanism using a preHandler hook, checking for a valid token before allowing access to the route.

4. Plugins

Plugins are at the heart of Fastify’s extensibility, allowing developers to modularize functionality and reuse code across different parts of the application. Plugins can encapsulate routes, hooks, utilities, and other functionalities, making the application easier to maintain and extend.

  1. Plugin Types

    • Utility Plugins: Extend Fastify’s core with new features like authentication, database connections, or caching.
    • Decorators: Add custom properties or methods to the Fastify instance, enabling shared functionality across routes.
    • Encapsulation: Plugins are isolated from the rest of the application, meaning changes within a plugin do not affect other parts of the system unless explicitly allowed.
  2. Creating and Registering Plugins

    Plugins are defined as functions that receive the Fastify instance, options, and an optional callback. They are registered using the fastify.register() method, which can include configuration options.

    Example: Creating a Custom Plugin

    myPlugin.ts
    import { FastifyPluginAsync } from "fastify";
    const myPlugin: FastifyPluginAsync = async (fastify, options) => {
    fastify.get("/plugin-route", async (request, reply) => {
    reply.send({ message: "This route is part of a custom plugin!" });
    });
    };
    export default myPlugin;
    // server.ts
    import fastify from "fastify";
    import myPlugin from "./myPlugin";
    const app = fastify();
    app.register(myPlugin);
    app.listen({ port: 3000 }, (err, address) => {
    if (err) {
    app.log.error(err);
    process.exit(1);
    }
    app.log.info(`Server running at ${address}`);
    });

    In this example, myPlugin registers a new route as part of a custom plugin, demonstrating how plugins can encapsulate functionality and enhance the modularity of your application.

5. Benefits of Using Schemas, Hooks, and Plugins

  • Performance Optimization: Schemas help validate and serialize data efficiently, reducing the need for manual checks and improving overall performance.
  • Code Reusability: Plugins allow developers to encapsulate and reuse functionality, keeping the core application code clean and focused.
  • Enhanced Control: Hooks provide precise control over request and response processing, allowing developers to add custom logic at critical points in the application flow.
  • Scalability: The modularity and encapsulation offered by schemas, hooks, and plugins enable the application to scale easily as new features are added or existing ones are modified.

6. Conclusion

Understanding Fastify’s core concepts—schemas, validation, hooks, and plugins—is essential for building robust, scalable, and high-performance APIs. These features not only streamline the development process but also provide the flexibility needed to customize and extend your application in line with evolving business requirements. By mastering these core components, you can leverage Fastify to its fullest potential, creating enterprise-grade microservices that are both efficient and easy to maintain.