Skip to content

Mini workshop that build and deploy a Reading List API using AWS Lambda, Amazon API Gateway, Cognito User Pool, and DynamoDB.

License

Notifications You must be signed in to change notification settings

zrierc/reading-list-api

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

22 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Reading List API

Tables of Contents:


Overview

Welcome to this mini workshop! Here you will learn to build and deploy Reading List API using RESTful API, AWS Lambda, Amazon API Gateway, Amazon Cognito, and Amazon DynamoDB. Please take a look the architecture below.

The content of this mini workshop may be updated and if you have questions or find issues in this mini workshop, please file them as an Issue.

Project Structure

reading-list-api/
β”œβ”€ functions/
β”œβ”€ .gitignore
β”œβ”€ architecture.png
β”œβ”€ LICENSE
β”œβ”€ README.md

Requirements

Before starting this mini workshop, the following runtime/tools must be met and configured properly.

  • Active AWS Account.
  • NodeJS v16 or latest with npm or yarn installed.
  • AWS CLI version 2.
  • API Testing Tools.

    Note
    This mini workshop will use curl to test the APIs. However, you can use your favorite API testing tools (e.g. Postman/Insomnia/Thunder Client/etc).

  • (optional) OS based on Linux.

    Note
    Build script for package lambda function code and it's dependecies require Linux/Unix shell to operate. If you are using an OS other than Linux and/or your device doesn't support Linux shell commands you can customize this build script to make sure it runs properly.

AWS Resources

Some of the services from AWS that are used in this mini workshop are as follows:


Setup Environment

πŸ’‘ TIP

If the settings/configurations are not specified in this guide, you can leave them as default or you can specify the values with your own.

DynamoDB

  1. Select AWS region.

  2. Create dynamoDB tables with following configuration:

    • Table name: reading-list-db
    • Partition key: readingID (string)
    • Sort key: userID (string)
    • Table class: DynamoDB Standard
    • Capacity mode: On-demand

