DEV Community

Cover image for Implementing Key and Token Authentication for a NodeJS API on AWS SAM
Benson Macharia for AWS Community Builders

Posted on

Implementing Key and Token Authentication for a NodeJS API on AWS SAM


In this article, we will look at how to create a securely authenticated serverless NodeJs API leveraging on AWS API Gateway key and custom JWT token. AWS SAM (Serverless Application Model) creates secure, high-performing APIs and provides developers with a simplified development environment that allows them to solely focus on writing code without worrying about server management, infrastructure scaling, or hardware failures.

Designs

High-Level Architecture Diagram

  • The API calls from a mobile or web client are routed through an API Gateway to a Lambda function for processing. Data is persisted in a DynamoDb and logs stored on CloudWatch.

High-Level architecture diagram for a NodeJS API deployed on AWS SAM

Sequence Diagram

  • The user first creates an account and then login using their credentials i.e. username and a password to get an access token. Making use of the access token, the user is then able to create a new event, update details and view all the events available.

Sequence diagram for a NodeJS API deployed on AWS SAM

Prerequisite

  • Basic skill in NodeJs
  • AWS Account
  • AWS CLI

Follow this guide (AWS CLI) to install.

$ aws --version
aws-cli/2.7.32 Python/3.9.11 Darwin/23.0.0 exe/x86_64 prompt/off
Enter fullscreen mode Exit fullscreen mode
  • SAM CLI

Follow this guide (SAM CLI) to install.

$ sam --version
SAM CLI, version 1.73.0
Enter fullscreen mode Exit fullscreen mode

Source
You can get the full source code here.

$ git clone https://github.com/bensonmacharia/sam-node-api.git
Enter fullscreen mode Exit fullscreen mode

Let's Build

1. Environment Setup

  • Create the project folder and initialise the sam project.
$ mkdir sam-projects  && cd sam-projects
$ sam init -r nodejs18.x -d npm -n sam-node-api
Which template source would you like to use?
Choice: 1
Choose an AWS Quick Start application template
Template: 6
Would you like to enable X-Ray tracing on the function(s) in your application?  [y/N]: N
Would you like to enable monitoring using CloudWatch Application Insights? [y/N]: N
Enter fullscreen mode Exit fullscreen mode
  • We are specifying to use nodejs version 18, npm as the dependency manager for our Lambda runtime and sam-node-api as the name of our project. We have also chosen not to enable X-Ray tracing and CloudWatch Application Insights.

2. Let's do some clean up

  • Open the generated source code in your favourite IDE. In this case, vscode.
$ cd sam-node-api && code .
Enter fullscreen mode Exit fullscreen mode
  • Delete the files that we will not be using.
$ rm -rf sam-node-dev events && rm -rf __tests__
Enter fullscreen mode Exit fullscreen mode

3. Time now to edit the template.yaml file

  • In AWS SAM, template.yaml serves as the foundational configuration file for defining our serverless application. Clear the content on the template.yaml file and add the following below content.

  • AWSTemplateFormatVersion: Specifies the AWS CloudFormation template version. For SAM templates, this is usually set to '2010-09-09'.

AWSTemplateFormatVersion: 2010-09-09
Enter fullscreen mode Exit fullscreen mode
  • Transform: Specifies the macro (transform) that AWS CloudFormation uses to process the template. For SAM templates, this should be set to 'AWS::Serverless-2016-10-31'.
- AWS::Serverless-2016-10-31
Enter fullscreen mode Exit fullscreen mode
  • Description: Provides a description of the CloudFormation stack and the serverless application.
Description: >-
  sam-node-api
Enter fullscreen mode Exit fullscreen mode
  • Resources: This section defines the AWS resources that make up your serverless application, such as AWS Lambda functions, API Gateway APIs, DynamoDB tables, and more. In this case we have an API Gateway, Secrets Manager, Lambda functions and DynamoDB as below.

API Gateway

Resources:
  # Create an API Gateway and add an API Key
  ApiGatewayEndpoint:
    Type: 'AWS::Serverless::Api'
    Properties:
      StageName: Prod
      Auth:
        # Require API Key for all endpoints
        ApiKeyRequired: true
        UsagePlan:
          CreateUsagePlan: PER_API
          UsagePlanName: GatewayAuthorization
