Fastify and PostgreSQL REST API
Tags: postgres, node, javascript, webdev
Published: 2021-04-20 17:23:48

---
title:Fastify and PostgreSQL REST API
published: true
tags: PostgreSQL, node, javascript, webdev
canonical_url: https://wanjohi.vercel.app/2021/04/20/Fastify-and-PostgreSQL-REST-API/
---

## Enter the 'speed force'
From the [documentation](https://www.fastify.io/),  fastify is a fast and low overhead web framework for Node.js.

So, I decided to explore some of the awesome features that fastify offers including but not limited to, speed, extensibility via plugins and decorators, schema validation, and serialization and logging. I dived into their [documentation](https://www.fastify.io/docs/latest/), which is excellent by the way, with the help of some GitHub repositories and decided to build some REST API endpoints powered by fastify and a PostgreSQL database.

You can check out the [source code](https://github.com/Kenwanjohi/fastify) or follow along in this post.

## Getting Started
### Setting up the project
Navigate to the root folder of your project and run `npm init` to initialize your project. Create an `src` folder for your project source code and create an `index.js` file as the entry point.
### Installing dependencies
**Installing nodemon**

[nodemon](https://www.npmjs.com/package/nodemon) is a dev dependency that'll monitor your file changes and restart your server automatically.

You can install nodemon locally with npm:
```
npm install nodemon --save-dev
```
Add this npm script to the scripts in the `package.json` file
```json
"start": "nodemon src/index.js"
```
**Installing Fastify**

Install with npm:
```
npm i fastify --save
```
### Hello World: Starting and running your server
In the `index.js` file add this block of code:

```javascript
const fastify = require('fastify')({logger: true}) 
fastify.get('/', async (request, reply) => { 
     reply.send({ hello: 'world' }) 
  }) 
async function start()  { 
    try{ 
        await fastify.listen(3000) 
    } catch(err) { 
        fastify.log.error(err) 
        process.exit(1) 
    } 
} 
start()
```

On the first line, we create a fastify instance and enable logging, fastify uses [pino](https://getpino.io/#/) as its logger. We then define a `GET` route method, specify a homepage endpoint `'/'` and pass in the route handler function which responds with the object `{hello: 'world'}` when we make a get request to the homepage.

We instantiate our fastify server instance (wrapped in our `start` function)  and listen for requests on port 3000. To start the server, run `npm start` on your terminal in the root folder. You Server should now be running and the following will be logged in the terminal:
```
{"level":30,"time":1618477680757,"pid":5800,"hostname":"x","msg":"Server listening at http://127.0.0.1:3000"}
```

When you visit the homepage you should see the response:
```
curl http://localhost:3000/ 
{"hello":"world"}
```

Great we have our server!
## Plugins
We can extend fastify's functionality with plugins.
From the documentation:

> A plugin can be a set of routes, a server decorator, or whatever.

We can refactor our route into a plugin and put it in a separate file i.e `routes.js`, then require it in our root file and use the `register` API to add the route or other plugins.

Create a `routes.js` file and add this code:
```javascript
async function routes(fastify, options) { 
    fastify.get('/', async (request, reply) => { 
        reply.send({ hello: 'world' }) 
    }) 
} 
module.exports= routes
```

We then require our module in `index.js` and register it.
```javascript
const fastify = require('fastify')({logger: true})
const route  = require('./routes')
fastify.register(route)
async function start()  {
   ...
}
start()
```

A request on the homepage should still work. Great, we have our first plugin.

## Creating our database
To create a database we first need to connect to `psql`, an interactive terminal for working with Postgres.

To connect to `psql` run the command in the terminal:
```
psql -h localhost -U postgres
```

Enter your password in the prompt to connect to `psql`.

The `CREATE DATABASE databaseName` statement creates a database:
```sql
CREATE DATABASE todos;
```
To connect to the created database run the command:
```sql
\c todos
```
To create our table run the statement
```sql
CREATE TABLE todos ( 
    id UUID PRIMARY KEY, 
    name VARCHAR(255) NOT NULL, 
    "createdAt" TIMESTAMP NOT NULL, 
    important BOOLEAN NOT NULL, 
    "dueDate" TIMESTAMP, 
    done BOOLEAN NOT NULL 
);
```
## Connecting our  database
To interface with postgreSQL database we need [node-postgres](https://node-postgres.com/) or the `pg` driver.

To install `node-postgres`:
```
npm install pg
```
**Database connection plugin**

Let's create a plugin to connect to our database. Create a `db.js` file and add the following code: 
```javascript
const fastifyPlugin = require('fastify-plugin') 
const { Client } = require('pg') 
require('dotenv').config() 
const client = new Client({ 
    user: 'postgres', 
    password:process.env.PASSWORD, 
    host: 'localhost', 
    port: 5432, 
    database: process.env.DATABASE 
}) 
async function dbconnector(fastify, options) { 
    try { 
        await client.connect() 
        console.log("db connected succesfully") 
        fastify.decorate('db', {client}) 
    } catch(err) { 
        console.error(err) 
    } 
} 
module.exports= fastifyPlugin(dbconnector)
```

Let's skip the `fastifyPlugin` part first.

We require `Client` module from `node-postgres` and create a `client` instance, passing in the object with the various fields.

Make sure to create a `.env` file and add:
```
PASSWORD='yourpassword'
```
Install and require the `dotenv` module to load the environment variables
```
npm i dotenv
```
We then create our `dbconnector` plugin and inside the try block, we connect to our postgres database.

Inside the block you can also see:
```javascript
fastify.decorate('db', {client})
```
**What is the decorate function?**

In fastify, to add functionality to the fastify instance, you use [decorators](https://www.fastify.io/docs/latest/Decorators/). We use the `decorate` API, pass the property name `'db'` as the first argument and the value of our `client` instance (`{client}`) as the second argument. The value could also be a function or a string.
We export the plugin wrapped in a `fastifyPlugin` module.

Require the module in the `index.js` file and register it.
```javascript
const dbconnector = require('./db')
fastify.register(dbconnector)
fastify.register(route)
async function start()  {
  ...
}
start()
```
We can now access our client instance in other parts of the application for instance in our routes to query data using  `fastify.db.client`.

Let's take a step back to the `fastifyPlugin` module. Why wrap our plugin with fastifyPlugin? When we register a plugin, we create a fastify context (encapsulation), which means access to the data outside our registered plugin is restricted. In this case, we can't access our database `client` instance using `fastify.db.client` anywhere in our application.

To share context, we wrap our plugin in a `fastifyPlugin` module. We can now access our database `client` instance anywhere in our application.

## Serialization
Lets refactor our homepage route to return information from our database:
```javascript
async function routes(fastify, options) {  
    //Access our client instance value from our decorator
    const client = fastify.db.client
    fastify.get('/', {schema: allTodos}, async function (request, reply) { 
            try { 
                const {rows} = await client.query('SELECT * FROM todos') 
                console.log(rows) 
                reply.send(rows) 
            } catch(err) { 
                throw new Error(err) 
            } 
        })
}  
module.exports= routes
```

We First access our database `client` instance and assign it to a `client` variable.
Inside our routes we query all columns from our database using the shorthand `*` and send the returned todos using `reply.send(rows)` - you could also use `return rows`.
Make sure you add some todos in your database first in the `psql` terminal i.e:
```sql
INSERT INTO todos (id, name, "createdAt", important, "dueDate",  done) 
VALUES ('54e694ce-6003-46e6-9cfd-b1cf0fe9d332', 'learn fastify', '2021-04-20T12:39:25Z', true, '2021-04-22T15:22:20Z', false); 
INSERT INTO todos (id, name, "createdAt", important, "dueDate",  done)  
VALUES ('d595655e-9691-4d1a-9a6b-9fbba046ae36', 'learn REST APIs', '2021-04-18T07:24:07Z',true, null, false);
```

If an error occurs, trying to query our database, we throw the error.

When you look closer at our get route method, you can see have an object as our second argument with a `schema` key and `allTodos` as the value.

Fastify uses  [fast-json-stringify](https://www.npmjs.com/package/fast-json-stringify) to serialize your response body when a schema is provided in the route options.

To add the schema create a `schemas.js` file and add the `allTodos schema`:
```javascript
const allTodos = {
    response: {
        200: {
            type: 'array',
            items: {
                type: 'object',
                required: ['id', 'name', 'createdAt', 'important', 'dueDate', 'done'],
                properties: {
                    id: {type: 'string',  format: 'uuid'},                                                              
                    name: {type: 'string'},                                           
                    createdAt:{type: 'string',format: "date-time"},                  
                    important: {type: 'boolean'},
                    dueDate: {type: 'string',format: "date-time"},
                    done: {type: 'boolean'},
                }
            }
        }
    }
}
```

Fastify recommends using [JSON Schema](https://json-schema.org/) to serialize your outputs, you can read how to write JSON schema [here](https://json-schema.org/understanding-json-schema/index.html). 

We're specifying the `response`, the response `status code`, and the entity which is an `array` type. The `items` specify each entry in the array as an object with the required keys and the properties with the various fields and types.

Remember to require the module in the `routes.js` file.

## Validation
In the `routes.js` file, let's add a `POST` method route inside our route plugin to add todos to our database.
```javascript
fastify.post('/', {schema: addTodo}, async function(request, reply) {
			const {name, important, dueDate} = request.body
			const id = uuidv4()
			const done = false
			createdAt = new Date().toISOString()
			const query = {
					text: `INSERT INTO todos (id, name, "createdAt", important, "dueDate", done)
									VALUES($1, $2, $3, $4, $5, $6 ) RETURNING *`,
					values: [id, name, createdAt, important, dueDate, done],
					}
			try {
					const {rows} = await client.query(query)
					console.log(rows[0])
					reply.code(201)
					return {created: true}
			} catch (err) {
					throw new Error(err)
			}

	})
```

We allow the client to send a JSON object in the body with `name` of the todo, `important`, and `dueDate` properties.

We then generate a unique `id`, assign false to `done` and a timestamp assigned to `createdAt`.

To generate the unique id install `uuid`:
```
npm install uuid
```
Require the module in the `routes.js`:
```javascript
const { v4: uuidv4 } = require('uuid');
```

We then construct a query object with a `text` property with the SQL statement to insert the todos in the database and the `values` property containing the values to be inserted into the respective columns.

After a successful insert we send a `201 Created` status code back to the client.
In the `schemas.js` file, let's add the validation schema for our todos:
```javascript
const addTodo = {
    body: {
        type: 'object',
        required: ['name'],
        properties: {
            name: {type: 'string',},
            dueDate: {type: 'string', format: 'date-time', nullable: true, default: null},
            important: {type: 'boolean', default: false},
        }
    },
    response: {
        201: {
            type: 'object',
            properties: {
                created: {type: 'boolean'}
            }
        }
    }

}
```

Fastify uses [Ajv](https://ajv.js.org/) to validate requests.
We expect the client to always send the `name` of the todo by adding it in the required property array.

The `dueDate` property can be omitted by the client whereby it will be `null` by default. This is made possible by setting the `nullable` property to `true` which allows a data instance to be JSON null. When provided it has to be of the format 'date-time'.

The `client` can optionally indicate whether a todo is important or it falls back to the default.

If the above conditions are not satisfied, fastify will automatically send an error object with the error message.

For instance, if you omit a name, you should see an error like
```json
{
  "statusCode": 400,
  "error": "Bad Request",
  "message": "body should have required property 'name'"
}
```

Great! Our validation is working

## Adding other REST endpoints
**Update todo**
Let's allow users to set their todo as done or importance of the todo or change dueDate. To do that let's add a `PATCH` method route to our routes plugin.
```javascript
fastify.patch('/:id',{schema: updateTodo}, async function (request, reply) {
		const id = request.params.id
		const {important, dueDate, done} = request.body
		const query = {
				text:  `UPDATE todos SET 
								important = COALESCE($1, important), 
								"dueDate" = COALESCE($2, "dueDate"), 
								done = COALESCE($3, done) 
								WHERE id = $4 RETURNING *`,
				values : [important, dueDate, done, id]
		}
		try {
				const {rows} = await client.query(query)
				console.log(rows[0])
				reply.code(204)
		} catch (err) {
				throw new Error(err)
		}
})
```

We're extracting the `id` of the todo we want to update from the parameter and the values from the request body.

We then create our query statement, updating the columns provided optionally using the `COALESCE` function. That is, if the clients omit some properties in the JSON body, we update only the provided properties and leave the rest as they are in the todo row.

We then respond with a `204 No Content`.

Lets add a  validation schema for our route:
```javascript
const updateTodo = {
    body: {
        type: 'object',
        properties: {
            dueDate: {type: 'string', format: 'date-time'},
            important: {type: 'boolean'},
            done: {type: 'boolean'}
        }
    },
    params: {
        type: 'object',
        properties: {
          id: { type: 'string', format: 'uuid' }
        }
    }
}
```
params validates the params object.

**Delete todo**

To delete a todo, we just need the `id` sent in the URL parameter.
Add a `DELETE` method route:
```javascript
fastify.delete('/:id', {schema: deleteTodo}, async function(request, reply) {
			console.log(request.params)
			try {
					const {rows} = await client.query('DELETE FROM todos
					WHERE id = $1 RETURNING *', [request.params.id])
					console.log(rows[0])
					reply.code(204)
			} catch(err) {
					throw new Error(err)
			}
	})
```
Lets add a  validation schema for our `DELETE` route:
```javascript
const deleteTodo = {
    params: {
        type: 'object',
        properties: {
            id: {type: 'string', format: 'uuid'}
        }
    }
}
```

## Conclusion:
Give fastify a try and "take your HTTP server to ludicrous speed" ~ [Matteo Collina](https://twitter.com/matteocollina).

You can check out the project source code [here](https://github.com/Kenwanjohi)
### References:
* [Fastify Documentation](https://www.fastify.io/docs/latest/)
* [Understanding JSON schema](https://json-schema.org/understanding-json-schema/index.html)

Fastify examples; GitHub repos:
* [fastify-example](https://github.com/delvedor/fastify-example)
* [fastify-example-todo](https://github.com/fastify/fastify-example-todo)