Backend with Fastify - Part 2 (Creating REST APIs )

Search for a command to run...

No comments yet. Be the first to comment.
In this series, I will explore the concepts of fastify and see how we can build a production ready backend service with it.
Continuing from Part 2, our next step is to set up the database for our application. To follow along, you can clone this branch. The complete code for this part can be found here. To keep things straightforward for our purpose, we'll create two table...
Now that we have our database set up and seeded in part 4, it's crucial to grasp some key concepts of Fastify before diving into application development. You can find the complete code for this part here Fastify Concepts Lifecycle and Hooks Fastify o...

Introduction Recently, at work, I encountered the following scenario: A static website was uploaded to an S3 bucket with a filepath similar to the following: - index.html - page1 - index.html - page2 - index.html This S3 bucket file was ...

Continuing from part 3, we will explore how to seed data using Knex. To follow along, you can use the part-3 branch from this repo. The full code for this post is in the part-4 branch. Power of seeding Seed files allow us to populate the database wit...

Continuing from Part 2, our next step is to set up the database for our application. To follow along, you can clone this branch. The complete code for this part can be found here. To keep things straightforward for our purpose, we'll create two table...

In part 1, we have already set up our project. In this part, we will familiarize ourselves with the basic concepts of Fastify, which are essential for creating REST APIs.
If you wish to follow along, you can clone the Part I branch. For the complete code, you can refer to the Part 2 branch.
In this section, we will create some simple endpoints for saving and fetching movies, and along the way, we will explore some important Fastify concepts.
Let's create the following three endpoints:
GET /movies
POST /movies
GET /movies/:movieGenre
Inside the src folder, create a file app.ts and add the following code:
import { type FastifyInstance, type FastifyPluginOptions } from 'fastify'
interface FavMovies {
title: string
description: string
genre: string
}
/** Our simple database for movies **/
const favMovies: FavMovies[] = [];
export default async function (
fastify: FastifyInstance,
opts: FastifyPluginOptions,
): Promise<void> {
fastify.route({
url: '/movies',
method: 'GET',
handler: function myHandler(request, reply) {
reply.send({
message: 'Movies listed successfully',
success: true,
data: favMovies
})
},
})
}
The code above demonstrates the simplest way to create an API endpoint using Fastify. The route method accepts a JSON object as a parameter, where we define our request handler and the endpoint coordinates.
The handler function should be familiar if you've worked with frameworks like Express. The business logic can be included in the handler. The HTTP request is available in the request argument, and the response to the client can be handled using the reply object.
Before this endpoint can work, we need to start our application. To do that, add the following code to index.ts:
import Fastify from 'fastify'
import App from './app'
async function start(): Promise<void> {
const fastify = Fastify({
logger: true,
})
await fastify.register(App)
await fastify.listen({
host: '0.0.0.0',
port: 8081,
})
}
start().catch(err => {
console.error(err)
process.exit(1)
})
Fastify has a concept of plugins. In Fastify, everything is a plugin, including routes and middleware.
Once we get more comfortable with using fastify, we can understand what plugins in fastify actually mean and the value they provide.
The line await fastify.register(App) in the code above is creating a scope where all the logic written in app.ts is contained.
To start the application, you can use:
pnpm dev
And if you request the endpoint with curl:
curl http://0.0.0.0:8081/movies
You should receive a response with "Movies listed successfully," even though the list is currently empty.
Now, let's create a POST endpoint.
In app.ts, add the following code to our previous function.
import Sensible from '@fastify/sensible'
// Favmovies interface and variable
export default async function(fastify: FastifyInstance, opts: FastifyPluginOptions){
await fastify.register(Sensible)
// GET /movies endpoint
fastify.route({
url: '/movies',
method: 'POST',
handler: function handler(request, reply) {
const data = request.body as FavMovies
if (!data?.title || !data?.description || !data.genre) {
throw fastify.httpErrors.badRequest(
'Please ensure all information, title, description and genre are provided',
)
}
favMovies.push({
title: data.title,
description: data.description,
genre: data.genre,
})
reply.send({
message: 'Movie added succesfully',
success: true,
data: null,
})
},
})
}
Now, let's go through the code above:
We've added a new endpoint using the route method. In this example, we perform simple validation and throw an error when it fails.
To make it easier to throw an error, we use a Fastify plugin called Sensible, which we installed in Part I. Sensible is a community Fastify plugin that provides some helpful utilities. This demonstrates how easily you can add community plugins to extend the features of your Fastify application.
You can test the POST request with curl:
curl --location 'http://0.0.0.0:8081/movies' \
--header 'Content-Type: application/json' \
--data '{
"title": "Pulp fiction",
"description": "A must watch movie!",
"genre": "action"
}'
Fastify provides a shorthand method for creating API routes. In app.ts, add the following code to the default function:
fastify.get('/movies/:movieGenre', function getMovie(request, reply) {
const requestParams = request.params as { movieGenre: string }
const searchingFor = requestParams.movieGenre
const result = favMovies.filter(movie => movie.genre === searchingFor)
if (result) {
return {
message: 'Movie info found succesfully',
success: true,
data: result,
}
} else {
throw fastify.httpErrors.notFound(
`Could not find movies with the genre: ${searchingFor}`,
)
}
})
The above code demonstrates an alternative way to write routes. It also shows how the request object is used to get request parameters. In this case, we are not using the reply object but simply returning the result from the function, which works the same as using the reply.send method.
In this part, we’ve explored the fundamental techniques for creating API endpoints in Fastify. Now that we have a solid grasp of how to build basic APIs with Fastify, our journey continues in the next installment. In the upcoming section, we will shift our focus towards the crucial aspect of setting up and integrating a database with our Fastify application, taking our development to the next level.