4. Amazon API Gateway

Amazon API Gateway is a very flexible cloud service, mostly used to create a REST API powered by a lambda function. I am going to take you through the instructions on how to create a simple HTTP GET endpoint with the help of the Serverless Framework, and how we can return a response in JSON.

Although the following instruction are simple, API Gateway is a very powerful tool that can handle things such as:

  • Serverless Websockets
  • The Validation of input data in accordance with its own JSON schema
  • The generation of Swagger documentation
  • Authorization with an API key
  • HTTP request and response monitoring
  • Authorization controlled by its own lambda function
  • The settings of different kinds of limits and throttling requirements for various API keys
  • AWS REST API proxy (advanced design pattern)
    and many others…

Complete source code illustration sample:

purple-technology/serverless-cz
💜🚀 Podklady pro tutorial sérii o Serrverlessu. Contribute to purple-technology/serverless-cz development by creating an account on GitHub.

Serverless.yml

In a serverless.yml file we define a “customers” function, which processes API requests. It has to be set up in events, the so-called “http” event source, which ensures that the function is executed when receiving a GET request from the address /customers.

service: four-api-gateway

plugins:
  - serverless-offline

provider:
  name: aws
  runtime: nodejs12.x
  region: eu-central-1

functions:
  customers:
    handler: src/customers.handler
    events:
      - http:
          path: /customers
          method: get

The Handler

Every lambda function must have a “handler” function, which is called each time the handler function is invoked. In the case of JavaScript; the handler can take two forms – a promise (async/await) – the same as in our example, or the classic callback (for further information click here).

'use strict'

module.exports.handler = async (event) => {
  console.log('Načítám klienty z databáze...')
  console.log(JSON.stringify(event, null, 2))

  return {
    statusCode: 200,
    headers: {
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      customers: [
        {
          id: 1,
          name: 'Karel'
        },
        {
          id: 2,
          name: 'Pepa'
        },
        {
          id: 3,
          name: 'Janek'
        }
      ]
    })
  }
}

The response structure

  • statusCode – the HTTP status code of a response
  • headers – HTTP header we want to send in the response
  • body – the body of the HTTP response, which must always be a string, therefore we use JSON.stringify

Here is an example of the contents of the API Gateway input event

{
  "resource": "/customers",
  "path": "/customers",
  "httpMethod": "GET",
  "headers": {
    "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9",
    "Accept-Encoding": "gzip, deflate, br",
    "Accept-Language": "en-GB,en-US;q=0.9,en;q=0.8",
    "CloudFront-Forwarded-Proto": "https",
    "CloudFront-Is-Desktop-Viewer": "true",
    "CloudFront-Is-Mobile-Viewer": "false",
    "CloudFront-Is-SmartTV-Viewer": "false",
    "CloudFront-Is-Tablet-Viewer": "false",
    "CloudFront-Viewer-Country": "CZ",
    "Cookie": "ajs_user_id=null",
    "Host": "96g84zqz75.execute-api.eu-central-1.amazonaws.com",
    "sec-fetch-dest": "document",
    "sec-fetch-mode": "navigate",
    "sec-fetch-site": "none",
    "sec-fetch-user": "?1",
    "upgrade-insecure-requests": "1",
    "User-Agent": "......",
    "Via": "2.0 7da8d24daaa6257fb28a90cd4a3bbe5d.cloudfront.net (CloudFront)",
    "X-Amz-Cf-Id": "cALrq-Aso-SFccHhkGlqespelPm3PnDWOFLo7NhnJk5Cx-m0jliUdg==",
    "X-Amzn-Trace-Id": "Root=1-5f08d25f-3851b6fdbb7c12f7da82c599",
    "X-Forwarded-For": "88.112.141.53, 130.176.34.83",
    "X-Forwarded-Port": "443",
    "X-Forwarded-Proto": "https"
  },
  "multiValueHeaders": {
    "Accept": [
      "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9"
    ],
    "Accept-Encoding": ["gzip, deflate, br"],
    "Accept-Language": ["en-GB,en-US;q=0.9,en;q=0.8"],
    "CloudFront-Forwarded-Proto": ["https"],
    "CloudFront-Is-Desktop-Viewer": ["true"],
    "CloudFront-Is-Mobile-Viewer": ["false"],
    "CloudFront-Is-SmartTV-Viewer": ["false"],
    "CloudFront-Is-Tablet-Viewer": ["false"],
    "CloudFront-Viewer-Country": ["CZ"],
    "Cookie": ["ajs_user_id=null"],
    "Host": ["96g84zqz75.execute-api.eu-central-1.amazonaws.com"],
    "sec-fetch-dest": ["document"],
    "sec-fetch-mode": ["navigate"],
    "sec-fetch-site": ["none"],
    "sec-fetch-user": ["?1"],
    "upgrade-insecure-requests": ["1"],
    "User-Agent": [
      "......"
    ],
    "Via": ["2.0 7da8d24baay6657fb28a90cd4a3bbe5d.cloudfront.net (CloudFront)"],
    "X-Amz-Cf-Id": ["cALrq-Aso-SFtcHhkGlqespelPm5PnDWOFLo7NhnKk5Cx-m0jliUdg=="],
    "X-Amzn-Trace-Id": ["Root=1-5f08d25f-1851b6fdbb7c12f7da02w199"],
    "X-Forwarded-For": ["85.212.151.53, 120.876.44.13"],
    "X-Forwarded-Port": ["443"],
    "X-Forwarded-Proto": ["https"]
  },
  "queryStringParameters": null,
  "multiValueQueryStringParameters": null,
  "pathParameters": null,
  "stageVariables": null,
  "requestContext": {
    "resourceId": "n0mk11",
    "resourcePath": "/customers",
    "httpMethod": "GET",
    "extendedRequestId": "PeXO5H6DFiAFgxw=",
    "requestTime": "10/Jul/2020:20:41:03 +0000",
    "path": "/dev/customers",
    "accountId": ".......",
    "protocol": "HTTP/1.1",
    "stage": "dev",
    "domainPrefix": "96g8b1h47j",
    "requestTimeEpoch": 1594413663007,
    "requestId": "77691ybt-n351-4cde-9db7-f8b5f64e0137",
    "identity": {
      "cognitoIdentityPoolId": null,
      "accountId": null,
      "cognitoIdentityId": null,
      "caller": null,
      "sourceIp": "81.412.743.14",
      "principalOrgId": null,
      "accessKey": null,
      "cognitoAuthenticationType": null,
      "cognitoAuthenticationProvider": null,
      "userArn": null,
      "userAgent": "......",
      "user": null
    },
    "domainName": "96g8b1h47j.execute-api.eu-central-1.amazonaws.com",
    "apiId": "96g8b1h47j"
  },
  "body": null,
  "isBase64Encoded": false
}

