DEV Community 👩‍💻👨‍💻

Cover image for Create and Deploy 'Stages' of a REST API using API Gateway and Lambda with Serverless
Kaye Alvarado for AWS Community Builders

Posted on

Create and Deploy 'Stages' of a REST API using API Gateway and Lambda with Serverless

API (Application Programming Interface) is a technology that allows two systems to talk to each other based on an agreed protocol. It sits between a client (consumer) and the backend (provider).

This series explores the AWS services that provides this functionality from development up to deployment. Two major services are highlighted for this purpose:

  1. API Gateway
  2. Lambda

Both services are serverless meaning developers can focus on developing code and not think about maintaining the underlying infrastructure.

Note: This demo extends this YouTube tutorial of Complete Coding by further exploring deployment with stages. Stages allows for multiple deployments of an API, which can be used in various use cases such as deploying a development and production version of an API in the same AWS account.

Prerequisites

  1. npm and node - npm is a package manager for JavaScript while NodeJS is a JavaScript runtime environment. Both are heavily used throughout the demo.
  2. AWS account and aws-cli - the services will be deployed in an AWS account using aws-cli (the command line interface for interacting with AWS).
  3. Serverless - serverless framework will be used to deploy the code to AWS.

Creating a TypeScript Project

We will be developing the sample API in TypeScript. Typescript is a strongly typed programming language that builds on JavaScript with the benefit of catching mistakes early and provides a more efficient JavaScript development experience.

To create the project, we will use a boilerplate code of serverless by running the code below.

#Create a TypeScript project using a template
serverless create --template aws-nodejs-typescript --path serverless-api-demo

✔ Project successfully created in "serverless-api-demo" from "aws-nodejs-typescript" template (13s)
Enter fullscreen mode Exit fullscreen mode

In serverless projects, there is a file in the root folder named serverless which is a configuration file that tells details of the cloud services being deployed and how they will be deployed. Since the template used is in TypeScript, the serverless file created will also be in TypeScript.

I personally find yaml files easier to read for configuration files, so I've replaced the ts file with it's yml equivalent.

service: serverless-api-demo
frameworkVersion: '3'

plugins:
  - serverless-esbuild

provider:
  name: aws
  runtime: nodejs14.x
  apiGateway:
    minimumCompressionSize: 1024
    shouldStartNameWithService: true
  environment:
    AWS_NODEJS_CONNECTION_REUSE_ENABLED: '1'
    NODE_OPTIONS: '--enable-source-maps --stack-trace-limit=1000'

functions:
  - hello
Enter fullscreen mode Exit fullscreen mode

You'll notice that it has been created with a service name (serverless-api-demo). Custom section is not included here but it is a section that allows you to create custom variables. Plugins is a list of plugins that you need for the project. Provider section is the configuration for the cloud provider where the services will be deployed. There is also a section for functions, where you can add the function handlers configurations of your code.

In the tsconfig.json file, make sure you're also updating the included file from serverless.ts to serverless.yml.

 "include": ["src/**/*.ts", "serverless.yml"]
Enter fullscreen mode Exit fullscreen mode

Adding a Function

Now let's add a simple function. Functions in AWS is a Lambda concept that you can invoke with a trigger to run your code. Under /src/functions folder, create a folder named person and create a file named getpersondata.ts. This file will contain a function that will accept a name parameter and respond with the personal information of that person from a static data.

Let's copy the contents of handler.ts from the template and add it to this file. Now, first thing to do, let's add the schema (type) of the static data object. For simplicity, we'll limit the details to fullname and nickname.

interface PersonData {
    fullname: string;
    nickname: string;
}
Enter fullscreen mode Exit fullscreen mode

Next, let's create the static data

const persondata: { [key: string]: PersonData} = {
    karina:{
        fullname: 'Karina Alvarado',
        nickname: 'Kaye'
    },
    sarita:{
        fullname: 'Sarita De Jesus',
        nickname: 'Es'
    }
}
Enter fullscreen mode Exit fullscreen mode

Let's also define the possible responses of the function.

const serviceResponses = {
    Successful: (body: { [key :string]: any }) => {
        return {
            statusCode: 200,
            body: JSON.stringify( body, null, 2)
        }
    },
    BadRequest: (body: { [key :string]: any }) => {
        return {
            statusCode: 200,
            body: JSON.stringify(body, null, 2)
        }
    },
}
Enter fullscreen mode Exit fullscreen mode