Cognito

  1. Select AWS region.

  2. Create Cognito User pool with following configurations:

    i. Sign-In Experience

    • Provider type: Cognito user pool
    • Cognito user pool sign-in options: username, email
    • Multi-factor authentication: No MFA

    ii. Sign-Up Experience

    • Enable Self-registration: true
    • Allow Cognito to automatically send messages to verify and confirm: true

    iii. Messaging / Message Delivery

    • Email provider: Send email with Cognito

    iv. App Integration

    • User pool name: user-auth-reading-list-api
    • App type: Public client
    • App client name: lambda-jwt-auth
    • Authentication flow: ALLOW_CUSTOM_AUTH, ALLOW_REFRESH_TOKEN_AUTH, ALLOW_USER_PASSWORD_AUTH, ALLOW_USER_SRP_AUTH
    • (optional) Enable Cognito Hosted UI: true

      Note
      For Domain section, you can use either Cognito domain or custom domain if you have. If you choose Cognito domain, make sure it unique. For example, you can use your name for your cognito domain (e.g https://shinji-reading-list-api.auth.ap-southeast-1.amazoncognito.com).

      • Callback URLs: http://localhost

        This URL is used for testing purposes only. You can change it to your site or your API endpoint afterwards.

      • OpenID Connect scopes: Email, OpenID, Phone, Profile
      • Access token expiration: 1 day(s)
      • ID token expiration: 1 day(s)
  3. Attach Lambda trigger to Cognito user pool.

    πŸ’‘ TIP

    You can setup this after completing the Lambda section.

    • Trigger type: Authentication
    • Authentication: Pre token generation trigger
    • Lambda function: use Lambda Pre-Token Generation.

Lambda

1. Build Code

  • Clone this repository

    git clone https://github.com/zrierc/reading-list-api.git
  • Navigate to functions/

    cd functions/
  • Install required dependencies

    yarn

    or

    npm i
  • Build your lambda functions code and dependencies to zip

    yarn build

    or

    npm run build

2. Select AWS Region

3. Create IAM Role for Lambda

  • Role name: reading-list-fn-role
  • Trusted entitiy Type: AWS service
  • Use cases: Lambda
  • Permission policies:
    • AWSLambdaBasicExecutionRole (AWS Managed)
    • AmazonDynamoDBFullAccess (AWS Managed)

4. Deploy Pre-Token Generation Function

i. Create Lambda function with following configurations:

  • Function name: preTokenGenerationFn
  • Runtime: Node.js 16.x
  • Handler: cognito-auth/preTokenGeneration.handler
  • Timeout: 60s

ii. Publish functions code that you already build before.

  • Navigate to functions/ directory

    cd functions/
  • Publish code via AWS CLI

    aws lambda update-function-code \
      --function-name preTokenGenerationFn \
      --zip-file fileb://functions.zip

5. Deploy List Book Function

i. Create Lambda function with following configurations:

  • Function name: listBookFn
  • Runtime: Node.js 16.x
  • Handler: book/listBook.handler
  • Execution role: select reading-list-fn-role that you already created before.
  • Timeout: 60s
  • Environment variables:
    • TABLE_NAME: dynamoDB table name (reading-list-db).
    • COGNITO_USER_POOL_ID: Id of Amazon Cognito user pool that you already created before.
    • COGNITO_CLIENT_ID: Id of user pool app client that you already created before.

ii. Publish functions code that you already build before.

  • Navigate to functions/ directory

    cd functions/
  • Publish code via AWS CLI

    aws lambda update-function-code \
      --function-name listBookFn \
      --zip-file fileb://functions.zip

6. Deploy Get Detail Book Function

i. Create Lambda function with following configurations:

  • Function name: getBookFn
  • Runtime: Node.js 16.x
  • Handler: book/getBook.handler
  • Execution role: select reading-list-fn-role that you already created before.
  • Timeout: 60s
  • Environment variables:
    • TABLE_NAME: dynamoDB table name (reading-list-db).
    • COGNITO_USER_POOL_ID: Id of Amazon Cognito user pool that you already created before.
    • COGNITO_CLIENT_ID: Id of user pool app client that you already created before.

ii. Publish functions code that you already build before.

  • Navigate to functions/ directory

    cd functions/
  • Publish code via AWS CLI

    aws lambda update-function-code \
      --function-name getBookFn \
      --zip-file fileb://functions.zip

7. Deploy Add New Book Function

i. Create Lambda function with following configurations:

  • Function name: saveBookFn
  • Runtime: Node.js 16.x
  • Handler: book/saveBook.handler
  • Execution role: select reading-list-fn-role that you already created before.
  • Timeout: 60s
  • Environment variables:
    • TABLE_NAME: dynamoDB table name (reading-list-db).
    • COGNITO_USER_POOL_ID: Id of Amazon Cognito user pool that you already created before.
    • COGNITO_CLIENT_ID: Id of user pool app client that you already created before.

ii. Publish functions code that you already build before.

  • Navigate to functions/ directory

    cd functions/
  • Publish code via AWS CLI

    aws lambda update-function-code \
      --function-name saveBookFn \
      --zip-file fileb://functions.zip

8. Deploy Delete Book Function

i. Create Lambda function with following configurations:

  • Function name: deleteBookFn
  • Runtime: Node.js 16.x
  • Handler: book/deleteBook.handler
  • Execution role: select reading-list-fn-role that you already created before.
  • Timeout: 60s
  • Environment variables:
    • TABLE_NAME: dynamoDB table name (reading-list-db).
    • COGNITO_USER_POOL_ID: Id of Amazon Cognito user pool that you already created before.
    • COGNITO_CLIENT_ID: Id of user pool app client that you already created before.

ii. Publish functions code that you already build before.

  • Navigate to functions/ directory

    cd functions/
  • Publish code via AWS CLI

    aws lambda update-function-code \
      --function-name deleteBookFn \
      --zip-file fileb://functions.zip

πŸ˜• Are you stuck?

Check this common issues:

❌ Failed running build command
  • Make sure npm or yarn already installed on your machine.
  • Make sure zip and unzip already installed on your machine. Click here to read guide how to install zip and unzip in Linux machine.
  • Make sure your machine support Linux/Unix commands to run build script. For Windows user, you can try to install WSL 2 to run Linux on your Windows machine OR you can create custom build script using powershell/cmd. Don't forget to change build script command inside package.json.
⚠ Error when publish code to Lambda using AWS CLI
  • Make sure you code already built and function.zip in the same directory as functions/ exists.
  • Make sure you already installed AWS CLI on your machine. Click here to read more about how to install the AWS CLI.
  • Setup IAM credentials for authentication AWS CLI. Click here to read more about how to setup credentials.

    Note
    Make sure your IAM credentials for AWS CLI has AWSLambda_FullAccess policy.

API Gateway

  1. Select AWS region.

  2. Create Amazon API Gateway with following configurations:

    • API type: HTTP API
    • API name: reading-list-api
  3. Create routes:

    1. Route for list books

      • method: GET
      • path: /book
    2. Route for add new books

      • method: POST
      • path: /book
    3. Route for get detail book

      • method: GET
      • path: /book/{id}
    4. Route for delete book

      • method: DELETE
      • path: /book/{id}
  4. Attach lambda integrations:

    Note
    Make sure you enable 'Grant API Gateway permission to invoke your Lambda function' option.

    1. Integration for list books API

      • Route: GET /book
      • Integration type: Lambda function
      • Target: choose listBookFn
    2. Integration for add new books API

      • Route: POST /book
      • Integration type: Lambda function
      • Target: choose saveBookFn
    3. Integration for get detail book API

      • Route: GET /book/{id}
      • Integration type: Lambda function
      • Target: choose getBookFn
    4. Integration for delete book API

      • Route: DELETE /book
      • Integration type: Lambda function
      • Target: choose deleteBookFn
  5. Add cognito as API authorization:

    1. Create authorizer with following configurations:

      • Authorizer type: JWT
      • Name: JWTCognitoAuth
      • Issuer URL: https://cognito-idp.<aws-region>.amazonaws.com/<your_cognito_userpool_id>
      • Audience: <cognito_app_client_id_of_userpool>

      πŸ’‘ TIP

      <your_cognito_userpool_id> in Issuer URL should be same as COGNITO_USER_POOL_ID value on Lambda environment variables. While, Audience value should be same as COGNITO_CLIENT_ID value on Lambda enviroment variables.

    2. Attach authorizer for all routes.


πŸ§ͺ Testing

Step 1: Create Cognito user.

You can create via AWS CLI:

  • Create user

    aws cognito-idp sign-up \
      --client-id <cognito_app_client_id_of_userpool> \
      --username <your_username> \
      --user-attributes Name=email,Value=<your_mail> \
      --password <your_password>
  • Verify user

     aws cognito-idp admin-confirm-sign-up \
     --user-pool-id <your_cognito_userpool_id> \
      --username <your_username>

You can also create user via Amazon Cognito console. If you enable hosted UI when setup Cognito, you can create user via hosted UI.

Step 2: Test API

πŸ§ͺ⚠ Scenario

For testing, you're going to do 3 scenarios:

  1. Using random string as authorization token and without using authorization token β€” which will make the requests failed
  2. Using JWT token from Amazon Cognito which will led to successful request
  3. Test all API endpoint
  1. First scenario:

    • Open terminal

    • Execute following commands:

      without authorization token

      curl https://<your-http-api-gateway-endpoint>/book

      with random string as authorization token

      curl -H "Authorization: MY-RANDOM-STRING" https://<your-http-api-gateway-endpoint>/book
    • If you see the following response, then your cognito JWT authorizer works correctly

      { "message": "Unauthorized" }
  2. Second scenario:

    • Open terminal

    • Get JWT token from Cognito using AWS CLI

      aws cognito-idp initiate-auth \
        --auth-flow USER_PASSWORD_AUTH \
        --client-id <cognito_app_client_id_of_userpool> \
        --auth-parameters USERNAME=<your-username>,PASSWORD='<your-password>'

      If you see the following response, copy token from IdToken

      {
        "ChallengeParameters": {},
        "AuthenticationResult": {
          "AccessToken": "a1b2c3d4e5c644444555556666Y2X3Z1111",
          "ExpiresIn": 3600,
          "TokenType": "Bearer",
          "RefreshToken": "xyz654cba321dddccc1111",
          "IdToken": "a1b2c3d4e5c6aabbbcccddd"
        }
      }
  3. Test GET /book endpoint to list all books

    Execute the following command:

    curl -H 'Authorization: <paste-your-IdToken>' \
    https://<your-http-api-gateway-endpoint>/book

    If you see the following response, then it works correctly

    { "data": [] }
  4. Test POST /book endpoint to add new books

    Sample data (single):

    {
      "bookTitle": "Cosmos",
      "bookAuthor": "Carl Sagan"
    }

    Sample multiple data:

    [
      {
        "bookTitle": "Chainsaw Man, Vol. 1",
        "bookAuthor": "Tatsuki Fujimoto"
      },
      {
        "bookTitle": "Chainsaw Man, Vol. 2",
        "bookAuthor": "Tatsuki Fujimoto"
      },
      {
        "bookTitle": "Chainsaw Man, Vol. 3",
        "bookAuthor": "Tatsuki Fujimoto"
      }
    ]

    Execute the following commands:

    Send data (single)

    curl -X POST \
      https://<your-http-api-gateway-endpoint>/book \
      -H 'Authorization: <paste-your-IdToken>' \
      -H 'Content-Type: application/json' \
      -d '{"bookTitle": "Cosmos", "bookAuthor": "Carl Sagan"}'

    Send multiple data

    curl -X POST \
      https://<your-http-api-gateway-endpoint>/book \
      -H 'Authorization: <paste-your-IdToken>' \
      -H 'Content-Type: application/json' \
      -d '[{"bookTitle":"Chainsaw Man, Vol. 1","bookAuthor":"Tatsuki Fujimoto"},{"bookTitle":"Chainsaw Man, Vol. 2","bookAuthor":"Tatsuki Fujimoto"},{"bookTitle":"Chainsaw Man, Vol. 3","bookAuthor":"Tatsuki Fujimoto"}]'

    If you see the following response, then it works correctly

    { "message": "success" }

    πŸ’‘ TIP

    To make sure data already stored on database, you can check the data that you sent in DynamoDB Console. Learn more.
    If successful, the data that you sent before will exist in the DynamoDB table as items.

  5. Test GET /book/{id} endpoint to get detail book

    Get current book that available

    curl -H 'Authorization: <paste-your-IdToken>' \
    https://<your-http-api-gateway-endpoint>/book

    If you see the following response, copy id from readingID

    Note
    readingID may be different value than the example. Adjust the id with your output.

    {
      "data": [
        {
          "readingID": "abcd1234-ab12-cd23-ef45-abcdef123456",
          "userID": "john",
          "bookTitle": "Cosmos",
          "bookAuthor": "Carl Sagan",
          ...
        },
        ...
      ]
    }

    Get detail book:

    curl -X GET \
      https://<your-http-api-gateway-endpoint>/book/<readingID> \
      -H 'Authorization: <paste-your-IdToken>'

    If you see the following response, then it works correctly

    {
      "readingID": "abcd1234-ab12-cd23-ef45-abcdef123456",
      "userID": "john",
      "bookTitle": "Cosmos",
      "bookAuthor": "Carl Sagan",
      "dateAdded": 1690370601856,
      "readingStatus": "added",
      "lastUpdated": 1690370601856,
      "lastPageRead": null
      "dateFinished": null,
    }
  6. Test DELETE /book/{id} endpoint to delete book

    Get current book that available

    curl -H 'Authorization: <paste-your-IdToken>' \
    https://<your-http-api-gateway-endpoint>/book

    If you see the following response, copy id from readingID

    Note
    readingID may be different value than the example. Adjust the id with your output.

    {
      "data": [
        {
          "readingID": "abcd1234-ab12-cd23-ef45-abcdef123456",
          "userID": "john",
          "bookTitle": "Cosmos",
          "bookAuthor": "Carl Sagan",
          ...
        },
        ...
      ]
    }

    Delete book

    curl -X DELETE \
      https://<your-http-api-gateway-endpoint>/book/<readingID> \
      -H 'Authorization: <paste-your-IdToken>' \
      -H 'Content-Type: application/json'

    If you see the following response, then it works correctly

    { "message": "success" }

    πŸ’‘ TIP

    To make sure data already deleted on database, you can check the data that you sent in DynamoDB Console. Learn more.
    If successful, the data you deleted before will no longer exist in the DynamoDB table.


Clean Up Resources

This workshop uses AWS services that are mostly covered by the Free Tier allowance - ONLY if your account is less than 12 months old. For accounts passed the free tier eligibility, it may incur some costs. To minimize the cost, make sure you delete resources used in this workshop when you are finished.

Follow the sequence according to the steps to delete the AWS resources:

  1. Delete HTTP API Gateway via Amazon API Gateway console.

  2. Delete Cognito User Pool via Amazon Cognito console.

    For detail guide, please follow tutorial from official docs.

  3. Delete following Lambda functions via AWS Lambda Functions console:

    • preTokenGenerationFn
    • listBookFn
    • saveBookFn
    • getBookFn
    • deleteBookFn

    Note
    You can also delete custom IAM role for Lambda that you've created before via IAM console. Learn more.

  4. Delete DynamoDB table via DynamoDB console.

    Click here to read more how to delete tables in DynamoDB.

About

Mini workshop that build and deploy a Reading List API using AWS Lambda, Amazon API Gateway, Cognito User Pool, and DynamoDB.

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published