Enter fullscreen mode Exit fullscreen mode

Secret Manager

JWTUserTokenSecret:
    Type: AWS::SecretsManager::Secret
    Properties:
      Name: JWTUserTokenSecret
      Description: "This secret has a dynamically generated secret password."
      # Generate a random string 30 charatcers long for the JWT secret
      GenerateSecretString:
        GenerateStringKey: 'jwt_secret'
        PasswordLength: 30
        ExcludeCharacters: '"@/\:;+*'''
        SecretStringTemplate: '{"secret_name": "sam-node-app-jwt-secret"}'
Enter fullscreen mode Exit fullscreen mode

Get All Events Lambda Function

# This is a Lambda function config associated with the source code: get-all-events.js for getting all the Events.
  getAlleventsFunction:
    Type: AWS::Serverless::Function
    Properties:
      Handler: src/handlers/get-all-events.getAllEventsHandler
      Runtime: nodejs18.x
      Architectures:
        - x86_64
      MemorySize: 128
      Timeout: 100
      Description: Includes a HTTP get method to get all events from a DynamoDB table.
      Policies:
        # Give Create/Read/Update/Delete Permissions to the EventsTable
        - DynamoDBCrudPolicy:
            TableName: !Ref EventsTable
        - AWSSecretsManagerGetSecretValuePolicy:
            SecretArn: !Ref JWTUserTokenSecret
      Environment:
        Variables:
          # Make table name accessible as environment variable from function code during execution
          SAMPLE_TABLE: !Ref EventsTable
      Events:
        Api:
          Type: Api
          Properties:
            RestApiId:
              Ref: ApiGatewayEndpoint
            # Expose the API through the path /v1/api/events
            Path: /v1/api/events
            Method: GET
Enter fullscreen mode Exit fullscreen mode

Get Single Event By ID Lambda Function

# This is a Lambda function config associated with the source code: get-event.js for getting a single Event by its ID
  getEventByIdFunction:
    Type: AWS::Serverless::Function
    Properties:
      Handler: src/handlers/get-event.getEventByIdHandler
      Runtime: nodejs18.x
      Architectures:
        - x86_64
      MemorySize: 128
      Timeout: 100
      Description: Includes a HTTP get method to get one event by id from a DynamoDB table.
      Policies:
        # Give Create/Read/Update/Delete Permissions to the EventsTable
        - DynamoDBCrudPolicy:
            TableName: !Ref EventsTable
        - AWSSecretsManagerGetSecretValuePolicy:
            SecretArn: !Ref JWTUserTokenSecret
      Environment:
        Variables:
          # Make table name accessible as environment variable from function code during execution
          SAMPLE_TABLE: !Ref EventsTable
      Events:
        Api:
          Type: Api
          Properties:
            RestApiId:
              Ref: ApiGatewayEndpoint
            # Expose the API through the path /v1/api/event/<id>
            Path: /v1/api/event/{id}
            Method: GET
Enter fullscreen mode Exit fullscreen mode

Create an Event Lambda Function

# This is a Lambda function config associated with the source code: put-item.js for posting an Event into the database
  addEventFunction:
    Type: AWS::Serverless::Function
    Properties:
      Handler: src/handlers/add-event.addEventHandler
      Runtime: nodejs18.x
      Architectures:
        - x86_64
      MemorySize: 128
      Timeout: 100
      Description: Includes a HTTP post method to add one event to a DynamoDB table.
      Policies:
        # Give Create/Read/Update/Delete Permissions to the EventsTable
        - DynamoDBCrudPolicy:
            TableName: !Ref EventsTable
        - AWSSecretsManagerGetSecretValuePolicy:
            SecretArn: !Ref JWTUserTokenSecret
      Environment:
        Variables:
          # Make table name accessible as environment variable from function code during execution
          SAMPLE_TABLE: !Ref EventsTable
      Events:
        Api:
          Type: Api
          Properties:
            RestApiId:
              Ref: ApiGatewayEndpoint
            # Expose the API through the path /v1/api/event
            Path: /v1/api/event
            Method: POST
