Build a CRUD API with Fastify
Tags: javascript, codenewbie, programming, productivity
Published: 2021-07-22 19:18:29

Hello everyone, in this article we are going to build a NodeJS CRUD API with [Fastify](https://fastify.com). Fastify is a NodeJS framework for building fast NodeJS servers. With this wonderful tool, you can create a server with NodeJS, create routes (endpoints), handle requests to each endpoint, and lots more.

Fastify is an alternative of Express (L express) which you must have heard of if you are familiar with NodeJS before. In fact, Fastify draws its inspiration from Express only that Fastify servers are way faster compared to Express servers.

I have tested it, and I can testify of its speed. I am currently building a Mobile Application, and in this app, I use Fastify for my API.

So in this article, we will be building a basic NodeJS server with Fastify. This server will have endpoints to Create data, Read data, Update data, and Delete data (CRUD). We will also be doing some authentication using jwt (next article), just to introduce you to the Fastify plugin ecosystem and how cool it is.

**Prerequisites**
What are the things you need to know before getting started with Fastify:

- **JavaScript**: You should know a good amount of JavaScript especially es5 and es6. [CodeCademy](https://www.pntra.com/t/TUJGR0lLR0JHSUtNTkhCR0ZISk1N) has great courses that would guide you
- **NodeJS**: You should also be familiar with NodeJS. You can also find NodeJS courses on [Codecademy.](https://www.pntra.com/t/TUJGR0lLR0JHSUtNTkhCR0ZISk1N)
- **Express**: This is totally optional, but if you already know express then you will learn Fastify at a faster paste.

So enough with the introduction, let's jump right into the code.

See the complete [code on Github](https://github.com/Elijah-trillionz/fastify-api)

## Getting Familiar with Fastify

### Setting up the App

Just as we would create a server with Express and use a simple endpoint to test if it's running, I am going to show you how we would do this with Fastify. We will initialize the server, register a port and listen for events via the port.

Let's initialize a package.json file. You can do that with `npm init -y` in a terminal, this will create a file called package.json with some information about your app in JSON.

Now let's install Fastify using NPM. You can use yarn also. Use the `npm install Fastify` command to install Fastify. Other packages we will be installing are

- nodemon: for automatically restarting the server whenever we make any changes. We will install this package as a dev dependency. Using NPM is `npm install -D nodemon`.
- config: for storing secrets. Useful when you want to publish to GitHub. Installing it would be `npm install config`

Other packages will be introduced and installed when needed. Let's move on to setting up our package.json.

Go to your package.json and change the value of `main` to `server.js`, because the file we will create our server in is going to be called server.js. Furthermore, delete the `test` property and value. Paste the following code inside the `script` property.

```json
  "start": "node server.js",
  "server": "nodemon server.js"
```

This simply means when we run the command `npm start` on the terminal, it will run our _server.js_ file which will be created soon. But when we run the command `npm run server` on the terminal, it will run our _server.js_ file using nodemon.

Now create a _server.js_ file and get ready to create your first NodeJS server using Fastify.

### Creating our Server

We go into our _server.js_ file and import Fastify. I.e

```js
const fastify = require('fastify')({ logger: true });
```

The `logger: true;` key value is an option for activating logging on our terminal from Fastify. So the information of requests, server starting, response, errors will all be logged in the terminal.

The next thing we would do is to assign a port to a `PORT` variable, I will use 5000 for mine. Why we create a variable for it is for the sake of deploying to production. So you should have something like `const PORT = process.env.PORT || 5000`. As such we are either using the port of the host company (like Heroku or digital ocean) or our customized 5000.

Now let's create a simple route for get requests to `/`.

```js
fastify.get('/', (req, reply) => {
  reply.send('Hello World!');
});
```

Isn't that familiar? Looks just like Express right? Yeah, so working with Fastify is going to be so easy for those already familiar with Express, its syntax is alike.

`req` and `reply` stands for request and reply (response). They are parameters obviously, so you can call it whatever you like. But we would go with this simple and readable form.

Ok now let's get our server running by listening for events. We use `fastify.listen(port)` to listen for requests to our server. But this function returns a promise, so we would create a function that handles this promise using async and await.

```js
const startServer = async () => {
  try {
    await fastify.listen(PORT);
  } catch (err) {
    fastify.log.error(err);
    process.exit(1);
  }
};
```

You wanna make sure you log the error and exit the server if an error occurs. Now we can just call `startServer()` and run `npm run server` on the terminal to get the server started.

![Fastify api](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nqudkwx42uw6yfv8zqur.png)

You should see your URL address in the log info in your terminal like in the image above or simply use `http://localhost:5000`. Use any API testing tool of your choice to test and you should get a **Hello world** message as a response.

### Creating more Routes

Now you wouldn't want all your routes to be in _server.js_ file, so we will create a folder called **routes.** This we will use to handle and organize all the different routes of our API.

This API is going to be for a blog, so our data will basically be about posts, and admins making these posts. So in the **routes** folder, create a _ posts.js_, and _admins.js_ file.

To make these files work as endpoints on our server, we need to register them as a plugin. Now don't panic, it's easier than you think. Add the code below to _server.js_ just before the `startServer` function.

```js
fastify.register(require('./routes/posts')); // we will be working with posts.js only for now
```

That will register the post routes. You could first import and assign it to a variable and then pass the variable as a parameter in the `register` function, choice is yours.

If you save, it's going to generate an error, now this is because we haven't created any route in _posts.js_ yet.

In _posts.js_, create a function called `postRoutes` and pass these three parameters **fastify**, **options**, and **done**. This function is going to make an instance of our fastify server, which means with the first parameter we can do everything we could do in _server.js_ with the `fastify` variable.

Now you can cut the get request from _server.js_ into the `postRoutes` function in _posts.js._

Your `postRoutes` should look like this:

```js
const postRoutes = (fastify, options, done) => {
  fastify.get('/', (req, reply) => {
    reply.send('Hello world');
  });
};
```

The **options** (sometimes written as **opts**) parameter is for options on the routes, we will not be using this.

The **done** parameter is a function we would call at the end of the `postRoutes` function, to indicate we are done. Just like making a middleware in Express and calling next to move on.

So you should have `done()` at the last line of the `postRoutes` function.

Now, let's export the function and save our file. Use the following command at the last line of the _posts.js_ file to export: `module.exports = postRoutes`.

Save your file and test your route.

### Organizing Routes

We could create more routes just like the one above and call it a day, but then we will hinder ourselves from some of the great features of Fastify. With Fastify we can better organize our API by separating concerns.

With Fastify we can create schemas for requests coming to a route and responses going out. For requests, we can tell Fastify what to expect from the body of the request, or the headers, or params, etc.

We can also tell Fastify what we intend to send as a response e.g the data that will be sent on a 200 response, or 400 response or 500 response, etc.

For example, let's make a schema for our get request above. In our get request we sent **Hello world** (a string) as a response, now we will be sending an array of posts like this

```js
fastify.get('/', (req, reply) => {
  reply.send([
    { id: 1, title: 'Post One', body: 'This is post one' },
    { id: 2, title: 'Post Two', body: 'This is post two' },
    { id: 3, title: 'Post Three', body: 'This is post three' },
  ]);
});
```

Let's make a schema for it. A schema in Fastify is an object, this object will be passed as a value for a `schema` property.

```js
const opts = {
  schema: {},
};

const postRoutes = (fastify, options, done) => {
  fastify.get('/', opts);

  done();
};
```

This is the way we will be defining our routes, the get method (could be post or any method) will take two parameters, the first being the route and the last is an object of options.

The three properties of the object of options we will be using in this API are

- `schema`: defines how our data should be set up, what data should come in, and what data should go out, including their types (string, boolean, number, etc).

- `preHandler`: a function that defines what should be done before requests are handled by the `handler` below.

- `handler`: a function that handles the request.

It may not be clear to you now, but when we make examples you will get it straight up. The `preHandler` is going to be used for authentication, which means it will be used on protected routes only.

Enough with the explanation, if you want more explanation check out the [docs](www.fastify.io). Let's dive into codes.

Our get request is about to look way better.

```js
const opts = {
  schema: {
    response: {
      200: {
        type: 'array',
        items: {
          type: 'object',
          properties: {
            id: { type: 'number' },
            title: { type: 'string' },
            body: { type: 'string' },
          },
        },
      },
    },
  },
  handler: (req, reply) => {
    reply.send([
      { id: 1, title: 'Post One', body: 'This is post one' },
      { id: 2, title: 'Post Two', body: 'This is post two' },
      { id: 3, title: 'Post Three', body: 'This is post three' },
    ]);
  },
};
```

While it has now become better, I presume it is now more confusing. Well, it's simple, let's analyze the schema object.

#### schema

In the schema object, we are telling Fastify that on a 200 response what we will send is an array. And each item in this array is an object and the properties of these objects are `id`, `title`, and `body` which are of type `number`, `string`, and `string` respectively.

Simple right. You should take note of the names of the properties used i.e `response`, `200`, `type`. The `items` and `properties` can be any name but I recommend using these names.

If you try removing the `id` property and value from the schema object you would notice the `id` property is no longer sent as part of the response. While if you try changing the `id` property from type `number` to type `string`, you would see it as a string in the response. Cool right!

#### handler

The handler function is clear enough, we simply copied what we had in our get request.

The _opts_ object is specific to a route. Unless you want to handle different requests on different routes with one response. If that's not the case, then you should make sure the name of the object is unique.

For example in our get request, since we are getting posts we could change the name to `getPostsOpts`.

Our _posts.js_ should now look like this

```js
const getPostsOpts = {
  schema: {
    response: {
      200: {
        type: 'array',
        items: {
          type: 'object',
          properties: {
            id: { type: 'number' },
            title: { type: 'string' },
            body: { type: 'string' },
          },
        },
      },
    },
  },
  handler: (req, reply) => {
    reply.send([
      { id: 1, title: 'Post One', body: 'This is post one' },
      { id: 2, title: 'Post Two', body: 'This is post two' },
      { id: 3, title: 'Post Three', body: 'This is post three' },
    ]);
  },
};

const postRoutes = (fastify, options, done) => {
  fastify.get('/', getPostsOpts);

  done();
};
```

Now imagine, having 10 routes with different schemas and handlers and maybe some preHandlers. You can tell the code is going to be very bucky and scary to read. This is where controllers come in.

Controllers is not some kind of plugin or package as it sounds. It's just a folder we will create to separate our routes from our schemas and handlers.

Inside of our **controllers** folder, I am going to create two other folders called schemas and handlers. It makes it look cleaner and easy to read.

In our **schemas** folder we will create a file called _posts.js._ This file will contain all the schemas for our post routes (getting all posts, creating a post, deleting a post, etc.).

In _schemas/posts.js_, create an object called `getPostsSchema` and cut the value of the `schema` property (from _routes/posts.js_) and paste it as the object. Your code should look like this

```js
const getPostsSchema = {
  response: {
    200: {
      type: 'array',
      items: {
        type: 'object',
        properties: {
          id: { type: 'number' },
          title: { type: 'string' },
          body: { type: 'string' },
        },
      },
    },
  },
};
```

Now let's export it;

```js
const getPostsSchema = {
  // our schemas
};

module.exports = { getPostsSchema };
```

We will import it in our _routes/posts.js_ file so we can use it as the value of the `schema` property.

```js
const { getPostsSchema } = require('../controllers/schemas/posts.js');

const getPostsOpts = {
  schema: getPostsSchema,
  handler: (req, reply) => {
    reply.send([
      { id: 1, title: 'Post One', body: 'This is post one' },
      { id: 2, title: 'Post Two', body: 'This is post two' },
      { id: 3, title: 'Post Three', body: 'This is post three' },
    ]);
  },
};
```

In our **handlers** folder, let's create a file called _posts.js._ This file will contain all our handler functions for our post routes (getting all posts, creating a post, deleting a post, etc.).

In _handlers/posts.js_, create a function called `getPostsHandler` with `req` and `reply` as our params. Copy the function body from the _routes/posts.js_ file and paste it here, after which export the function. It should look like this

```js
const getPostsHandler = (req, reply) => {
  reply.send([
    { id: 1, title: 'Post One', body: 'This is post one' },
    { id: 2, title: 'Post Two', body: 'This is post two' },
    { id: 3, title: 'Post Three', body: 'This is post three' },
  ]);
};

module.exports = { getPostsHandler };
```

Import the `getPostsHandler` into the _routes/posts.js_ file, and set it as the value of the handler method. Your _routes/posts.js_ would look like this

```js
const { getPostsSchema } = require('../controllers/schemas/posts.js');
const { getPostsHandler } = require('../controllers/handlers/posts.js');

const getPostsOpts = {
  schema: getPostsSchema,
  handler: getPostsHandler,
};

const postRoutes = (fastify, opts, done) => {
  fastify.get('/', getPostsOpts);

  done();
};
```

This looks cleaner right? Now save the file and test it, it should work fine as before.

I would have loved to talk about organizing authentication here, but it would make this article too long, so I will make another article on authentication.

Alright Elijah, can we just build the CRUD API already? Yeah sure!

## Building your first CRUD API with Fastify

We will create a blog API where we can create a post, read all posts, read a post, delete a post, and update a post. We will also be able to create admins, log admins in, and make protected routes. But we will do this in another article.

### Get all Posts

Since we already have a working get request, I will simply make some changes to the routes and the array of posts.

**In _routes/posts.js_.**

```js
fastify.get('/api/posts', getPostsOpts);
```

That should make the route look more like an API endpoint.

Let's create a folder in the root directory called **cloud** and create a _posts.js file._ This file will act as our database because we will be storing all our posts there. Paste the code below in it:

```js
const posts = [
  { id: 1, title: 'Post One', body: 'This is post one' },
  { id: 2, title: 'Post Two', body: 'This is post two' },
  { id: 3, title: 'Post Three', body: 'This is post three' }, // you can add as many as you want
];

module.exports = posts;
```

In _handlers/posts.js_, import posts and replace it with the array in the `send` function i.e

**In _handlers/posts.js_.**

```js
const posts = require('../../cloud/posts.js');

const getPostsHandler = (req, reply) => {
  reply.send(posts);
};

module.exports = { getPostsHandler };
```

Save the file and run the program, recall the routes have changed. To get all posts use `http://localhost:your_port/api/posts`

**Note:** There are four files called _posts.js._

- _cloud/posts.js:_ where the array of posts is stored (our database).
- _routes/posts.js:_ where we handle all routes of our blog posts.
- _handlers/posts.js:_ where we handle responses to our post routes.
- _schemas/posts.js:_ where we specify the schemas of our post routes.

I will make reference to each one of them with their folder so you can easily know who is who.

### Get a Post

The next route we would make is to get a post, we will do this with its id. So we get an `id` as a parameter from the request and we will filter the `posts` array to find that post.

#### Create the Route in _routes/posts.js_

In _routes/posts.js,_ just below our first route, paste the code below

```js
fastify.get('/api/posts/:id', getPostOpts); // the :id route is a placeholder for an id (indicates a parameter)
```

Let's create the `getPostOpts` object

```js
const getPostOpts = {
  schema: getPostSchema, // will be created in schemas/posts.js
  handler: getPostHandler, // will be created in handlers/posts.js
};
```

#### Create the Schema in _schemas/posts.js_

Create an object called `getPostSchema` and paste the following

```js
const getPostSchema = {
  params: {
    id: { type: 'number' },
  },
  response: {
    200: {
      type: 'object',
      properties: {
        id: { type: 'number' },
        title: { type: 'string' },
        body: { type: 'string' },
      },
    },
  },
};
```

The params property indicates what data should be collected in the params of the route. I am using it to format the id to a number. By default is a string. Since the ids in our posts array are a number, I simply want them to be of the same type.

Then, since we are fetching just one post, it means our response is going to be an object with id, title, and body as its properties. Export the `getPostSchema`, simply add it to the object being exported i.e `module.exports = { getPostsSchema, getPostSchema };`

Now take a good look at your _routes/posts.js,_ you would observe you have repeated yourself. So refactor it to make sure you are not repeating yourself, this is what I did

```js
const typeString = { type: 'string' }; // since i will be using this type a lot

const post = {
  type: 'object',
  properties: {
    id: { type: 'number' },
    title: typeString,
    body: typeString,
  },
};

const getPostsSchema = {
  response: {
    200: {
      type: 'array',
      items: post,
    },
  },
};

const getPostSchema = {
  params: {
    id: { type: 'number' },
  },
  response: {
    200: post,
  },
};

module.exports = { getPostsSchema, getPostSchema };
```

#### Create the Handler in _handlers/posts.js_

In _handlers/posts.js,_ create an object called `getPostHandler` and paste the following

```js
const getPostHandler = (req, reply) => {
  const { id } = req.params;

  const post = posts.filter((post) => {
    return post.id === id;
  })[0];

  if (!post) {
    return reply.status(404).send({
      errorMsg: 'Post not found',
    });
  }

  return reply.send(post);
};
```

The first line of the function body is how we fetch the id from the request route. So a route like `http://localhost:5000/api/posts/4` will return 4 as its id.

The `reply.status` function tells Fastify what status code the response should be. If the post is not found a customized error message is sent, with Fastify we could also use

```js
return reply.status(404).send(new Error('Post not found'));
```

So when a post is not found Fastify will send the JSON below as a response

```json
{
  "statusCode": 404,
  "error": "Not Found",
  "message": "Post not found"
}
```

Now export `getPostHandler` and save all files. Run the program and test your new route.

### Creating new Posts

#### Create the Route in _routes/posts.js_

First, let's create the route in the `postRoutes` function. Just after the last route we created, paste the code below

```js
fastify.post('/api/posts/new', addPostOpts);
```

`/api/posts/new` is our endpoint to add a new post to our array of posts. The next thing we'd do is create the `addPostOpts` object outside of our routes function and pass a value for schema and handler

```js
const addPostOpts = {
  schema: addPostSchema, // will be created in schemas/posts.js
  handler: addPostHandler, // will be created in handlers/posts.js
};
```

In my next article, I will make this route a private route, which means we will add a `preHandler` to the object above in the next article.

#### Create the Schema in _schemas/posts.js_

We will tell Fastify what data should come in from our request body and what data we will be sending out as a response.

Create an object called `addPostSchema`, assign the code below to it;

```js
const addPostSchema = {
  body: {
    type: 'object',
    required: ['title', 'body']
    properties: {
      title: typeString, // recall we created typeString earlier
      body: typeString,
    },
  },
  response: {
    200: typeString, // sending a simple message as string
  },
};
```

We use `body` as a property to tell Fastify what to expect from the request body of our post route. Just like we did with `params` above. We can also do the same for `headers` (I will show you this during authentication).

With the `required` property we are telling Fastify to return an error if both `title` and `body` are not part of the request body.

Fastify will return a **400 Bad Request** error as a response if a required field is not provided.

Add `addPostSchema` to the object being exported out of this file (_schemas/posts.js_).

#### Create the Handler in _handlers/posts.js_

We will create an id for the data that is sent to us and append it to our array of posts. Simple right!

```js
const addPostHandler = (req, reply) => {
  const { title, body } = req.body; // no body parser required for this to work

  const id = posts.length + 1; // posts is imported from cloud/posts.js
  posts.push({ id, title, body });

  reply.send('Post added');
};
```

Add `addPostHandler` to the object being exported out of this file (_handlers/posts.js_).

Before saving your files and running your program, make sure to add `addPostSchema` and `addPostHandler` to the object being imported into _routes/posts.js_.

To verify your post has been created you can run `http://localhost:your_port/api/posts` (our first endpoint), you would see it at the bottom of the array.

### Updating a Post

#### Create the Route in _routes/posts.js_

We will use the `put` method for this route. Add the code below to your `postRoutes` function

```js
fastify.put('/api/posts/edit/:id', updatePostOpts);
```

Next thing is to create the `updatePostOpts` object outside of the `postRoutes` function. As before, we will pass a value for the `schema` and `handler` properties i.e

```js
const updatePostOpts = {
  schema: updatePostSchema, // will be created in schemas/posts.js
  handler: updatePostHandler, // will be created in handlers/posts.js
};
```

Before moving to the other files quickly add `updatePostSchema` and `updatePostHandler` to the imported objects in this file (_routes/posts.js_).

### Create the Schema in _schemas/posts.js_

Create an object called `updatePostSchema` and use this code for it

```js
const updatePostSchema = {
  body: {
    type: 'object',
    required: ['title', 'body'],
    properties: {
      title: typeString,
      body: typeString,
    },
  },
  params: {
    id: { type: 'number' }, // converts the id param to number
  },
  response: {
    200: typeString, // a simple message will be sent
  },
};
```

Don't forget to add the `updatePostSchema` to the object being exported out.

#### Create the Handler in _handlers/posts.js_

```js
const updatePostHandler = (req, reply) => {
  const { title, body } = req.body;
  const { id } = req.params;

  const post = posts.filter((post) => {
    return post.id === id;
  })[0];

  if (!post) {
    return reply.status(404).send(new Error("Post doesn't exist"));
  }

  post.title = title;
  post.body = body;

  return reply.send('Post updated');
};
```

Don't forget to add the `updatePostHandler` to the object being exported out.

Now you can save your files and test your new route.

### Deleting a Post

#### Create the Route in _routes/posts.js_

We will follow the same procedure we have been following in the previous routes, we will only change the route and method.

```js
fastify.delete('/api/posts/:id', deletePostOpts);
```

The `deletePostOpts` object would be

```js
const deletePostOpts = {
  schema: deletePostSchema,
  handler: deletePostHandler,
};
```

#### Create the Schema in _schemas/posts.js_

You should note that creating schemas is completely optional, for a route like this, you may not have to create a schema.

```js
const deletePostSchema = {
  params: {
    id: { type: 'number' }, // converts the id param to number
  },
  response: {
    200: typeString,
  },
};
```

#### Create the Handler in _handlers/posts.js_

```js
const deletePostHandler = (req, reply) => {
  const { id } = req.params;

  const postIndex = posts.findIndex((post) => {
    return post.id === id;
  });

  if (postIndex === -1) {
    return reply.status(404).send(new Error("Post doesn't exist"));
  }

  posts.splice(postIndex, 1);

  return reply.send('Post deleted');
};
```

Export your handler and schema, and import them in _routes/posts.js_ accordingly. Save your files and test your new route.

## Final Words

These are my final words for this article, not for Fastify. We are yet to add admins' routes that will involve authentication. We will do that next, so you wanna make sure you get the notification when that is out.

With that being said, I want to congratulate you on building your first CRUD API with Fastify. In this project, we created routes for Creating data, Reading data, Updating data, and Deleting data. We also explained a tiny bit of Fastify. So great job.

If you find this article useful please do like and share. You can also support me with a [cup of coffee](https://buymeacoffee.com/elijahtrillionz). Thanks for reading and happy hacking.