How to Build a REST API with Fastify?

Fastify is a high-performance web framework designed for Node.js backend development. Known for its lightweight architecture and impressive speed, Fastify positions itself as a faster alternative to popular frameworks like Express, Koa, and Hapi.

What sets Fastify apart is its plugin-centric architecture?everything is treated as a plugin, allowing for better modularity and code reusability. This approach enables developers to encapsulate functionality and distribute it across projects efficiently.

In this tutorial, we will explore how to build a complete REST API with Fastify, covering:

  • Creating a basic Fastify server

  • Defining API routes with proper controllers

  • Implementing schema validation for requests

  • Using hooks for request lifecycle management

Requirements and Installation

Before building our REST API, ensure you have the following prerequisites:

  • Node.js (version 14 or higher)

  • A tool for testing endpoints (Postman, cURL, or Thunder Client)

First, verify your Node.js version:

node -v

Expected output:

v16.16.0

Create a new Node.js project:

npm init -y

Install Fastify as a dependency:

npm i fastify --save

Creating a Basic Fastify Server

Let's start by creating our main server file. Create an index.js file:

touch index.js

Add the following code to index.js:

// Initialize Fastify with logging enabled
const app = require('fastify')({
   logger: true
})

// Define a simple route
app.get('/', function (req, reply) {
   reply.send({
      Welcome: 'TutorialsPoint'
   })
})

// Start the server
app.listen({ port: 3000 }, (err, address) => {
   if (err) {
      app.log.error(err)
      process.exit(1)
   }
   app.log.info(`Server listening on ${address}`)
})

Run the server:

node index.js

Test the endpoint using cURL:

curl http://localhost:3000

Expected response:

{"Welcome":"TutorialsPoint"}

Building a Complete Books API

Now let's create a comprehensive REST API for managing books. We'll implement the following endpoints:

  • GET /api/books - Get all books

  • GET /api/books/:id - Get a specific book

  • POST /api/books - Add a new book

  • PUT /api/books/:id - Update a book

  • DELETE /api/books/:id - Delete a book

Creating the Books Controller

Create a controller directory and add books.js:

let books = [{
   id: 1,
   title: 'Maharana Pratap: The Invincible Warrior',
   author: 'Rima Hooja'
}, {
   id: 2,
   title: 'Prithviraj Chauhan - A Light on the Mist in History',
   author: 'Virendra Singh Rathore'
}, {
   id: 3,
   title: 'Rani Laxmibai: Warrior-Queen of Jhansi',
   author: 'Pratibha Ranade'
}]

// Get all books
const getAllBooks = async (req, reply) => {
   return books
}

// Get single book by ID
const getBook = async (req, reply) => {
   const id = Number(req.params.id)
   const book = books.find(book => book.id === id)
   
   if (!book) {
      reply.code(404).send({ error: 'Book not found' })
      return
   }
   
   return book
}

// Add new book
const addBook = async (req, reply) => {
   const id = books.length + 1
   const newBook = {
      id,
      title: req.body.title,
      author: req.body.author
   }
   books.push(newBook)
   
   reply.code(201).send(newBook)
}

// Update existing book
const updateBook = async (req, reply) => {
   const id = Number(req.params.id)
   const bookIndex = books.findIndex(book => book.id === id)
   
   if (bookIndex === -1) {
      reply.code(404).send({ error: 'Book not found' })
      return
   }
   
   books[bookIndex] = {
      id,
      title: req.body.title,
      author: req.body.author
   }
   
   return books[bookIndex]
}

// Delete book
const deleteBook = async (req, reply) => {
   const id = Number(req.params.id)
   const initialLength = books.length
   
   books = books.filter(book => book.id !== id)
   
   if (books.length === initialLength) {
      reply.code(404).send({ error: 'Book not found' })
      return
   }
   
   return { message: `Book with ID ${id} deleted successfully` }
}

// Schema validation for getting a book
const getBookValidation = {
   params: {
      type: 'object',
      properties: {
         id: { type: 'string', pattern: '^[0-9]+$' }
      },
      required: ['id']
   },
   response: {
      200: {
         type: 'object',
         properties: {
            id: { type: 'integer' },
            title: { type: 'string' },
            author: { type: 'string' }
         }
      }
   }
}

module.exports = {
   getAllBooks,
   getBook,
   addBook,
   updateBook,
   deleteBook,
   getBookValidation
}

Setting Up Routes

Create a routes directory and add books.js:

const booksController = require('../controller/books')

const routes = [{
   method: 'GET',
   url: '/api/books',
   handler: booksController.getAllBooks
}, {
   method: 'GET',
   url: '/api/books/:id',
   schema: booksController.getBookValidation,
   handler: booksController.getBook
}, {
   method: 'POST',
   url: '/api/books',
   handler: booksController.addBook
}, {
   method: 'PUT',
   url: '/api/books/:id',
   handler: booksController.updateBook
}, {
   method: 'DELETE',
   url: '/api/books/:id',
   handler: booksController.deleteBook
}]

module.exports = routes

Updating the Main Server File

Update your index.js to include the book routes:

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

// Root route
app.get('/', function (req, reply) {
   reply.send({
      Welcome: 'TutorialsPoint Books API'
   })
})

// Register book routes
const bookRoutes = require('./routes/books')

bookRoutes.forEach((route) => {
   app.route(route)
})

// Add hook to log registered routes
app.addHook('onRoute', (routeOptions) => {
   console.log(`Registered route: ${routeOptions.method} ${routeOptions.url}`)
})

// Start server
app.listen({ port: 3000 }, (err, address) => {
   if (err) {
      app.log.error(err)
      process.exit(1)
   }
   app.log.info(`Server listening on ${address}`)
})

Testing the API

Start your server and test the endpoints:

node index.js

Test getting all books:

curl http://localhost:3000/api/books

Test getting a specific book:

curl http://localhost:3000/api/books/1

Schema Validation in Action

Our API includes built-in validation. If you try to access an invalid book ID, you'll receive a proper error response:

curl http://localhost:3000/api/books/invalid

This returns a validation error:

{
  "statusCode": 400,
  "error": "Bad Request", 
  "message": "params/id must match pattern "^[0-9]+$""
}

Using Hooks for Lifecycle Management

Fastify hooks allow you to execute code at specific points in the request lifecycle. In our example, we used the onRoute hook to log registered routes:

Registered route: GET /
Registered route: GET /api/books
Registered route: GET /api/books/:id
Registered route: POST /api/books
Registered route: PUT /api/books/:id
Registered route: DELETE /api/books/:id

Conclusion

We've successfully built a complete REST API using Fastify with proper route organization, controller separation, schema validation, and hooks. Fastify's plugin-based architecture and high performance make it an excellent choice for building scalable Node.js APIs.

Updated on: 2026-03-15T23:19:01+05:30

2K+ Views

Kickstart Your Career

Get certified by completing the course

Get Started
Advertisements