Enter fullscreen mode Exit fullscreen mode

User Registration Lambda Function

# This is a Lambda function config associated with the source code: user-register.js for adding a new user record into the database
  registerUserFunction:
    Type: AWS::Serverless::Function
    Properties:
      Handler: src/handlers/user-register.registerUserHandler
      Runtime: nodejs18.x
      Architectures:
        - x86_64
      MemorySize: 128
      Timeout: 100
      Description: A HTTP post method to register a user and add record to a DynamoDB table.
      Policies:
        # Give Create/Read/Update/Delete Permissions to the UsersTable
        - DynamoDBCrudPolicy:
            TableName: !Ref UsersTable
      Environment:
        Variables:
          # Make table name accessible as environment variable from function code during execution
          USER_TABLE: !Ref UsersTable
      Events:
        Api:
          Type: Api
          Properties:
            RestApiId:
              Ref: ApiGatewayEndpoint
            # Expose the API through the path /v1/auth/user/register
            Path: /v1/auth/user/register
            Method: POST
            Auth:
              ApiKeyRequired: false
Enter fullscreen mode Exit fullscreen mode

User Login Lambda Function

# This is a Lambda function config associated with the source code: user-login.js for fetching a user record from the database
  loginUserFunction:
    Type: AWS::Serverless::Function
    Properties:
      Handler: src/handlers/user-login.loginUserHandler
      Runtime: nodejs18.x
      Architectures:
        - x86_64
      MemorySize: 128
      Timeout: 100
      Description: A HTTP post method to login a user and fetch record from a DynamoDB table.
      Policies:
        # Give Create/Read/Update/Delete Permissions to the UsersTable
        - DynamoDBCrudPolicy:
            TableName: !Ref UsersTable
        - AWSSecretsManagerGetSecretValuePolicy:
            SecretArn: !Ref JWTUserTokenSecret
      Environment:
        Variables:
          # Make table name accessible as environment variable from function code during execution
          USER_TABLE: !Ref UsersTable
      Events:
        Api:
          Type: Api
          Properties:
            RestApiId:
              Ref: ApiGatewayEndpoint
            # Expose the API through the path /v1/auth/user/login
            Path: /v1/auth/user/login
            Method: POST
            Auth:
              ApiKeyRequired: false
Enter fullscreen mode Exit fullscreen mode

Events DynamoDB Table

# DynamoDB table to store Event details
  EventsTable:
    Type: AWS::Serverless::SimpleTable
    Properties:
      PrimaryKey:
        Name: id
        Type: String
      ProvisionedThroughput:
        ReadCapacityUnits: 2
        WriteCapacityUnits: 2
Enter fullscreen mode Exit fullscreen mode

Users DynamoDB Table

 # DynamoDB table to store User details
  UsersTable:
    Type: AWS::Serverless::SimpleTable
    Properties:
      PrimaryKey:
        Name: username
        Type: String
      ProvisionedThroughput:
        ReadCapacityUnits: 2
        WriteCapacityUnits: 2
Enter fullscreen mode Exit fullscreen mode
  • Outputs: Specifies the values to be exported from the stack once it's created. Outputs can include endpoint URLs, resource IDs, or any other information you want to make accessible after the deployment.
Outputs:
  ApiGateway:
    Description: "The URL is:"
    Value: !Sub "https://${ApiGatewayEndpoint}.execute-api.${AWS::Region}.amazonaws.com/Prod/"
  ApiKey:
    Description: "You can find your API Key in the AWS console: (Put in the request HEADER as 'x-api-key')"
    Value: !Sub "https://console.aws.amazon.com/apigateway/home?region=${AWS::Region}#/api-keys/${ApiGatewayEndpointApiKey}"
Enter fullscreen mode Exit fullscreen mode

4. Edit the Environment Variables

  • Modify the env.json file to define our variables for the database names as below.
{
    "getAllEventsFunction": {
        "SAMPLE_TABLE": "EventsTable"
    },
    "getEventByIdFunction": {
        "SAMPLE_TABLE": "EventsTable"
    },
    "addEventFunction": {
        "SAMPLE_TABLE": "EventsTable"
    },
    "registerUserFunction": {
        "USER_TABLE": "UsersTable"
    },
    "loginUserFunction": {
        "USER_TABLE": "UsersTable"
    }
  }
