We are going to build a serverless RESTful API for getting contact information using Amazon Web Services (AWS)!

As a note, this article mainly focuses on getting everything working locally, so you can develop and test the API without having a dependency on internet and AWS. This article will also use pure ES6 that is supported by Node 8.10.x, no transpiling.

The original source code for this article can be found here: https://github.com/vanister/contacts_api/tree/medium-article-source

Project setup

Install the following tools and frameworks:

Next, create the project folder and initialize it using npm .

$ mkdir contacts_api

$ cd contacts_api/

$ npm init -y

Dependencies

Install the following packages to work with Serverless and AWS:

aws-sdk— The Amazon Web Services SDK is used for programmatic access to the various AWS services.

dotenv — A module for storing and setting environment variables.

jest — All-in-one JavaScript unit testing framework.

serverless — Framework for managing deployments on AWS.

serverless-offline— Plugin to emulate AWS API Gateway and Lambdas for local development.

$ npm install -g serverless

$ npm install --save-dev serverless-offline aws-sdk jest dotenv

Optionally, you can install the serverless-dynamodb-local plugin if you want to use serverless to manage DynamoDB locally.

Project structure

Before we start writing the handler code, we’re going to structure the project folder and configure our tools.

Create the following structure at the root level:

/contacts_api

|--/src

|----/handlers

|------/contacts

|--------contacts.serverless.yml

|--.env

|--.gitignore

|--jest.config.js

|--serverless.yml

| …

| …

|--package.json

Update the following config files with these settings:

The .env file will store our environment variables. Make sure to list it in the .gitignore file so that you don’t accidentally commit some of your private environment values, if you choose to use real AWS keys.

# file: .gitignore # package directories

node_modules/ # Serverless directories

.serverless/

.dynamodb # files

.DS_Store

.env # tests

coverage/ # file: .env AWS_ENDPOINT=’http://localhost:8000'

AWS_REGION=’localhost’

AWS_ACCESS_KEY_ID=’fake-access-key’

AWS_SECRET_ACCESS_KEY=’fake-secret-key’

The jest.config.js file is used to config Jest outside of the cli.

The serverless.yml and contacts.serverless.yml files are configurations for the Serverless framework to deploy and run our lambdas locally.

serverless.yml

contacts.serverless.yml

Repository and utilities

Before we can implement the handlers, we will need to write a repository and some utilities to help abstract the logic out of the handlers to make them testable.

We will be using the Repository pattern to create a layer of abstraction between DynamoDB and our Contact entity. The data we get back from the repository will need to be formatted, which we will create some helper utilities to aid with.

Create a file called src/repositories/contact.repository.js for our contact repository class.

We will set up our repository to accept the DynamoDB’s DocumentClient as a dependency which will act as the UnitOfWork.

contact.repository.js

We are using the DocumentClient and its promises to make working with DynamoDB simpler and leverage es6 and Node’s support for async/await.

The repository simply returns a promise with data returned from DocumentClient.

Next, let’s create the request and response utilities so that we can use them to extract, parse and format data to be used by our lambda functions.

Create a file called src/utils/request.util.js for our request utility.

For now, the request utility will contain a single function that will accept some parser function and return a new function that uses it to parse some text we pass to it.

request.util.js

Create a file called src/utils/response.util.js for our response utility.

response.util.js

The withStatusCode response utility function is similar to the request utility, but instead of parsing text, it will format data into text. It also contains addition checks to make sure our status codes are within the range of allowable status codes.

Lastly, we will create a factory module for creating instances of DynamoDB.DocumentClient so we can keep our Lambda functions DRY.

Create a file called src/dynamodb.factory.js for our DynamoDB factory.

dynamodb.factory.js

Lambda functions

The way to think about these functions is to treat them as if they are isolated from each other. We need to require all of the dependencies needed by a function so that it can execute independently.

Since we abstracted out our logic and AWS dependencies into a repository and utilities, our Lambda functions will simply call the repository and utilities and return data.

From the src/handlers/contacts folder, create the following Lambda function files:

add.js

delete.js

get.js

list.js

update.js

Open each of the files and add the following code to them:

add.js

delete.js

get.js

list.js

update.js

Note that we are requiring the dotenv/config module first, so we can set the correct environment variables to use in the DynamoDB factory module.

DynamoDB

To set up DynamoDB locally for development, we will need to create a new folder next to our project folder to hold the DynamoDB jar and sharedDB files.

Open a terminal at the parent folder of the contacts_api project folder and run the following commands:

$ mkdir dynamodb

$ cd dynamodb

$ nano start-dynamodb.sh

from inside the nano editor, add the following lines:

#!/bin/sh jar=DynamoDBLocal.jar

lib=DynamoDBLocal_lib

dynamodir=./dynamodb-local java -Djava.library.path=$dynamodir/$lib -jar $dynamodir/$jar -sharedDb

Hit control + x and then Y , for “Yes,” to save and exit.

Start DynamoDB locally by running the command:

# from the dynamodb folder $ sh start-dynamodb.sh

Seeding contacts table

Head back to the contacts_api project folder and add a new folder called seed then add the following files to the seed folder:

contact.seeder.js

contacts-test-json

runner.js

Open the contact.seeder.js file and add the following code:

contact.seeder.js

This ContactSeeder class takes a DynamoDB and DocumentClient and uses them to create a new contacts table and seed data into it.

You can find out more about DynamoDB and DocumentClient APIs on the AWS documentation portal.

Open the contacts-test-data.json file and add in some entries to seed the contacts table with.

contacts-test-data.json

Finally, open the runner.js file and add in the code to execute the seeder.

runner.js

Make sure that DynamoDB is running locally (see DynamoDB section above) and open a new terminal at the root of the project folder and run the runner.js file to create the contacts table and seed it with data.

$ node ./seed/runner.js # output from runner: >> Checking if ‘contacts’ table exists

>> Table ‘contacts’ exists, deleting

>> Creating ‘contacts’ table

>> Seeding data

>> Done!

Putting it all together

Now that we have everything set up, we can put it all together and run the API locally via the serverless-offline plugin of the Serverless Framework.

Open the package.json file and add in some npm scripts so we can run our API using npm run <command> rather than typing out the full commands.

{

“name”: “contacts-api”

“scripts” : {

“sls”: “serverless”,

“seed”: “node ./seed/runner.js”,

“start”: “sls offline start”,

}

}

The sls script is a quick way to call the serverless cli without having to type out the full path to ./node_modules/.bin/sls before running commands, this only applies if you do not have serverless installed globally.

To fully test everything, we will need to have DynamoDB running locally in one terminal and the API running from another terminal.

# from first terminal in the ./dynamodb folder where the jar file is

# start DynamoDB locally $ sh start-dynamodb.sh # from second terminal in the project root (where package.json is)

# start the api in offline mode $ npm run start # output from npm run start > contacts-api@1.0.0 start ./contact_list/contacts_api

> sls offline start Serverless: Starting Offline: dev/us-west-2. Serverless: Routes for list:

Serverless: GET /contacts Serverless: Routes for get:

Serverless: GET /contact/{id} Serverless: Routes for add:

Serverless: POST /contact Serverless: Routes for update:

Serverless: PUT /contact/{id} Serverless: Routes for delete:

Serverless: DELETE /contact/{id} Serverless: Offline listening on http://localhost:3000

Start Postman and hit the endpoint for listing all of the contacts (http://localhost:3000/contacts/) and you should get the two items we seeded in the database.