Backend with Fastify - Part 2 (Creating REST APIs )

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:

  1. GET /movies

  2. POST /movies

  3. GET /movies/:movieGenre

GET Request

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.

POST Request

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"
}'

Shorthand method

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.