Enter fullscreen mode Exit fullscreen mode

5. Install Dependencies

# Install API Gateway SDK
$ npm install @aws-sdk/client-api-gateway

# Install Secrets Manager SDK
$ npm install @aws-sdk/client-secrets-manager 

# Install jsonwebtoken package for generating JWT tokens
$ npm install jsonwebtoken

# Install bcryptjs package for password encryption
$ npm install bcryptjs

# Install uuid package for generating uuidv4 ids
$ npm install uuid
Enter fullscreen mode Exit fullscreen mode

6. Let's Handle User Registration

  • Create a new file user-register.mjs under sam-node-api/src/handlers
$ touch src/handlers/user-register.mjs 
Enter fullscreen mode Exit fullscreen mode
  • Add the user registration logic on user-register.mjs
// Import bcrypt for encrypting user password
import bcrypt from 'bcryptjs';
// Import uuid for generating unique user ID
import { v4 as uuidv4 } from 'uuid';

// Create a DocumentClient that represents the query to add an item
import { DynamoDBClient } from '@aws-sdk/client-dynamodb';
import { DynamoDBDocumentClient, PutCommand } from '@aws-sdk/lib-dynamodb';
const client = new DynamoDBClient({});
const ddbDocClient = DynamoDBDocumentClient.from(client);

// Get the DynamoDB table name from environment variables
const tableName = process.env.USER_TABLE;

/**
 * A HTTP post method to add one user to a DynamoDB table.
 */
export const registerUserHandler = async (event) => {
    if (event.httpMethod !== 'POST') {
        throw new Error(`postMethod only accepts POST method, you tried: ${event.httpMethod} method.`);
    }
    // All log statements are written to CloudWatch
    console.info('received:', event);

    // Get username and password from the body of the request
    const body = JSON.parse(event.body);
    // Use a random uuidv4 string as a User ID 
    const id = uuidv4();

    const username = body.username;

    // Generate a hashed password string
    const password = bcrypt.hashSync(body.password, 8);

    // Creates a new user, or replaces an old user record with a new one
    var params = {
        TableName: tableName,
        Item: { id: id, username: username, password: password }
    };

    try {
        const data = await ddbDocClient.send(new PutCommand(params));
        console.log("Success - user registered or updated", data);
    } catch (err) {
        console.log("Error", err.stack);
    }

    const responseData = {
        message: "Registration successful. Proceed to login",
        username: body.username

    }

    const response = {
        statusCode: 201,
        body: JSON.stringify(responseData)
    };

    // All log statements are written to CloudWatch
    console.info(`response from: ${event.path} statusCode: ${response.statusCode} body: ${response.body}`);
    return response;
};

Enter fullscreen mode Exit fullscreen mode

7. Handle User Login

  • Create a new file user-login.mjs under sam-node-api/src/handlers
$ touch src/handlers/user-login.mjs 
Enter fullscreen mode Exit fullscreen mode
  • Add the user login logic on user-login.mjs
// Import bcrypt for encrypting user password and comparing the password hash
import bcrypt from 'bcryptjs';
// Create a DocumentClient that represents the query to add an item
import { DynamoDBClient } from '@aws-sdk/client-dynamodb';
import { DynamoDBDocumentClient, GetCommand } from '@aws-sdk/lib-dynamodb';
import { GetSecretValueCommand, SecretsManagerClient } from "@aws-sdk/client-secrets-manager";
// Import jwt for generating the user token
import jwt from 'jsonwebtoken';
const client = new DynamoDBClient({});
const ddbDocClient = DynamoDBDocumentClient.from(client);
const clientsecret = new SecretsManagerClient();

// Get the DynamoDB table name from environment variables
const tableName = process.env.USER_TABLE;

/**
 * A HTTP post method for user login.
 */
