Beginner's Guide for Creating a Serverless REST API using NodeJS over Google Cloud Functions
Tags: webdev, beginners, javascript, serverless
Published: 2019-03-21 16:09:33

---
title: Beginner's Guide for Creating a Serverless REST API using NodeJS over Google Cloud Functions
published: true
description: How yo create a rest api using Node.js over google cloud functions(serverless) and firestore
tags: [webdev, beginners, javascript, serverless]
cover_image: https://cdn-images-1.medium.com/max/800/1*JQ18LLkSSswKfaweT-eHlw.png
---
# API REST using Google Cloud Functions (Serverless)

Serverless application has gained a lot of importance over time. It allows focussing on your app code/tests without worrying about configurations, deployment process or scalability.

We're going to create a function that will be exposed via rest URL. That function will be invoked every time a HTTP(S) request is received. 

During execution, an express server will be summoned exposing our REST services. 

## What we are going to build? 

- Express API for CRUD services(create, read, update and delete) on a Firestore database.
- Use Google Cloud Function to expose our Express server
- Deploy our Google Cloud Function using Cloud CLI. 


## Creating our firebase project

In order to create our first project, let's do it [here](https://console.firebase.google.com). Select _add project_, project's name must be unique, let's use prefix `github-ring-{github_user}`, github-ring-levinm in my case. Be sure to select Firestore as our database.

