Building A Serverless Backend API Part 1

Oct 29, 2019

 

buiding-serverless-API-1

Before we get started…

There are a few things you should have already:

What we’ll go over

  • Creating a new Serverless project from scratch
  • Adding a DynamoDB table
  • Adding a Lambda function
  • Handling IAM Permissions
  • Adding API Gateway endpoints

Boilerplates

The Serverless boilerplate is dead simple — a 15 line javascript file (which turns into our Lambda) and an 11 line serverless.yml file. To generate them, run:

sls create --template aws-nodejs --path <NEW-FOLDER-NAME>

A few things are happening:

  • sls create: tells the Serverless CLI that we’re making a new project
  • --template: specifies which template to use. In this case, we are using the AWS template with a Node.js runtime.
  • --path: specifies the name of the new folder to create the project in

Finally, we can deploy our new serverless app using:

sls deploy

And with that, our project is live.

1

The building blocks of functionality

Our project might be live, but that doesn’t mean it does anything. Before anything else, we can add a database resource to the project. In this case, we’ll be using DynamoDB. We need to specify some additional provider options:

  • profile: This will be the name of the profile that you have on your machine. If you’re not sure what this is or how to find it, here is more information.
  • region: This is the region you want to use. In this case, we’re using us-west-2, but your region may differ.
  • stage: Can be anything, but Serverless recommends sticking to prod, dev, and QA. We’ll be using dev for this.

Your updated provider section should look like this:

provider:
  name: aws
  runtime: nodejs10.x
  profile: aws-profile
  region: us-west-2
  stage: dev

Next, we’re going to add a ‘resources’ property to the bottom of our serverless.yml. Nested within that, we’re going to add a ‘Resources’ attribute. Nested within that, we’re going to add our DynamoDB resource. Name it whatever you like - it’s just a reference in the serverless.yml file, not the actual name in AWS. You should end up with this:

resources:
 Resources:
   myDynamoDBTable:

The nested resources attributes have to do with the CloudFormation/Serverless syntax. You can find out more information here if you’re interested.

Your DynamoDB resource needs to have four attributes under it:

  • Type: This specifies what the resource is. In this case, it will be AWS::DynamoDB::Table
  • Properties: This is where you define table attributes and key schema. We’ll get into it in just a bit.
  • BillingMode: There are two types: PROVISIONED and PAY_PER_REQUEST. We’ll be using PAY_PER_REQUEST, but more information can be found here.
  • TableName: This is what your table will be named in AWS.

Table Properties

Check out the documentation for more information, but for us, there are two key things that Properties needs to have:

  • AttributeDefinitions: Includes an AttributeName and and AttributeType.
  • KeySchema: Includes the previously defined AttributeName, and a KeyType, in this case, HASH.
  • BillingMode: PAY_PER_REQUEST for our project. Refer to the AWS documentation to determine which option works best for you.
  • TableName: The name of the table to be created in AWS.

Entering all of the above should leave you with this:,

 

myDynamoDBTable:
 Type: AWS::DynamoDB::Table
 Properties:
   AttributeDefinitions:
     - AttributeName: id
       AttributeType: S
   KeySchema:
     - AttributeName: id
       KeyType: HASH
 BillingMode: PAY_PER_REQUEST
 TableName: My-Table-Name

2

Creating a Lambda

When we first made our boilerplate Serverless application, we were set up with a file called handler.js. If you check out the Lambda console, you should see this file stored there. That’s because, in our serverless.yml, we’re including it under the functions property. It should look like this:

functions:
   hello:
       handler: handler.hello

Let’s update a few things to make it clear what our app does. First, rename handler.js to api.js. That change also needs to be reflected in our serverless.yml, so update the functions property to read:

functions:
  api:
    handler: api.handler

Then within api.js, we’re going to replace everything except ‘use strict’; with:

var AWS = require('aws-sdk');
const api = {};
const TABLE_NAME = process.env.TABLE_NAME
api.handler = async event => {   let response = {     body: {},     statusCode: 200   };   return response; };


