Deploying a serverless REST API with Node.js, AWS Lambda, API Gateway, RDS and PostgreSQL.

Giuseppe Picciano
Aug 28 · 8 min read

In this walkthrough, I’ll be making use of the Serverless Framework to build and deploy a simple Node.js API to Lambda and API Gateway. Our data will be stored using Amazon Relational Database Service (RDS) for PostgreSQL.

Pre-Checklist

  1. Node.js version 6.x or later installed locally on your machine. You can get it here.
  2. An AWS account, which you can create here.
  3. We’ll be using pgAdmin to create our database tables, you can download that here.
  4. Postman, a super handy tool we’ll be using to test our API. Get that here.
  5. A hot beverage of choice. Mine is a latte. ☕

Installing the Serverless Framework CLI

Let’s start by installing the serverless npm package. By using the -g flag, the package is installed globally on your machine.

npm install -g serverless

Creating a new project

serverless

Running the command serverless will guide you through creating a new serverless project. Select AWS Node.js as your template and then provide a meaningful name for the project. I’ll call mine serverless-tutorial-rds.

Now to add a package.json file by initialising the directory as a new npm package. Firstly navigate into the new projects directory and then run npm init. You can use the flag -y to skip the various npm package settings.

cd serverless-tutorial-rds
npm init -y

Project Structure

Open the project in your text editor of choice and you’ll see what files were created for us. The Serverless.yml file is where we define our functions, the events that trigger them, and the resources they use. The handler.js file is where we will maintain the logic for our functions.

Configuring Amazon Credentials

Before we write any code, let’s configure our amazon credentials to ensure we can deploy our project. In a new tab, open the AWS Management Console and navigate to the IAM service.

Select users on the sidebar navigation menu and then press Add user. Make sure for the access type you select programmatic access.

You should now be prompted to add specific permissions. You can add the user to a group, copy permissions from an existing user or attach inline policies directly. For this project, we’ll create a new group. We’ll need the following permissions:

  • AmazonRDSFullAccess
  • AWSLambdaFullAccess
  • IAMFullAccess
  • AmazonAPIGatewayAdministrator

Add your user to the newly created group and then proceed to create the user.

On successful creation, you should be able to see the Access key ID and the Secret access key. Head back to your terminal and enter the following command to configure your security credentials. Remember to replace ‘<your_access_key>’ and ‘<your_secret_key’> with the actual keys.

serverless config credentials --provider aws --key <your_access_key> --secret <your_secret_key>

Amazon RDS Setup

Head over to the RDS service in the AWS Management Console. Navigate to databases and click create database. Make sure to select free tier for the template.

Give your database-cluster an appropriate name, and then set username and password credentials for accessing the database.

To make our lives easier during development and testing, find the dropdown ‘additional connectivity configuration’ under ‘Connectivity’ and set ‘publicly accessible’ to Yes.

The remaining settings can be left at their defaults. It will take a few minutes for Amazon to build the database and make it available for use.

Connecting with pgAdmin

Now that our database is accessible, we can open pgAdmin and use it to connect. On the dashboard, select add new server.

Give your server a name, note this is just for you. By clicking on the connection tab you can input the specific information relating to the database you created.

Host & Port

This can be found under the Connectivity & security tab on your database cluster. Enter the database’s endpoint as the host.

Maintenance Database

Set the maintenance database to postgres, this is the name of the default database created for us.

Username & Password

The username and password fields refer to the credentials you defined when creating the database cluster.

Creating Tables

Select the database on the left menu, select the postgres database, select Schemas, select public, and then right-click Tables and create a new table.

We’ll set the table name to todo and set the columns as follows:

Serverless.yml

We’ll build our API to create, edit and delete Todo objects. It will follow the standard REST architecture, making use of the following HTTP methods:

  • GET : To return the Todo objects.
  • POST : To create a Todo object.
  • PUT : To update a Todo object.
  • DELETE : To delete a Todo object.

Here is my code for the serverless.yml file.

service: serverless-tutorial-rds
app: server-app
provider:
name: aws
runtime: nodejs10.x
memorySize: 128
timeout: 30
functions: getTodo:
handler: handler.getTodo
events:
- http:
path: todo/{id}
method: get
cors: true
getAllTodos:
handler: handler.getAllTodos
events:
- http:
path: todo
method: get
cors: true
createTodo:
handler: handler.createTodo
events:
- http:
path: todo
method: post
cors: true
updateTodo:
handler: handler.updateTodo
events:
- http:
path: todo/{id}
method: put
cors: true
deleteTodo:
handler: handler.deleteTodo
events:
- http:
path: todo/{id}
method: delete
cors: true

Database config

In the root directory, create a new folder called config and then create a new file inside of this folder called db.js. From this file we will export our database credentials as follows:

module.exports =  {
database: 'postgres',
host: 'your-endpoint',
port: '5432',
user: 'user-name',
password: 'password',
}

Make sure to add this file to your .gitignore before committing to GitHub.

Postgresql-easy

In order to query the database, we’ll use the npm package postgresql-easy.

npm install postgresql-easy

Now, in the root directory create a file called db_connect.js. In this file, we’ll import the db config file, import the postgresql-easy module and then create and export a new postgres connection.

const PgConnection = require('postgresql-easy');
const dbConfig = require('./config/db');
const pg = new PgConnection(dbConfig);
module.exports = pg;

Handler.js

At the top of the handler.js file, import the db_connect file.

const db = require('./db_connect');

Now let’s write the functions to handle each of the possible requests.

getAllTodos

The function db.getAll takes a table name as an argument and returns all of its records.

module.exports.getAllTodos = (event, context, callback) => {  context.callbackWaitsForEmptyEventLoop = false;  db.getAll('todo')
.then(res => {
callback(null, {
statusCode: 200,
body: JSON.stringify(res)
})
})
.catch(e => {
console.log(e);
callback(null, {
statusCode: e.statusCode || 500,
body: 'Error: Could not find Todos: ' + e
})
})
};

getTodo

The function db.getById takes two arguments. The first is the Table name and the second is the Id of the record to return. For this function to work, the table must have a column named id.

module.exports.getTodo = (event, context, callback) => {  context.callbackWaitsForEmptyEventLoop = false;  db.getById('todo', event.pathParameters.id)
.then(res => {
callback(null,{
statusCode: 200,
body: JSON.stringify(res)
})
})
.catch(e => {
callback(null,{
statusCode: e.statusCode || 500,
body: "Could not find Todo: " e
})
})
};

createTodo

The function db.insert takes two arguments: the table name and the data of the record to be created.

module.exports.createTodo = (event, context, callback) => {  context.callbackWaitsForEmptyEventLoop = false;  const data = JSON.parse(event.body);  db.insert('todo', data)
.then(res => {
callback(null,{
statusCode: 200,
body: "Todo Created!" + res
})
})
.catch(e => {
callback(null,{
statusCode: e.statusCode || 500,
body: "Could not create Todo " + e
})
})
};

updateTodo

The function db.updateById takes three arguments: the table name, the id of the record to be updated, and the data to be updated with. The id is pulled from the URL of the request by using event.pathParameters.id.

module.exports.updateTodo = (event, context, callback) => {  context.callbackWaitsForEmptyEventLoop = false;  const data = JSON.parse(event.body);
db.updateById('todo', event.pathParameters.id, data)
.then(res => {
callback(null,{
statusCode: 200,
body: "Todo Updated!" + res
})
})
.catch(e => {
callback(null,{
statusCode: e.statusCode || 500,
body: "Could not update Todo" + e
})
})
};

deleteTodo

The function db.deleteById takes two arguments: the table name, and the id of the record to be deleted.

module.exports.deleteTodo = (event, context, callback) => {  context.callbackWaitsForEmptyEventLoop = false;  db.deleteById('todo', event.pathParameters.id)
.then(res => {
callback(null,{
statusCode: 200,
body: "Todo Deleted!"
})
})
.catch(e => {
callback(null,{
statusCode: e.statusCode || 500,
body: "Could not delete Todo" + e
})
})
};

Testing

The serverless-offline node module allows you to run the server locally such that you can test if the requests are handled correctly.

npm install serverless-offline

In order to use the module, it needs to be added as a plugin to the serverless.yml file. Add the following lines to serverless.yml.

plugins:
- serverless-offline

To run the server locally, run the following command.

serverless offline

After running the command you should see the available routes printed in the terminal.

POST

We can use Postman to send requests to these routes and see if our code functions as intended. Let’s start with a POST request to test todo creation. In the body of the request, select the raw option and provide the data for the Todo as a JSON object.

GET

To test if the record was saved in the database, send a GET request to http://localhost:3000/todo. If successful, you should see something like this:

PUT

To test the update method, sent a PUT request as follows. I’m going to change the ‘completed’ attributed of my todo to ‘true’.

DELETE

And finally, the delete request.

Deploying

The last step is to deploy the API to AWS lambda. The serverless framework makes this really easy, all we need to do is run the following command.

serverless deploy

Conclusion

Your API is now ready to to go 🎉

If you’re interested you can find my code here.

If you enjoyed this article, feel free to like and share.

The Startup

Medium's largest active publication, followed by +504K people. Follow to join our community.

Giuseppe Picciano

Written by

Software engineer and computer science student from the UK.

The Startup

Medium's largest active publication, followed by +504K people. Follow to join our community.

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade