Fastify CRUD API with Authentication
Tags: javascript, programming, codenewbie, productivity
Published: 2021-08-05 10:30:14

Hi there! Today's article is a continuation of my previous article on Fastify.

We will improve on our [CRUD API](https://dev.to/elijahtrillionz/build-a-crud-api-with-fastify-688) in this article. You should check out the previous article else I don't think you'll be able to follow along.

Or you can just clone [this repo](https://github.com/Elijah-trillionz/fastify-api), and follow along.

What are we introducing? Today's article is going to focus on authentication. How would we easily protect routes in Fastify? [Fastify's documentation](https://www.fastify.io/docs/latest/) is very detailed, I recommend you go through the docs after reading this article.

Quickly let's go into our app and get things started. If you cloned the API's repo, you wanna make sure you run `npm install` to install all dependencies and then test all endpoints in the _test.http_ file to ensure it's working.

In the previous article, I didn't talk about installing **fastify-auth**. But in the API's repo, **fastify-auth** is a dependency along with several others like **jsonwebtoken**, **fastify-swagger** (which we will get to in a second). So if you haven't installed the above dependencies you should do so now because we will be using them in this article.

**Let's get started**

## Creating and Registering the Admins Routes

The first thing we want to do before protecting routes is to have a form of registering and logging in admins to the app (when connected to the front-end of course). We will have a route for registering admins, and for logging them in.

We are not connecting this API to a database, so like we did with the `posts` array in _cloud/posts.js_, that is how we would do the same for the admins.

We will have a simple array of admins in a file, have it exported and used whenever and however we want. The array can be empty initially or you can add placeholders. I will leave mine empty.

When a user creates an account, his/her details are appended to the array. As soon as he/she logs in a token will be generated for him/her. It is with this token he/she can access protected routes like that of deleting posts.

Simple right!

Alright then, let's start by creating our database array. In the **cloud** folder, create a file called _admins.js_, and add the following

```js
const admins = [
  {
    id: 1,
    username: 'johndoe_360',
    email: 'johndoe@gmail.com_',
    password: '341',
  },
  {
    id: 2,
    username: 'sarahjohnson',
    email: 'sarah@websitename.com',
    password: 'sarahCodes',
  },
];

module.exports = admins;
```

In the **routes** folder, create a file called _admins.js_ and create a function called `adminRoute`. This function is our route plugin that we will register in _server.js_ in a minute. This function usually takes three parameters i.e _fastify_, _options_, and _done_. Find more explanation on this in the previous article or in Fastify's docs.

We will create all of our admins' routes in this function. And just before the closure of the function, we wanna make sure we call `done()` to signify that we are done.

```js
const adminRoutes = (fastify, options, done) => {
  // all our routes will appear here

  done();
};

module.exports = adminRoutes;
```

Before we start creating routes, let's register this `adminRoutes` in _server.js._ Just after the `fastify.register(require('./routes/posts'));` add this

```js
fastify.register(require('./routes/admins'));
```

That should register your route and get you going, you can test it's working with a simple route e.g

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

Add the code above inside the `adminRoutes` function in _routes/admins.js_ and test your enpoint.

## Get all Admins

This may not be useful in a real-time app, but just in case you want to get all admins, we can use the `GET` method to do that.

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

In place of our test route we made in `adminRoutes` function, we should add this

```js
fastify.get('/api/admins', getAdminsOpts);
```

Now let's create the `getAdminsOpts` object. This object as always should go outside of the `adminRoutes` function. Create and add the following

```js
const getAdminsOpts = {
  schema: getAdminsSchema,
  handler: getAdminsHandler,
};
```

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

This file has not been created yet, so we will create it now. In **controllers/schemas** folder, create a file called a _admins.js_. In this file create an object called `getAdminsOpts`.

With this schema, we want to filter out what to send to the client from our array of admins. For example, you wouldn't want to send the password of each admin to the client. So this is the easy way to do it

```js
const getAdminsSchema = {
  response: {
    200: {
      type: 'array',
      items: {
        type: 'object',
        properties: {
          id: { type: 'number' },
          username: typeString, // typeString will be created soon
          email: typeString,
        },
      },
    },
  },
};
```

At the beginning of this file, create a variable called `typeString` and assign `{ type: 'string' }` to it.

Now let's export `getAdminsSchema` out of the _schemas/admins.js_ file.

```js
module.exports = { getAdminsSchema };
```

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

This file has not been created yet, so let's do that now. In **controllers/handlers** folder, create a file called _admins.js_. This file will have all the handler functions of our admin routes.

Let's create our first handler, which will return all the admins we have, then we will export it.

```js
const admins = require('../../cloud/admins'); // import the admins array

const getAdminsHandler = (req, reply) => {
  reply.send(admins);
};

module.exports = { getAdminsHandler };
```

Import `getAdminsHandler` and `getAdminsSchema` into your _routes/admins.js_ as objects.

Now save your files and test your new route.

There is a route I am going to skip, that is getting an admin, if this API was for production, I definitely would have made it. But it's not so we wouldn't need it.

## Register an Admin

Let's create accounts for our new admins. So far I believe you have grasped a lot about creating routes with Fastify, so I am going to speed up the process a little bit.

I will just show you what your schema should look like and what your handler should do.

**Schema**

```js
const registerAdminSchema = {
  body: {
    type: 'object',
    required: ['username', 'email', 'password'],
    properties: {
      username: typeString,
      email: typeString,
      password: typeString,
    },
  },
  response: {
    200: typeString,
  },
};
```

**Handler**

```js
const registerAdminHandler = (req, reply) => {
  const { username, email, password } = req.body;
  const id = admins.length + 1;

  admins.push({
    id,
    username,
    email,
    password, // you can hash the password if you want
  });

  reply.send('Account created successfully');
};
```

## Log in Admin

When we log in an admin, we would send a token to the client. This token will be generated using JsonWebToken (JWT). It is only with this access token the user can access protected (private) routes.

There are currently no protected routes, but we would make some posts' routes private in a moment.

First, let's see what the schema for this route should look like and how the handler should function

**Schema**

```js
const loginAdminSchema = {
  body: {
    type: 'object',
    required: ['username', 'password'],
    properties: {
      username: typeString,
      password: typeString,
    },
  },
  response: {
    200: {
      type: 'object',
      properties: {
        token: typeString,
      },
    },
  },
};
```

**Handler**

```js
// at the beginning of the file, import jwt and assign to a variable
const jwt = require('jsonwebtoken');

const loginAdminHandler = (req, reply) => {
  const { username, password } = req.body;

  const admin = admins.filter((admin) => {
    return admin.username === username;
  })[0];

  if (!admin) {
    return reply.send("This admin doesn't exist");
  }

  // check if password is correct
  if (password !== admin.password) {
    return reply.send('Invalid credentials');
  }

  // sign a token
  jwt.sign(
    { id: admin.id },
    'my_jwt_secret',
    { expiresIn: 3 * 86400 },
    (err, token) => {
      if (err) reply.status(500).send(new Error(err));

      reply.send({ token });
    }
  );
};
```

The first thing we did was bring in jwt, you should install it first if you haven't. Use `npm i jsonwebtoken` to install it.
Then we did some verification to make sure the user exists and the given password is the correct password.
Lastly, we signed a token that will expire in three days with a payload of just the admin's id, you can add username and scope if you want.

If we had used it with a database and then had to get the data asynchronously using `async/await` we would have run into some Fastify errors. This is what I mean

```js
const loginAdminHandler = async (req, reply) => {
  const { username, password } = req.body;

  try {
    const admin = await Admins.findOne({ username }); // assumming we used mongodb

    if (!admin) {
      return reply.send("This admin doesn't exist");
    }

    // check if password is correct
    if (password !== admin.password) {
      return reply.send('Invalid credentials');
    }

    // sign a token
    jwt.sign(
      { id: admin.id },
      'my_jwt_secret',
      { expiresIn: 3 * 86400 },
      (err, token) => {
        if (err) throw err;

        reply.send({ token });
      }
    );
  } catch (err) {
    console.log(err);
    reply.status(500).send('Server error');
  }
};
```

You should note that the token we are signing is being done asynchronously. So that means our `reply.send` is inside an async function that is inside another async function. This can confuse Fastify to give you an error like this:

![Fastify asyncawait error](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/s6b2t7khd2rdli0s5iov.png)

Solving this is simple. We just need to tell Fastify to wait for a reply in an async function. We usually do this in the root async function i.e down the try block add `await reply`. That will solve the problem.

So you would have something like this

```js
const loginAdminHandler = async (req, reply) => {
  const { username, password } = req.body;

  try {
    const admin = await Admins.findOne({ username }); // assumming we used mongodb

    if (!admin) {
      return reply.send("This admin doesn't exist");
    }

    // check if password is correct
    if (password !== admin.password) {
      return reply.send('Invalid credentials');
    }

    // sign a token
    jwt.sign(
      { id: admin.id },
      'my_jwt_secret',
      { expiresIn: 3 * 86400 },
      (err, token) => {
        if (err) throw err;

        reply.send({ token });
      }
    );

    await reply;
  } catch (err) {
    console.log(err);
    reply.status(500).send('Server error');
  }
};
```

## Making Private Routes

This is the simple part. Some of our routes are going to be restricted to a specific type of users. These routes are from the last article I made. Go check it out.

**Private Routes**
_/api/posts/new_ : adding a post
_/api/posts/edit/:id_ : updating a post
_/api/posts/:id_ : deleting a post

These are the routes we will make private.

What makes a route private is the authentication that restricts unauthorized users from gaining access. If access is granted, these users can perform any action within the routes with the same authentication.

This means that we would need to authenticate the user for each of our private routes and this authentication needs to be done before any action can be performed.

With Fastify this is easy, Fastify has a plugin for easier authentication, and this authentication will be done in the preHandler function (in our routes opts).

With the fastify-auth plugin, we will tell fastify that whoever doesn't have a token should be rejected.

To do that first thing we'd do is register the `fastify-auth` plugin using any Fastify instance.

```js
// in routes/posts.js, at the bottom of the postRoutes function add this
fastify
  .register(require('fastify-auth'))
  .after(() => privatePostRoutes(fastify)); // we will create the privatePostRoutes later
```

The next thing is to create an authentication function. This function is what Fastify will use to validate (authenticate) the user. In our app, we need the user to have a valid token. This token would come from the request header

```js
// create this function in an auth folder in controllers and export it
const verifyToken = (req, reply, done) => {
  const { token } = req.headers;

  jwt.verify(token, 'my_jwt_secret', (err, decoded) => {
    if (err) {
      done(new Error('Unauthorized'));
    }

    req.user = {
      id: decoded.id, // pass in the user's info
    };
  });

  done();
};
```

Because we passed an error to done, Fastify will not give that user any access.

You would notice we didn't check if there is a token before verifying the token. This is because in our route's schema we restrict whoever doesn't have a token as part of the request.

Now let's apply the `verifyToken` function to a route. You should do this in the `preHandler` property in routes opts. This is the function Fastify will run first before running the `handler`.

Import the `verifyToken` into our _routes/posts.js_ file. Create a function outside of the `postRoutes` function called `privatePostRoutes`, pass fastify as the only parameter. Now cut and paste all our private routes from `postRoutes` to `privatePostRoutes`. You should have something like this:

```js
const postRoutes = (fastify, opts, done) => {
  // get all posts
  fastify.get('/api/posts', getPostsOpts);

  // get a post
  fastify.get('/api/posts/:id', getPostOpts);

  fastify
    .register(require('fastify-auth'))
    .after(() => privatePostRoutes(fastify));

  done();
};

const privatePostRoutes = (fastify) => {
  // create a new post
  fastify.post('/api/posts/new', addPostOpts);

  // update a post
  fastify.put('/api/posts/edit/:id', updatePostOpts);

  // delete a post
  fastify.delete('/api/posts/:id', deletePostOpts);
};
```

Finally, let's add the preHandlers to our private route opts. Each of our preHandlers will contain a function from `fastify-auth` that checks if the user is authenticated using our `verifyToken` function.

```js
const privatePostRoutes = (fastify) => {
  // create a new post
  fastify.post('/api/posts/new', {
    preHandler: fastify.auth([verifyToken]),
    ...addPostOpts,
  });

  // update a post
  fastify.put('/api/posts/edit/:id', {
    preHandler: fastify.auth([verifyToken]),
    ...updatePostOpts,
  });

  // delete a post
  fastify.delete('/api/posts/:id', {
    preHandler: fastify.auth([verifyToken]),
    ...deletePostOpts,
  });
};
```

When a user is unauthorized, Fastify will return a 401 error with our customized message. If you are going to use the same auth function for more than one route, instead of importing it into each of the routes files, you can make the auth function available to every file in the API using `fastify.decorate`. In `server.js` import `verifyToken` and add this before your routes registering

```js
fastify.decorate('verifyToken', verifyToken); // the string can be any name you like
```

Now we can remove the `verifyToken` function we created in _routes/posts.js_ and add this to our preHandlers

```js
const privatePostRoutes = (fastify) => {
  // create a new post
  fastify.post('/api/posts/new', {
    preHandler: fastify.auth([fastify.verifyToken]),
    ...addPostOpts,
  });

  // same thing goes for the other routes
};
```

Something we should add is the headers schema that will return an error whenever there is no token provided as part of the request's header.

In _schemas/posts.js_, create an object called `headerSchema`. Pass in the following

```js
const headerSchema = {
  type: 'object',
  required: ['token'],
  properties: {
    token: typeString,
  },
};
```

For every of our private route's schema, add the `headerScheema` object like this

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

Your API is ready to go. Finally, let's talk about fastify-swagger. Fastify-swagger basically gives us documentation of our API, the endpoints, methods, and we can also test our endpoints with it.

We will register it as a plugin and then we are good to go. In our _server.js_ file, add this

```js
fastify.register(require('fastify-swagger'), {
  exposeRoute: true,
  routePrefix: '/docs',
  swagger: {
    info: { title: 'Fastify-api' },
  },
});
```

Visit `http://localhost:your-port/docs` to see the docs.

## Conclusion
Great job if you finished this project. Now you should start building more with Fastify. [Source of our Fastify CRUD API](https://github.com/Elijah-trillionz/fastify-auth-api)

Thank you for reading. See you next time. Don't forget to [buy me a coffee](https://buymeacoffee/elijahtrillionz).