![image1](https://thepracticaldev.s3.amazonaws.com/i/m6tvhhv2ifz29iugrctp.png)

For creating our database, click on _Develop_>_Database_ and select "start in test mode". 

![image2](https://thepracticaldev.s3.amazonaws.com/i/uckh6gvzzcv9wnhk37tv.png)


## Initializing our project locally

We need to install firebase using NPM.

```
npm install -g firebase-tools
```

Then, let's login into our firebase account.

```
firebase login
........... input credentials
```

Initialize the project

```
firebase init
........ select project
```

![image3](https://thepracticaldev.s3.amazonaws.com/i/gx4iai2itqmmxsfjftlz.png)

It will prompt an interactive console.

1. Select Functions and Hosting options. 
2. What language would you like to use to write Cloud Functions? TypeScript
3. Do you want to use TSLint to catch probable bugs and enforce style? Yes
4. Do you want to install dependencies with npm now? Yes
5. What do you want to use as your public directory? Press enter to select public (it is the default option)
6. Configure as a single-page app (rewrite all urls to /index.html)? No


We're ready, our firebase project was initialized.

## Installing Express.js and dependencies

```
cd functions
npm install --save express body-parser 
```

## Creating our Google Cloud Function

Open `src/index.ts`, it will be the entrypoint for our Express.js server

### Import main libraries

```javascript
import * as functions from 'firebase-functions';
import * as admin from 'firebase-admin';
import * as express from 'express';
import * as bodyParser from "body-parser";
```

### Initialize firebase for accesing to its services
```javascript
admin.initializeApp(functions.config().firebase);
```

### Intialize Express.js server
```javascript
const app = express();
const main = express();
```

### Configure the server.

- Let's add the path used for receiving the request.
- Select JSON as our main parser for processing requests body.

```javascript
main.use('/api/v1', app);
main.use(bodyParser.json());
```

### Export our function.

Last but not least, let's define our Google Cloud Function name, we are going to expose it using `export`. Our function will receive an express server object(this case `main`) which will be used for request processing. If you want more information regarding how it works, you can check this good [answer](https://github.com/twhite96/js-dev-reads) on Stackoverflow

```javascript
export const webApi = functions.https.onRequest(main);
```

### Creating our first service
Let's expose a GET endpoint returning just a string. 


```javascript
app.get('/warm', (req, res) => {
    res.send('Calentando para la pelea');
})
```

Our `src/index.ts` file should look like this:

```javascript

import * as functions from 'firebase-functions';
import * as admin from 'firebase-admin';
import * as express from 'express';
import * as bodyParser from "body-parser";

admin.initializeApp(functions.config().firebase);

const app = express();
const main = express();

main.use('/api/v1', app);
main.use(bodyParser.json());

export const webApi = functions.https.onRequest(main);

app.get('/warmup', (request, response) => {

    response.send('Warming up friend.');

})

```

### Deploying our function.

Before deploying it, we need to change our config file `firebase.json` as follows:

<pre>
{
  "functions": {
    "predeploy": [
      "npm --prefix \"$RESOURCE_DIR\" run lint",
      "npm --prefix \"$RESOURCE_DIR\" run build"
    ]
  },
  "hosting": {
    "public": "public",
    "ignore": [
      "firebase.json",
      "**/.*",
      "**/node_modules/**"
    ],
    <b>"rewrites": [
      {
        "source": "/api/v1/**",
        "function": "webApi"
      }
    ]</b>
  }
}
</pre>

This rule allows "to route" all requests sent through `api/v1` to be served by `webApi` function (Our exported one).

Also, Google CLI installs Typescript v2 by default. So, we need to update our typescript version `>=3.3.1`. You can do it in `functions.package.json`. 

<pre>
  "devDependencies": {
    "tslint": "~5.8.0",
    <b>"typescript": "~3.3.1"</b>
  },
</pre>

Re-install dependencies.

```
cd functions
npm install
```

We are ready for deploying.

```
firebase deploy
```
```
.....
✔  Deploy complete!

Project Console: https://console.firebase.google.com/project/github-ring-levivm/overview
Hosting URL: https://github-ring-levivm.firebaseapp.com
```

If everything is ok, Hosting URL will be our Google Cloud Function endpoint. 

### Testing our function

Let's send a `GET` request using `CURL`
```shell
$ curl -G "https://github-ring-levivm.firebaseapp.com/api/v1/warmup"
Warming up friend.
```

## Rest API CRUD 

Let's add our CRUD endpoints. We are going to manage `fights` information. 

### Create a record

First, let's initialize our database. We open our `src/index.ts` and add this after admin initialization

```javascript

admin.initializeApp(functions.config().firebase);
const db = admin.firestore(); // Add this

```

In order to create a fight record, let's create `POST /fights/` endpoint. Our fight record is going to have a `winner`,  `loser` and `title`.

```javascript
app.post('/fights', async (request, response) => {
  try {
    const { winner, loser, title } = request.body;
    const data = {
      winner,
      loser,
      title
    } 
    const fightRef = await db.collection('fights').add(data);
    const fight = await fightRef.get();

    response.json({
      id: fightRef.id,
      data: fight.data()
    });

  } catch(error){

    response.status(500).send(error);

  }
});
```
- We get our post data using `request.body`
- We use `add()` method to add a new fight, if the collection doesn't exist (our case), it will create it automatically. 
- In order to get the actual record data, we must use `get()` over the ref.
- Return a json using `response.json`. 

### Get a record 

We create a `GET /fights/:id` endpoint in order to fetch a fight by id.

```javascript
app.get('/fights/:id', async (request, response) => {
  try {
    const fightId = request.params.id;

    if (!fightId) throw new Error('Fight ID is required');

    const fight = await db.collection('fights').doc(fightId).get();

    if (!fight.exists){
        throw new Error('Fight doesnt exist.')
    }

    response.json({
      id: fight.id,
      data: fight.data()
    });

  } catch(error){

    response.status(500).send(error);

  }
  
});
```
- We get the fight id using `request.params`.
- We validate if the id is not blank.
- We get the fight and check if it exists.
- If fight doesn't exist we throw an error
- If fight exists, we return the data.

### Get a record list

We create a `GET /fights/` endpoint.

```javascript
app.get('/fights', async (request, response) => {
  try {

    const fightQuerySnapshot = await db.collection('fights').get();
    const fights = [];
    fightQuerySnapshot.forEach(
        (doc) => {
            fights.push({
                id: doc.id,
                data: doc.data()
            });
        }
    );

    response.json(fights);

  } catch(error){

    response.status(500).send(error);

  }
  
});

```

- We get a collection snapshot.
- We iterate over every document and push the data into an array.
- We return our fight list.


### Update a record

We must create a `PUT /fights/:id` endpoint in order to update a fight by `id`.

```javascript
app.put('/fights/:id', async (request, response) => {
  try {

    const fightId = request.params.id;
    const title = request.body.title;

    if (!fightId) throw new Error('id is blank');

    if (!title) throw new Error('Title is required');

    const data = { 
        title
    };
    const fightRef = await db.collection('fights')
        .doc(fightId)
        .set(data, { merge: true });

    response.json({
        id: fightId,
        data
    })


  } catch(error){

    response.status(500).send(error);

  }
  
});
```
- We get request data.
- We validate the data
- We update a record using `set(data, merge: true)`. It means it is going to update only the fields passed on data parameter.


### Deleting a record. 

For deleting a fight, we need to add an endpoint `DELETE /fights/:id`. 

```javascript 

app.delete('/fights/:id', async (request, response) => {
  try {

    const fightId = request.params.id;

    if (!fightId) throw new Error('id is blank');

    await db.collection('fights')
        .doc(fightId)
        .delete();

    response.json({
        id: fightId,
    })


  } catch(error){

    response.status(500).send(error);

  }
  
});

```
- We get the fight id.
- We use `delete()` in order to delete a doc instance (Remember that firestore is database based on documents( "NoSQL" ))


Our `src/index.ts` file should looks like this

```javascript
import * as functions from 'firebase-functions';
import * as admin from 'firebase-admin';
import * as express from 'express';
import * as bodyParser from "body-parser";

admin.initializeApp(functions.config().firebase);
const db = admin.firestore(); // Add this

const app = express();
const main = express();

main.use('/api/v1', app);
main.use(bodyParser.json());

export const webApi = functions.https.onRequest(main);

app.get('/warmup', (request, response) => {

    response.send('Warming up friend.');

});

app.post('/fights', async (request, response) => {
  try {
    const { winner, losser, title } = request.body;
    const data = {
      winner,
      losser,
      title
    } 
    const fightRef = await db.collection('fights').add(data);
    const fight = await fightRef.get();

    response.json({
      id: fightRef.id,
      data: fight.data()
    });

  } catch(error){

    response.status(500).send(error);

  }
});

app.get('/fights/:id', async (request, response) => {
  try {
    const fightId = request.params.id;

    if (!fightId) throw new Error('Fight ID is required');

    const fight = await db.collection('fights').doc(fightId).get();

    if (!fight.exists){
        throw new Error('Fight doesnt exist.')
    }

    response.json({
      id: fight.id,
      data: fight.data()
    });

  } catch(error){

    response.status(500).send(error);

  }
});

app.get('/fights', async (request, response) => {
  try {

    const fightQuerySnapshot = await db.collection('fights').get();
    const fights = [];
    fightQuerySnapshot.forEach(
        (doc) => {
            fights.push({
                id: doc.id,
                data: doc.data()
            });
        }
    );

    response.json(fights);

  } catch(error){

    response.status(500).send(error);

  }
  
});

app.put('/fights/:id', async (request, response) => {
  try {

    const fightId = request.params.id;
    const title = request.body.title;

    if (!fightId) throw new Error('id is blank');

    if (!title) throw new Error('Title is required');

    const data = { 
        title
    };
    const fightRef = await db.collection('fights')
        .doc(fightId)
        .set(data, { merge: true });

    response.json({
        id: fightId,
        data
    })


  } catch(error){

    response.status(500).send(error);

  }
  
});

app.delete('/fights/:id', async (request, response) => {
  try {

    const fightId = request.params.id;

    if (!fightId) throw new Error('id is blank');

    await db.collection('fights')
        .doc(fightId)
        .delete();

    response.json({
        id: fightId,
    })


  } catch(error){

    response.status(500).send(error);

  }
  
});
```

## Testing

We deploy our function.

```shell
firebase deploy
....
```

We test all our endpoints.
```bash
# Testing create fight (POST /fights)
$ curl -d '{"winner":"levi", "losser":"henry", "title": "fight1"}' -H "Content-Type: application/json" -X POST "https://github-ring-levivm.firebaseapp.com/api/v1/fights/"

> {"id":"zC9QORei07hklkKUB1Gl","data":{"title":"fight1","winner":"levi","losser":"henry"}

# Testing  get a fight (GET /fight:id)
$ curl -G "https://github-ring-levivm.firebaseapp.com/api/v1/fights/zC9QORei07hklkKUB1wGl/"

>{"id":"zC9QORei07hklkKUB1Gl","data":{"winner":"levi","losser":"henry","title":"fight1"}}


# Testing get fights list (GET /fights/)
$ curl -G "https://github-ring-levivm.firebaseapp.com/api/v1/fights/"
> [{"id":"zC9QORei07hklkKUB1Gl","data":{"title":"fight1","winner":"levi","losser":"henry"}}]

# Testing update a fight (PUT /fights/:id)
$ curl -d '{"title": "new fight title"}' -H "Content-Type: application/json" -X PUT "https://github-ring-levivm.firebaseapp.com/api/v1/fights/zC9QORei07hklkKUB1Gl/"

> {"id":"zC9QORei07hklkKUB1Gl","data":{"title":"new fight title"}}

# Testing delete a fight (DELETE /fight/:id)
$ curl -X DELETE "https://github-ring-levivm.firebaseapp.com/api/v1/fights/zC9QORei07hklkKUB1Gl/"

> {"id":"zC9QORei07hklkKUB1Gl"}

```

And we are done, we have built our API Rest using Google Cloud Function (Serverless).

Note: You can check your database using Firestore interface within our Firebase console. 

![image4](https://thepracticaldev.s3.amazonaws.com/i/fepjsdvbvkwz47t1ftoq.png)

If this was helpful, share it :). 

> If you like my content or it was helpful, you can motivate me to write more content by [buying me a coffee](https://www.buymeacoffee.com/R3i9IQJ)