Local development

serverless-offline

$ serverless offline

is a popular plugin for the Serverless Framework, which enables local development of the REST API.

Installation: $ npm install serverless-offline --save-dev

In the sample repository, the local server is started by:
$ npm run dev

$ npm run dev

> 4-api-gateway@1.0.0 dev ......./4-api-gateway
> serverless offline

Serverless: Starting Offline: dev/eu-central-1.

Serverless: Routes for customers:
Serverless: GET /customers
Serverless: POST /{apiVersion}/functions/four-api-gateway-dev-customers/invocations

Serverless: Offline [HTTP] listening on http://localhost:3000
Serverless: Enter "rp" to replay the last request

Once running, you should see that the local API test request now works 🎉

$ curl http://localhost:3000/customers

{"customers":[{"id":1,"name":"Karel"},{"id":2,"name":"Pepa"},{"id":3,"name":"Janek"}]}   

Deployment on the AWS

$ serverless deploy

In the sample repository, deployment to AWS is done by the command:
$ npm run deploy

$ npm run deploy

> 4-api-gateway@1.0.0 deploy ......./4-api-gateway
> serverless deploy

Serverless: Packaging service...
Serverless: Excluding development dependencies...
Serverless: Creating Stack...
Serverless: Checking Stack create progress...........
Serverless: Stack create finished...
Serverless: Uploading CloudFormation file to S3...
Serverless: Uploading artifacts...
Serverless: Uploading service four-api-gateway.zip file to S3 (91.23 KB)...
Serverless: Validating template...
Serverless: Updating Stack...
Serverless: Checking Stack update progress...................................
Serverless: Stack update finished...
Service Information
service: four-api-gateway
stage: dev
region: eu-central-1
stack: four-api-gateway-dev
resources: 11
api keys:
  None
endpoints:
  GET - https://n7rex1gz2i.execute-api.eu-central-1.amazonaws.com/dev/customers
functions:
  customers: four-api-gateway-dev-customers
layers:
  None

Again, we should see that the test request for the deployed API works 🎉

$ curl https://n7rex1gz2i.execute-api.eu-central-1.amazonaws.com/dev/customers

{"customers":[{"id":1,"name":"Karel"},{"id":2,"name":"Pepa"},{"id":3,"name":"Janek"}]}

Logs

The logs from the “customers” Lambda are available in Amazon CloudWatch, in the log group called: /aws/lambda/four-api-gateway-dev-customers

Logs from "customers" lambda function

Viewing API details in the AWS console

The newly created API can also be seen in the AWS console. It is not recommended that any manual changes be made here, as to prevent any collisions with the Serverless Framework.

Information about the deployed application

$ serverless info -v

If you want to go back, for example, to the URL of the “customers” endpoint, the only thing you need to do, in the case of the sample repository, is to run:
$ npm run info

$ npm run info

> 4-api-gateway@1.0.0 info ......./4-api-gateway
> serverless info -v

Service Information
service: four-api-gateway
stage: dev
region: eu-central-1
stack: four-api-gateway-dev
resources: 11
api keys:
  None
endpoints:
  GET - https://n7rex1gz2i.execute-api.eu-central-1.amazonaws.com/dev/customers
functions:
  customers: four-api-gateway-dev-customers
layers:
  None

Stack Outputs
CustomersLambdaFunctionQualifiedArn: arn:aws:lambda:eu-central-1:124515691641:function:four-api-gateway-dev-customers:3
ServiceEndpoint: https://n7rex1gz2i.execute-api.eu-central-1.amazonaws.com/dev
ServerlessDeploymentBucketName: four-api-gateway-dev-serverlessdeploymentbucket-1ujue1xbdlkvw

Removal from the AWS

$ serverless remove

The sample repository is removed from the AWS by using the command:
$ npm run remove

$ npm run remove

> 4-api-gateway@1.0.0 remove ......./4-api-gateway
> serverless remove

Serverless: Getting all objects in S3 bucket...
Serverless: Removing objects in S3 bucket...
Serverless: Removing Stack...
Serverless: Checking Stack removal progress...
...........
Serverless: Stack removal finished...

As expected, testing the request for the removed API does not work anymore  🚫 👍

$ curl https://n7rex1gz2i.execute-api.eu-central-1.amazonaws.com/dev/customers

curl: (6) Could not resolve host: n7rex1gz2i.execute-api.eu-central-1.amazonaws.com

Conclusion

As I mentioned in the introduction, API Gateway is a very powerful tool and you are surely going to see more articles about it on our blog.

In case you have any questions, feel free to contact me at Twitter @FilipPyrek.

With ❤️ made in Brno.