All this is doing is laying the groundwork for our API. One thing to notice though is the process.env.TABLE_NAME part. If you recall, we know the table name already, because it’s generated back in the serverless.yml file, and we can set up our Lambda to take advantage of that. Under functions.api, add a new property:

environment:
  TABLE_NAME: MY-TABLE-NAME


Now our Lambda can reference our DynamoDB table!

3

4

Making a POST request

When our Lambda is triggered it will have an event sent to it with all the request details. All we need to do is deconstruct that event and handle the information accordingly. Working from the API Gateway to the database, we’ll need to figure out three things:

  • The path of the request
  • The method of the request
  • What to do with the request

Step 1 — Path

To tackle step 1, we first need to settle on what our path will even be. For this, we’ll just use ‘/user’.
Now let’s make some logic in our handler function to check the path:

api.handler = async event => {
 let response = {body: {}, statusCode: 200};
try {    if (event.path.includes('user')) {      //method logic goes here    }  } catch (e) {    response.body = e.message;    response.statusCode = e.statusCode;  }  return response; }

 

What’s going on?

  • The default values for the response are set.
  • If the event path includes ‘user’, the logic we’ll cover in step 2 will trigger.
  • If there are any errors, the response is updated.
  • Finally, the response is returned.

Step 2 — Method

Step 2 is just step 1 with a small tweak. Instead of checking event.path, we’re checking event.httpMethod. Update api.handler with:

if (event.path.includes('user')) {
  if (event.httpMethod === 'POST') {
    // request handling goes here
  }
}

Step 3 — Handle the request

Step 3 is a little more involved — we have to now figure out what to do with the request. First, let’s update our handler function to call a handleCreateUser function and update response with the results:

if (event.httpMethod === 'POST') {
  response.body = JSON.stringify(await api.handleCreateUser(event.body));
}

 

Below api.handler make a new function called api.handleCreateUser and pass in the variable item. We’re going to have it return a promise that uses an AWS tool called Document Client that helps to abstract a lot of the work involved with integrating a database.

Here’s what our new function ends up looking like:

api.handleCreateUser = (item) => {
  return new Promise((resolve, reject) => {
    let documentClient = new AWS.DynamoDB.DocumentClient();
    let params = {
      TableName: TABLE_NAME,
      Item: item
    };
    documentClient.put(params, (err, data) => {
      if (err) reject(err);
      else resolve(data);
    });
  });
};

 

What’s going on?

  • We’re calling the “put” method on documentClient, which takes parameters, and a callback function. See the documentation for a complete list of allowed operations.
  • The parameters include the table name and the new item object from the request.
  • If there is an error, the promise is rejected and the error is returned to the handler.
  • Otherwise, the promise is resolved and the resulting data is returned to the handler.
  • The handler updates the response object accordingly and returns it.

Step 4 — IAM Permissions

The basic structure of our POST request is there, but if we try to run it now, we’re going to have some permissions issues. Currently, our Lambda doesn’t have and IAM permissions to access our database. Let’s fix that.

Back in serverless.yml, we’re going to add a new attribute, iamRoleStatement, to our provider property. It will have a few things nested under it:

  • Effect: In this case, ‘Allow’.
  • Action: What operations are affected by the Effect?
  • Resource: Which resource does this all apply to?

Run sls deploy to make the changes live!

5

6

API Gateway

Creating different API endpoints with the Serverless Framework is extremely easy. All we have to do is make the endpoint a property of the Lambda itself. All in all its just 4 lines of configuration per endpoint.

Under functions.api, add a new property called events. Events itself is an array, and can take several different types of events depending on the platform. In this case, we want the API Gateway to trigger the Lambda, so we will use the http event. Http events have a path, method, and CORS option. It should look like this:

 

events:
 - http:
     path: user
     method: POST
     cors: true

 

Rinse and repeat for all the others you need to make, and next time you deploy, the Serverless CLI will return a list of endpoints that have been generated for each path and request type.

7

Review

  • We wrote 21 additional lines in a .yml file
  • We wrote 23 additional lines in a .js file

Result

A database that’s hooked up to our API through a Lambda function.

Next

If you want to learn more, please see part two.