export const loginUserHandler = async (event) => {
    if (event.httpMethod !== 'POST') {
        throw new Error(`postMethod only accepts POST method, you tried: ${event.httpMethod} method.`);
    }
    // All log statements are written to CloudWatch
    console.info('received:', event);

    // Get username and password from the body of the request
    const body = JSON.parse(event.body);
    const username = body.username;
    const password = body.password;

    // Fetch the JWT secret string from AWS Secrets Manager 
    const secret_value = await clientsecret.send(new GetSecretValueCommand({
        SecretId: "JWTUserTokenSecret",
    }));
    const jwt_secret = JSON.parse(secret_value.SecretString);

    var params = {
        TableName: tableName,
        Key: { username: username },
    };

    var token = "";
    var message = "";
    var status_code = 200;

    try {
        const data = await ddbDocClient.send(new GetCommand(params));
        var item = data.Item;
        // Comprate a hash of the received password from the request body with the password hash from the database, if they match login the user and generate a JWT token
        if (bcrypt.compareSync(password, item.password)) {
            // create JWT token
            const jwttoken = jwt.sign(
                {
                    userId: item.id,
                    userName: body.username,
                },
                jwt_secret.jwt_secret,
                { expiresIn: "1h" }
            );
            message = "Login successful.";
            token = jwttoken;
        } else {
            status_code = 403;
            message = "Wrong password. Try again";
            token = "NOT OKAY";
        }
    } catch (err) {
        console.log("Error", err);
    }

    const responseData = {
        message: message,
        user: {
            username: body.username,
            token: token
        }
    }

    const response = {
        statusCode: status_code,
        body: JSON.stringify(responseData)
    };

    // All log statements are written to CloudWatch
    console.info(`response from: ${event.path} statusCode: ${response.statusCode} body: ${response.body}`);
    return response;
};
Enter fullscreen mode Exit fullscreen mode

8. Add Events Logic

  • Rename the file put-item.mjs in sam-node-api/src/handlers to add-event.mjs
$ mv src/handlers/put-item.mjs src/handlers/add-event.mjs
Enter fullscreen mode Exit fullscreen mode
  • Edit the add-event.mjs file
// Create a DocumentClient that represents the query to add an item
import { DynamoDBClient } from '@aws-sdk/client-dynamodb';
import { DynamoDBDocumentClient, PutCommand } from '@aws-sdk/lib-dynamodb';
import { GetSecretValueCommand, SecretsManagerClient } from "@aws-sdk/client-secrets-manager";
// Import jwt for validating the user token
import jwt from 'jsonwebtoken';

const client = new DynamoDBClient({});
const ddbDocClient = DynamoDBDocumentClient.from(client);
const clientsecret = new SecretsManagerClient();

// Get the DynamoDB table name from environment variables
const tableName = process.env.SAMPLE_TABLE;

/**
 * A HTTP post method to add one Event item to a DynamoDB table.
 */
export const addEventHandler = async (event) => {
    if (event.httpMethod !== 'POST') {
        throw new Error(`postMethod only accepts POST method, you tried: ${event.httpMethod} method.`);
    }
    // All log statements are written to CloudWatch
    console.info('received:', event);

    // Get token from the authorization header
    const token = await event.headers.Authorization.split(" ")[1];
    if (!token) {
        throw new Error(`Authorization token required.`);
    }

    // Get Event id, title and description from the body of the request
    const body = JSON.parse(event.body);
    const id = body.id;
    const title = body.title;
    const description = body.description;

    // Fetch the JWT secret string from AWS Secrets Manager 
    const secret_value = await clientsecret.send(new GetSecretValueCommand({
        SecretId: "JWTUserTokenSecret",
    }));
    const jwt_secret = JSON.parse(secret_value.SecretString);

    //Check if the token is valid
    const decodedToken = jwt.verify(token, jwt_secret.jwt_secret);
    if (!decodedToken) {
        throw new Error(`Authorization token invalid or it has expired.`);
    }

    // Decode the JWT token to get user data
    const userID = decodedToken.userId;
    const userName = decodedToken.userName;

     console.info('decode-username:', userName);
     console.info('decode-userid:', userID);

    // Creates a new Event item, or replaces an old item with a new item
    var params = {
        TableName : tableName,
        Item: { id : id, title: title, description: description, added_by:  userName, added_by_id: userID}
    };

    try {
        const data = await ddbDocClient.send(new PutCommand(params));
        console.log("Success - event added successfully", data);
      } catch (err) {
        console.log("Error", err.stack);
      }

    const response = {
        statusCode: 200,
        body: JSON.stringify(body)
    };

    // All log statements are written to CloudWatch
    console.info(`response from: ${event.path} statusCode: ${response.statusCode} body: ${response.body}`);
    return response;
};
Enter fullscreen mode Exit fullscreen mode

