4

How to create, test, and deploy a Node.js REST API using Mocha and Google Cloud...

 2 years ago
source link: https://apollo-learn.com/lessons/how-to-create%2C-test%2C-and-deploy-a-node.js-rest-api-using-mocha-and-google-cloud-functions-62
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

Welcome! Today we'll be creating a REST API (https://www.ibm.com/cloud/learn/rest-apis) using Node.js (https://nodejs.org/en/) and the testing framework Mocha (https://mochajs.org/), and then we'll be deploying our API onto Google Cloud Platform's (GCP) Cloud Functions (https://cloud.google.com/functions).

Installing Node.js

GCP has a great guide for getting setup locally with Node.js https://cloud.google.com/nodejs/docs/setup - before you begin make sure you have node and npm installed!

Project Setup

To create a fresh node project using npm run the following which will create a new folder, cd into that folder, and run the npm init command:

$ mkdir geodist && cd geodist && npm init --yes

Then we'll want to install our dependencies by running the following in the same folder:

$ npm install mocha expect --save-dev

Then we'll want to modify our package.json file to run mocha for our test command, change your package.json to the following (just the test section under scripts is modified):

{
  "name": "geodist",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "mocha *.test.js"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "expect": "^28.1.1",
    "mocha": "^10.0.0"
  }
}

Now we can start writing code!

Writing the code

Now open up your favourite text editor and create a file called index.js in the geodist directory we created earlier and paste the following inside:

/**
 * Responds to any HTTP request.
 *
 * @param {!express:Request} req HTTP request context.
 * @param {!express:Response} res HTTP response context.
 */
const main = (req, res) => {
    try {
        const {lat1, lat2, lon1, lon2} = req.body
        if (!lat1 || !lat2 || !lon1 || !lon2) {
            res.status(400).json({message: "missing param", items: {lat1, lat2, lon1, lon2}})
        } else {
            const R = 6371e3; // metres
            const φ1 = lat1 * Math.PI/180; // φ, λ in radians
            const φ2 = lat2 * Math.PI/180;
            const Δφ = (lat2-lat1) * Math.PI/180;
            const Δλ = (lon2-lon1) * Math.PI/180;

            const a = Math.sin(Δφ/2) * Math.sin(Δφ/2) +
                      Math.cos(φ1) * Math.cos(φ2) *
                      Math.sin(Δλ/2) * Math.sin(Δλ/2);
            const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));

            const distance = R * c; // in metres

            res.json({distance})
        }
    }
    catch (err) {
        console.log(err)
        res.status(500).send(err)
    }
}
exports.main = main

This code does a few things: it takes the params lat1, lat2, lon1, and lon2 from the request body and then returns a 400 status if any are missing. If all params are present we calculate the distance in meters between them and send the result back as JSON (i.e. Content-Type: application/json) with a 200 status (implict when using res.json). If we encounter an error for any reason we log it to the console and return a 500 status. Calculation for the distance between 2 points found on https://www.movable-type.co.uk/scripts/latlong.html 

This code is easy to deploy to GCP (coming below) however it would be great to test its functionality locally, which is why we've installed Mocha and Except. In your editor again create a new file, this time named index.test.js, and paste the following inside:

const assert = require('assert') 
const {main} = require('./index')
                                                    
describe('Compute distance between 2 points', () => {                                                                        
    const resMock = {
        status: s => {resMock.statusContent.push(s); return resMock},
        statusContent: [],
        json: body => {resMock.jsonContent.push(body); return resMock}, 
        jsonContent: [],
        reset: () => { resMock.statusContent = []; resMock.jsonContent = [] },
    }
    beforeEach(() => {
        resMock.reset()
    })
    it('Should return a 400 status with missing input', () => {
        const reqMock = {body: {lon1: 1, lon2: 1, lat1: 1}}
        main(reqMock, resMock)

        assert.equal(resMock.statusContent.length, 1)
        assert.equal(resMock.statusContent[0], 400)

        assert.equal(resMock.jsonContent.length, 1)
        assert.equal(resMock.jsonContent[0].message, 'missing param')
        assert.equal(resMock.jsonContent[0].items.lat1, 1)
        assert.equal(resMock.jsonContent[0].items.lat2, undefined)
        assert.equal(resMock.jsonContent[0].items.lon1, 1)
        assert.equal(resMock.jsonContent[0].items.lon2, 1)
    })                                  
    it('Should return the distance of 0 between 2 of the same points', () => {
        const reqMock = {body: {lon1: 1, lon2: 1, lat1: 1, lat2: 1}}
        main(reqMock, resMock)

        assert.equal(resMock.statusContent.length, 0)

        assert.equal(resMock.jsonContent.length, 1)
        assert.equal(resMock.jsonContent[0].distance, 0)
    })                                                                                                                                                                                                            
    it('Should return the distance between 2 points when input is valid', () => {
        const reqMock = {body: {lat1: 19.432798, lon1: -99.064590, lat2: 40.758017, lon2: -73.985667}}
        main(reqMock, resMock)

        assert.equal(resMock.statusContent.length, 0)

        assert.equal(resMock.jsonContent.length, 1)
        // Mexico City is more than 3000KM away from NYC (where these points are)
        assert.ok(resMock.jsonContent[0].distance > 3000000)
    })                                                                                                                                                                                                            
}) 

Now when you run npm run test these tests will execute and you can be sure that the logic of our program is as expected. In these tests we define a mock which captures the input to the function calls on our response object so we can see what would be returned in a live scenario.

Deploying the code

Now that we've written our code we can deploy it to GCP by using the gcloud command. If you do not already have it installed follow these instructions: https://cloud.google.com/sdk/docs/install. After you have the gcloud command available deploying our code is as simple as running the following from inside the same directory as the previous commands:

$ gcloud functions deploy geodist --entry-point main --runtime nodejs16 --trigger-http --allow-unauthenticated --source ./

And now you need to allow anyone to trigger your cloud function by giving the principal allUsers access to invoke your cloud function. First go to https://console.cloud.google.com/functions/list and find your cloud function:

Click on it and then go to the Permissions tab:

Next click Add:

Then under New principals write allUsers and under Role select Cloud Functions Invoker. After you click save you will be prompted to allow public access - press allow when prompted.

Running our function

To test your function you can either use the built in testing section on GCP or use curl. Here are a couple test requests using the same input as our unit tests, to test on your function substitute out the URL https://YOUR_REGION_AND_PROJECT.cloudfunctions.net/geodist with your cloud function's URL.

$ curl --header 'content-type: application/json' --url https://YOUR_REGION_AND_PROJECT.cloudfunctions.net/geodist --data '{"lon1": 1, "lat1" : 1, "lon2": 1, "lat2": 1}'

{"distance":0}
$ curl --header 'content-type: application/json' --url https://YOUR_REGION_AND_PROJECT.cloudfunctions.net/geodist --data '{"lat1": 19.432798, "lon1": -99.064590, "lat2": 40.758017, "lon2": -73.985667}'

{"distance":3359181.255445961}

Next steps

Congradulations you've created, tested, and deployed your Node.js REST API! Next you can think about what you can do to improve your program - perhaps you want to further validate the input, or save the data somewhere so you don't need to recompute results. You could also experiment with adding additional npm packages, the sky's the limit!


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK