You are here

How to Build a Serverless REST API with Node.js and MongoDB

Adnan Rahić published a tutorial on Hackernoon to build a Serverless API. He guides you with ten steps to create a service, install module, create a database on MongoDB Atlas, configure YAML, flesh out functions, add a database connection, add a note model, use dotenv for environment variables, deploy, and monitor.
A Serverless API allows to build and run apps without servers. To build the API, the tutorial will show you how to use MongoDB Atlas, a product that manages databases, global distribution, and backup. You will also use Node.js language.
 
Set up
Register for an AWS account. The AWS will act as a Lambda function, enabling to run code to initialize the database connection.

1. Create a service

To create a service, you need a command that will organize the required files and code to create the Lambda functions and API events. The reason why the author uses a rest-api is to create a path that will guide you to a directory.

$ sls create -t aws-nodejs -p rest-api && cd rest-api  

2. Install modules

You will need two modules, one for run the code before deploying to AWS by using a Serverless offline plugin. The second module avoids pushing Keys to GitHub.  For this purpose, you will use mongoose, an object modeling tool that works in an asynchronous environment and dotenv, a zero-dependency module that loads environment variables from an .env file into process.env.
 
The installation will follow this order: Serverless Offline, mongoose, and dotenv. You should be in the rest-api directory and here’s how the terminal should look: 

$ npm init -y
$ npm i --save-dev serverless-offline
$ npm i --save mongoose dotenv  

3. Create a database on MongoDB Atlas

Sign up to MongoDB Atlas to play in the sandbox. Now, open your account page and add a new organization, press on the new project button, type rest-api, and hit next. Skip the permissions page.
You will see a green Build a new cluster button. Press it to open a window, ensuring to choose the M0 instance and disable backups. Following, you will add an admin user for the cluster for the amount of $0.00. Confirm and Deploy.
 
Let’s write the code

4. Configure YAML

Configure in the serverless.yml file adding the CRUD methods to the handler.js. The functions will include: create, getOne, getAll, update, and delete. One more thing to do is to add a plugins section and a serverless-offline, that will be useful to test the service before AWS deployment.
 
Here’s the code for reference:

service: rest-api
provider:
 name: aws
 runtime: nodejs6.10 # set node.js runtime
 memorySize: 128 # set the maximum memory of the Lambdas in Megabytes
 timeout: 10 # the timeout is 10 seconds (default is 6 seconds)
 stage: dev # setting the env stage to dev, this will be visible in the routes
 region: us-east-1
functions: # add 4 functions for CRUD
 create:
   handler: handler.create # point to exported create function in handler.js
   events:
     - http:
         path: notes # path will be domain.name.com/dev/notes
         method: post
         cors: true
 getOne:
   handler: handler.getOne
   events:
     - http:
         path: notes/{id} # path will be domain.name.com/dev/notes/1
         method: get
         cors: true
 getAll:
   handler: handler.getAll # path will be domain.name.com/dev/notes
   events:
    - http:
        path: notes
        method: get
        cors: true
 update:
   handler: handler.update # path will be domain.name.com/dev/notes/1
   events:
    - http:
        path: notes/{id}
        method: put
        cors: true
 delete:
   handler: handler.delete
   events:
    - http:
        path: notes/{id} # path will be domain.name.com/dev/notes/1
        method: delete
        cors: true
plugins:
- serverless-offline # adding the plugin to be able to run the offline emulation  

5. Flesh out functions

Note the code below will include five functions with the same values. context.callbackWaitsForEmptyEventLoop will be set to false and connectToDatabase() will be the function call to start. You will see how the connectToDatabase() function will open the database interaction by using mongoose. If you want to learn how AWS explains a Lambda function with Node.js, take a break by reading this article.
 
Now, open the handler.js file, delete the default hello function, and add the following code: 