9. View All Events Login

  • Rename the file get-all-items.mjs in sam-node-api/src/handlers to get-all-events.mjs
$ mv src/handlers/get-all-items.mjs src/handlers/get-all-events.mjs
Enter fullscreen mode Exit fullscreen mode
  • Edit the get-all-events.mjs file
import { DynamoDBClient } from '@aws-sdk/client-dynamodb';
import { DynamoDBDocumentClient, ScanCommand } from '@aws-sdk/lib-dynamodb';
import { GetSecretValueCommand, SecretsManagerClient } from "@aws-sdk/client-secrets-manager";
// Import jwt for validating the user token
import jwt from 'jsonwebtoken';

const client = new DynamoDBClient({});
const ddbDocClient = DynamoDBDocumentClient.from(client);
const clientsecret = new SecretsManagerClient();

// Get the DynamoDB table name from environment variables
const tableName = process.env.SAMPLE_TABLE;

/**
 * A HTTP get method to get all events from a DynamoDB table.
 */
export const getAllEventsHandler = async (event) => {
    if (event.httpMethod !== 'GET') {
        throw new Error(`getAllItems only accept GET method, you tried: ${event.httpMethod}`);
    }
    // All log statements are written to CloudWatch
    console.info('received:', event);

    // Get token from the authorization header
    const token = await event.headers.Authorization.split(" ")[1];
    if (!token) {
        throw new Error(`Authorization token required.`);
    }

    // Fetch the JWT secret string from AWS Secrets Manager 
    const secret_value = await clientsecret.send(new GetSecretValueCommand({
        SecretId: "JWTUserTokenSecret",
    }));
    const jwt_secret = JSON.parse(secret_value.SecretString);

    //Check if the token is valid
    const decodedToken = jwt.verify(token, jwt_secret.jwt_secret);
    if (!decodedToken) {
        throw new Error(`Authorization token invalid or it has expired.`);
    }

    // Get all items from the table (only first 1MB data, you can use `LastEvaluatedKey` to get the rest of data)
    var params = {
        TableName : tableName
    };

    try {
        const data = await ddbDocClient.send(new ScanCommand(params));
        var items = data.Items;
    } catch (err) {
        console.log("Error", err);
    }

    const response = {
        statusCode: 200,
        body: JSON.stringify(items)
    };

    // All log statements are written to CloudWatch
    console.info(`response from: ${event.path} statusCode: ${response.statusCode} body: ${response.body}`);
    return response;
}
Enter fullscreen mode Exit fullscreen mode

10. View Event By ID Login

  • Rename the file get-by-id.mjs in sam-node-api/src/handlers to get-event.mjs
$ mv src/handlers/get-by-id.mjs src/handlers/get-event.mjs
Enter fullscreen mode Exit fullscreen mode
  • Edit the get-event.mjs file
import { DynamoDBClient } from '@aws-sdk/client-dynamodb';
import { DynamoDBDocumentClient, GetCommand } from '@aws-sdk/lib-dynamodb';
import { GetSecretValueCommand, SecretsManagerClient } from "@aws-sdk/client-secrets-manager";
// Import jwt for validating the user token
import jwt from 'jsonwebtoken';

const client = new DynamoDBClient({});
const ddbDocClient = DynamoDBDocumentClient.from(client);
const clientsecret = new SecretsManagerClient();

// Get the DynamoDB table name from environment variables
const tableName = process.env.SAMPLE_TABLE;

/**
 * A HTTP get method to get one Event item by id from a DynamoDB table.
 */
