Create and Deploy 'Stages' of a REST API using API Gateway and Lambda with Serverless
Tags: aws, apigateway, lambda, devops
Published: 2022-10-31 15:14:48

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](https://www.youtube.com/watch?v=HhgXwKFUzT8) 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](https://www.npmjs.com/) and [node](https://nodejs.org/en/)** - 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](https://aws.amazon.com/cli/)** - the services will be deployed in an AWS account using aws-cli (the command line interface for interacting with AWS).
3. **[Serverless](https://www.serverless.com/framework/docs/getting-started)** - 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.

```bash
#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)
```
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.

```yaml
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
```
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**.

```yaml
 "include": ["src/**/*.ts", "serverless.yml"]
```
## 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**.

```typescript
interface PersonData {
    fullname: string;
    nickname: string;
}
```
Next, let's create the static data
```typescript
const persondata: { [key: string]: PersonData} = {
    karina:{
        fullname: 'Karina Alvarado',
        nickname: 'Kaye'
    },
    sarita:{
        fullname: 'Sarita De Jesus',
        nickname: 'Es'
    }
}
```
Let's also define the possible responses of the function.

```typescript
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)
        }
    },
}
```
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:

```typescript
const name = event.pathParameters?.name;

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

  return serviceResponses.Successful(persondata[name]);
```
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:

```yaml
functions:
  getpersondata:
    handler: 'src/functions/person/getpersondata.getpersoninfohandler'
    events:
      - http:
          path: /name/{name}
          method: GET
          cors: true
```
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:

```bash
$ 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"
```
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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zz8jn04uxdtnpyx672l2.png)

## 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:

```yaml
custom:
  stage: ${opt:stage, 'dev'}
```
Then, in the provider section, add a reference to this parameter

```yaml
stage: ${self:custom.stage}
```
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:

```bash
$ 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"
```
You should now have a new API deployed in PROD stage.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2tlza66v99ymzcy6rjxu.png)



## 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