'use strict';
module.exports.create = (event, context, callback) => {
 context.callbackWaitsForEmptyEventLoop = false;
 connectToDatabase()
   .then(() => {
     Note.create(JSON.parse(event.body))
       .then(note => callback(null, {
         statusCode: 200,
         body: JSON.stringify(note)
       }))
       .catch(err => callback(null, {
         statusCode: err.statusCode || 500,
         headers: { 'Content-Type': 'text/plain' },
         body: 'Could not create the note.'
       }));
   });
};
module.exports.getOne = (event, context, callback) => {
 context.callbackWaitsForEmptyEventLoop = false;
 connectToDatabase()
   .then(() => {
     Note.findById(event.pathParameters.id)
       .then(note => callback(null, {
         statusCode: 200,
         body: JSON.stringify(note)
       }))
       .catch(err => callback(null, {
         statusCode: err.statusCode || 500,
         headers: { 'Content-Type': 'text/plain' },
         body: 'Could not fetch the note.'
       }));
   });
};
module.exports.getAll = (event, context, callback) => {
 context.callbackWaitsForEmptyEventLoop = false;
 connectToDatabase()
   .then(() => {
     Note.find()
       .then(notes => callback(null, {
         statusCode: 200,
         body: JSON.stringify(notes)
       }))
       .catch(err => callback(null, {
         statusCode: err.statusCode || 500,
         headers: { 'Content-Type': 'text/plain' },
         body: 'Could not fetch the notes.'
       }))
   });
};
module.exports.update = (event, context, callback) => {
 context.callbackWaitsForEmptyEventLoop = false;
 connectToDatabase()
   .then(() => {
     Note.findByIdAndUpdate(event.pathParameters.id, JSON.parse(event.body), { new: true })
       .then(note => callback(null, {
         statusCode: 200,
         body: JSON.stringify(note)
       }))
       .catch(err => callback(null, {
         statusCode: err.statusCode || 500,
         headers: { 'Content-Type': 'text/plain' },
         body: 'Could not fetch the notes.'
       }));
   });
};
module.exports.delete = (event, context, callback) => {
 context.callbackWaitsForEmptyEventLoop = false;
 connectToDatabase()
   .then(() => {
     Note.findByIdAndRemove(event.pathParameters.id)
       .then(note => callback(null, {
         statusCode: 200,
         body: JSON.stringify({ message: 'Removed note with id: ' + note._id, note: note })
       }))
       .catch(err => callback(null, {
         statusCode: err.statusCode || 500,
         headers: { 'Content-Type': 'text/plain' },
         body: 'Could not fetch the notes.'
       }));
   });
};

6. Add the database connection

Here’s where you will connect to the database MongoDB. You will create a new file in the root directory, along the handler.js. The author added db.js. 

const mongoose = require('mongoose');
mongoose.Promise = global.Promise;
let isConnected;
module.exports = connectToDatabase = () => {
 if (isConnected) {
   console.log('=> using existing database connection');
   return Promise.resolve();
 }
 console.log('=> using new database connection');
 return mongoose.connect(process.env.DB)
   .then(db => {
     isConnected = db.connections[0].readyState;
   });
};

7. Add a Note model

Create a new folder in the root directory and name it models. Inside, create a file names Note.js. It will help you to establish a mongoose schema. 

const mongoose = require('mongoose');
const NoteSchema = new mongoose.Schema({  
 title: String,
 description: String
});
module.exports = mongoose.model('Note', NoteSchema);

Now, export the model to use the handler.js. Like this: 

// top of handler.js
const connectToDatabase = require('./db');
const Note = require('./models/Note');

What's next is to add  a database connection URL to use the MondoDB. For this purpose, you will use dotenv.

8. Use dotenv for environment variables

The reason why you’re using dotenv is to leave your configuration files and your access keys in separate files. You just need to add the file to .gitgnore.
 
First, find the connection URL going back to MongoDB(Atlas). You will see there’s a connection button to press on the cluster you created earlier. A new popup will open up to add an IP address to the whitelist and access the database. Grab the connection URL pressing Connect your Application button. You will be redirected to a Copy a connection string. Press I am using driver 3.4 or earlier to copy the URL.
 
Once you’ve copy the URL, go to the variables.env file and add the connection URL: 

DB=mongodb://dbadmin:reallystrongpassword@cluster0-shard-00-00-e9ai4.mongodb.net:27017,cluster0-shard-00-01-e9ai4.mongodb.net:27017,cluster0-shard-00-02-e9ai4.mongodb.net:27017/test?ssl=true&replicaSet=Cluster0-shard-0&authSource=admin

Deploy and monitor

Dashbird is a tool that monitors, traces, alerts errors, and debugs Serverless applications. You can use it to ensure the service is working the way you want.

9. Deploy

Deploy by running the command $ sls deploy, that will access the AWS package to push the code and send it to Lambdas. Your terminal should look like this:

10. Monitor

Dashbird will help you monitor your project. After you press the rest-api-dev-getAll Lambda function, you will see a screen with stats and logs.

 
 

Original Article

Building a Serverless REST API with Node.js and MongoDB

Mayela Gonzalez Mayela Gonzalez is a Writer, Researcher, & Web Content Editor at ProgrammableWeb.com
 

Comments