export const getEventByIdHandler = async (event) => {
  if (event.httpMethod !== 'GET') {
    throw new Error(`getMethod only accept GET method, you tried: ${event.httpMethod}`);
  }
  // All log statements are written to CloudWatch
  console.info('received:', event);

  // Get the token from the authorization header
  const token = await event.headers.Authorization.split(" ")[1];
  if (!token) {
      throw new Error(`Authorization token required.`);
  }

  // Fetch the JWT secret string from AWS Secrets Manager 
  const secret_value = await clientsecret.send(new GetSecretValueCommand({
      SecretId: "JWTUserTokenSecret",
  }));
  const jwt_secret = JSON.parse(secret_value.SecretString);

  //Check if the token is valid
  const decodedToken = jwt.verify(token, jwt_secret.jwt_secret);
  if (!decodedToken) {
      throw new Error(`Authorization token invalid or it has expired.`);
  }

  // Get id from pathParameters from APIGateway because of `/{id}` at template.yaml
  const id = event.pathParameters.id;

  // Get the item from the table
  var params = {
    TableName : tableName,
    Key: { id: id },
  };

  var scode = 200;
  var bdy = "";

  try {
    const data = await ddbDocClient.send(new GetCommand(params));
    var item = data.Item;
    bdy = JSON.stringify(item);
    if (!bdy) {
      scode = 404;
      bdy = JSON.stringify("Event details not found.");
    }
  } catch (err) {
    console.log("Error", err);
    scode = 400;
    bdy = err;
  }

  const response = {
    statusCode: scode,
    body: bdy
  };

  // All log statements are written to CloudWatch
  console.info(`response from: ${event.path} statusCode: ${response.statusCode} body: ${response.body}`);
  return response;
}
Enter fullscreen mode Exit fullscreen mode

11. Build and Deploy the API

  • Build the SAM App
$ sam build
Build Succeeded

Built Artifacts  : .aws-sam/build
Built Template   : .aws-sam/build/template.yaml
Enter fullscreen mode Exit fullscreen mode
  • Deploy the SAM App
$ sam deploy --guided
Stack Name [sam-app]: sam-node-app
AWS Region [us-east-1]: us-east-1
#Shows you resources changes to be deployed and require a 'Y' to initiate deploy
Confirm changes before deploy [y/N]: y
#SAM needs permission to be able to create roles to connect to the resources in your template
Allow SAM CLI IAM role creation [Y/n]: Y
#Preserves the state of previously provisioned resources when an operation fails
Disable rollback [y/N]: N
Save arguments to configuration file [Y/n]: Y
SAM configuration file [samconfig.toml]: 
SAM configuration environment [default]: 

Deploy this changeset? [y/N]: y

CloudFormation outputs from deployed stack
---------------
Outputs                                                                                                                                                                                                           
---------------
Key                 ApiKey                                                                                                                                                                                        
Description         You can find your API Key in the AWS console: (Put in the request HEADER as 'x-api-key')                                                                                                      
Value               https://console.aws.amazon.com/apigateway/home?region=us-east-1#/api-keys/w22y9chd81                                                                                                          

Key                 ApiGateway                                                                                                                                                                                    
Description         The URL is:                                                                                                                                                                                   
Value               https://d2io9v58l4.execute-api.us-east-1.amazonaws.com/Prod/                                                                                                                                  
-----------------

Successfully created/updated stack - sam-node-app in us-east-1
Enter fullscreen mode Exit fullscreen mode

12. Test the API

  • Test user registration
$ curl -X POST --header "Content-Type: application/json" --data '{"username":"bmacharia", "password":"pAssW0rd@s3cr3t"}' https://d2io9v58l4.execute-api.us-east-1.amazonaws.com/Prod/v1/auth/user/register

HTTP/1.1 200 OK
{"message":"Registration successful. Proceed to login","username":"bmacharia"}%  
Enter fullscreen mode Exit fullscreen mode
  • Test user login
$ curl -X POST --header "Content-Type: application/json" --data '{"username":"bmacharia", "password":"pAssW0rd@s3cr3t"}' https://d2io9v58l4.execute-api.us-east-1.amazonaws.com/Prod/v1/auth/user/login

