Article Categories
- All Categories
-
Data Structure
-
Networking
-
RDBMS
-
Operating System
-
Java
-
MS Excel
-
iOS
-
HTML
-
CSS
-
Android
-
Python
-
C Programming
-
C++
-
C#
-
MongoDB
-
MySQL
-
Javascript
-
PHP
-
Economics & Finance
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.