Lastly, let's edit the existing function code. Let's rename hello function name to getpersoninfohandler. This function will still receive an event like before, and will return back a JSON object. Update the function with the following code:

const name = event.pathParameters?.name;

  if(!name|| !persondata[name]){
    return serviceResponses.BadRequest({ message: 'Personal Data Not Found!'});
  }

  return serviceResponses.Successful(persondata[name]);
Enter fullscreen mode Exit fullscreen mode

Notice that the function handles two scenarios, when it didn't receive a name parameter or the name is not found in our static data, it will respond back with a 'Personal Data Not Found!' message, and will respond with the person's information if found. Make sure you add export on the handler const for other files to access the code in this file.

Go back to the serverless.yml definition and replace the functions section with the following code:

functions:
  getpersondata:
    handler: 'src/functions/person/getpersondata.getpersoninfohandler'
    events:
      - http:
          path: /name/{name}
          method: GET
          cors: true
Enter fullscreen mode Exit fullscreen mode

This configuration exposes a path to accept a name parameter, and links it back to our code handler.

Deploying the API to AWS

Before we can deploy our code to AWS, we need to create a user that has programmatic access to run aws-cli to our AWS account. To do this, follow the steps below:

  1. In the AWS Dashboard, go to the IAM service, and under Users, click on Add users.
  2. Add a name to the user and select Access key - Programmatic access under AWS credential type. This will allow this user to run aws-cli commands.
  3. As a security best practice, you should only attach a permission policy based on what resources this user will run (least user privilege). You can add specific policies for API Gateway and Lambda since these are the services that the user will be creating.
  4. All other parts of the Add user wizard can remain as default. The screen will then provide you with an AWS Access Key and Secret Access Key.
  5. In order to configure your local machine to use this user, run the aws configure command in your terminal window and input the access keys. You can now run commands in aws-cli!

Going back to the project, run npm install to ensure that you have the required dependencies in your local machine to provision the resources in AWS. Then run the deployment using serverless:

$ sls deploy
Running "serverless" from node_modules

Deploying serverless-api-demo to stage dev (us-east-1)

✔ Service deployed to stack serverless-api-demo-dev (67s)

endpoint: GET - https://dirvt4u9p5.execute-api.us-east-1.amazonaws.com/dev/name/{name}
functions:
  getpersondata: serverless-api-demo-dev-getpersondata (966 B)

Want to ditch CloudWatch? Try our new console: run "serverless --console"
Enter fullscreen mode Exit fullscreen mode

The API is now deployed!

Testing the API with Postman

You should notice that serverless prints out the created endpoint to the screen. By default, serverless also deploys to the dev stage and the API endpoint should have this in the path as well.

You can copy this endpoint in Postman and replace {name} with a value from our static data. You should see the response as a JSON object with the person data tied to the name key.

Image description

Deploying stages of an API

If you want to change a function of an API and don't want to touch the current deployed version, you can make use of stages. In your serverless.yml code, add the custom section below:

custom:
  stage: ${opt:stage, 'dev'}
Enter fullscreen mode Exit fullscreen mode

Then, in the provider section, add a reference to this parameter

stage: ${self:custom.stage}
Enter fullscreen mode Exit fullscreen mode

The code uses dev as a default stage, which can be overriden when the user runs the deploy command with a stage parameter. Try to deploy a prod version of the API:

$ serverless deploy --stage prod
Running "serverless" from node_modules

Deploying serverless-api-demo to stage prod (us-east-1)

✔ Service deployed to stack serverless-api-demo-prod (137s)

endpoint: GET - https://sw7zan1ch6.execute-api.us-east-1.amazonaws.com/prod/name/{name}
functions:
  getpersondata: serverless-api-demo-prod-getpersondata (966 B)

Monitor all your API routes with Serverless Console: run "serverless --console"
Enter fullscreen mode Exit fullscreen mode

You should now have a new API deployed in PROD stage.

Image description

What's Next?

Below are a few more concepts in API Gateway and Lambda that you can further explore after this tutorial:

  • Authentication
  • Function Destination (Lambda to trigger an AWS Service)
  • Adding a pipeline to deploy API stages

Top comments (0)

Hey 😍

Want to help the DEV Community feel more like a community?

Head over to the Welcome Thread and greet some new community members!

It only takes a minute of your time, and goes a long way!