HTTP/1.1 200 OK
{"message":"Login successful.","user":{"username":"bmacharia","token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiI4NDMxMTU5Ny05YTlkLTQ2NDEtYjBjMS1mYTUzMGIzNTkwNzYiLCJ1c2VyTmFtZSI6ImJtYWNoYXJpYSIsImlhdCI6MTY5ODk2MjM2OSwiZXhwIjoxNjk4OTY1OTY5fQ.keoma2P6fdXnkrRklWX1vVoyaUXB_G06p6gvQLyOHHY"}}% 
Enter fullscreen mode Exit fullscreen mode
  • Test creating event
$ curl -X POST --header "Content-Type: application/json" --header "x-api-key: Laqoh0N6kCao2yw4SeDxCa9pwRdjxwra267VGjMH" --header "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiI4NDMxMTU5Ny05YTlkLTQ2NDEtYjBjMS1mYTUzMGIzNTkwNzYiLCJ1c2VyTmFtZSI6ImJtYWNoYXJpYSIsImlhdCI6MTY5ODk2MjM2OSwiZXhwIjoxNjk4OTY1OTY5fQ.keoma2P6fdXnkrRklWX1vVoyaUXB_G06p6gvQLyOHHY" --data '{"id":"2", "title":"Cybersecurity Summit", "description":"Downtown Hotel, 31st December 2023"}' https://d2io9v58l4.execute-api.us-east-1.amazonaws.com/Prod/v1/api/event

HTTP/1.1 200 OK
{"id":"2","title":"Cybersecurity Summit","description":"Downtown Hotel, 31st December 2023"}%   
Enter fullscreen mode Exit fullscreen mode
  • Test getting all events
$ curl --header "Content-Type: application/json" --header "x-api-key: Laqoh0N6kCao2yw4SeDxCa9pwRdjxwra267VGjMH" --header "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiI4NDMxMTU5Ny05YTlkLTQ2NDEtYjBjMS1mYTUzMGIzNTkwNzYiLCJ1c2VyTmFtZSI6ImJtYWNoYXJpYSIsImlhdCI6MTY5ODk2MjM2OSwiZXhwIjoxNjk4OTY1OTY5fQ.keoma2P6fdXnkrRklWX1vVoyaUXB_G06p6gvQLyOHHY" https://d2io9v58l4.execute-api.us-east-1.amazonaws.com/Prod/v1/api/events

HTTP/1.1 200 OK
[{"description":"Downtown Hotel, 31st December 2023","id":"2","title":"Cybersecurity Summit","added_by":"bmacharia","added_by_id":"84311597-9a9d-4641-b0c1-fa530b359076"},{"description":"Whitesands Hotel, 1st January 2024","id":"1","title":"GRC Conference","added_by":"bmacharia","added_by_id":"84311597-9a9d-4641-b0c1-fa530b359076"}]%    
Enter fullscreen mode Exit fullscreen mode
  • Get Event By ID
$ curl --header "Content-Type: application/json" --header "x-api-key: Laqoh0N6kCao2yw4SeDxCa9pwRdjxwra267VGjMH" --header "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiI4NDMxMTU5Ny05YTlkLTQ2NDEtYjBjMS1mYTUzMGIzNTkwNzYiLCJ1c2VyTmFtZSI6ImJtYWNoYXJpYSIsImlhdCI6MTY5ODk2MjM2OSwiZXhwIjoxNjk4OTY1OTY5fQ.keoma2P6fdXnkrRklWX1vVoyaUXB_G06p6gvQLyOHHY" https://d2io9v58l4.execute-api.us-east-1.amazonaws.com/Prod/v1/api/event/1

HTTP/1.1 200 OK
{"description":"Whitesands Hotel, 1st January 2024","id":"1","title":"GRC Conference","added_by":"bmacharia","added_by_id":"84311597-9a9d-4641-b0c1-fa530b359076"}%     
Enter fullscreen mode Exit fullscreen mode

13. Clean Up

  • Delete the SAM App
$ sam delete --stack-name sam-node-app
Are you sure you want to delete the stack sam-node-app in the region us-east-1 ? [y/N]: y
Are you sure you want to delete the folder sam-node-app in S3 which contains the artifacts? [y/N]: y

Deleted successfully
Enter fullscreen mode Exit fullscreen mode

Top comments (1)

Collapse
 
mean2me profile image
Emanuele Colonnelli

Much better and useful than mostly all official docs :)