Configuring Serverless Backend CRUD Operations with AWS API Gateway and DynamoDB: A No-Lambda Approach

2022-12-27

Configuring Serverless Backend CRUD Operations with AWS API Gateway and DynamoDB: A No-Lambda Approach

In the world of serverless architecture, AWS Lambda often takes the spotlight. However, Lambda's not always necessary for every serverless operation. In this article, we'll explore how to configure serverless backend CRUD operations using AWS API Gateway and DynamoDB without using Lambda to run any code. We'll start with a simple example without authentication, then move on to a more complex example with JWT token authentication, and finally, we'll delve into a comprehensive example using AWS Cognito.

Simple Example: CRUD Operations without Authentication

Let's start with a basic example where we'll create a simple CRUD (Create, Read, Update, Delete) operation for a Users table in DynamoDB. We'll use API Gateway as the HTTP interface for these operations.

  1. Create a DynamoDB Table: First, create a Users table in DynamoDB with a primary key of userId.
  2. Set Up API Gateway: Next, set up a REST API in API Gateway. Create resources for each operation (GET, POST, PUT, DELETE) under a Users resource.
  3. Configure Integration Request: Configure the Integration Request to be a DynamoDB action for each operation. For example, the DynamoDB action would be GetItem for the GET operation. You'll need to set up the request templates to map the HTTP request to the DynamoDB request.
  4. Configure Integration Response: Configure the Integration Response to map the DynamoDB response back to the HTTP response.
  5. Deploy the API: Finally, deploy the API, and you'll have a serverless backend for your Users table.

If you use CloudFormation to orchestrate Infrastructure as code, here is the template for this first approach:

Resources:
  MyDynamoDBTable:
    Type: 'AWS::DynamoDB::Table'
    Properties:
      TableName: Users
      AttributeDefinitions:
        - AttributeName: userId
          AttributeType: S
      KeySchema:
        - AttributeName: userId
          KeyType: HASH
      ProvisionedThroughput:
        ReadCapacityUnits: 5
        WriteCapacityUnits: 5

  MyApi:
    Type: 'AWS::ApiGateway::RestApi'
    Properties:
      Name: UsersApi

  UsersResource:
    Type: 'AWS::ApiGateway::Resource'
    Properties:
      RestApiId: !Ref MyApi
      ParentId: !GetAtt 
        - MyApi
        - RootResourceId
      PathPart: users

  GetMethod:
    Type: 'AWS::ApiGateway::Method'
    Properties:
      RestApiId: !Ref MyApi
      ResourceId: !Ref UsersResource
      HttpMethod: GET
      AuthorizationType: NONE
      Integration:
        Type: AWS
        IntegrationHttpMethod: POST
        Uri: 
          Fn::Sub:
            - 'arn:aws:apigateway:${AWS::Region}:dynamodb:action/GetItem'
            - { AWS::Region: !Ref "AWS::Region" }
        PassthroughBehavior: WHEN_NO_TEMPLATES
        RequestTemplates:
          application/json: |
            {
              "TableName": "Users",
              "Key": {
                "userId": {
                  "S": "$input.params('userId')"
                }
              }
            }
        IntegrationResponses:
          - StatusCode: 200
            ResponseTemplates:
              application/json: |
                #set($inputRoot = $input.path('$'))
                {
                  "userId": "$inputRoot.Item.userId.S",
                  "name": "$inputRoot.Item.name.S",
                  "email": "$inputRoot.Item.email.S"
                }
      MethodResponses:
        - StatusCode: 200

JWT Auth Example: CRUD Operations with Jason Web Token Authentication

Now, let's add JWT token authentication to our API.

  1. Configure Authorizers: In API Gateway, configure an authorizer for your API. This will be a JWT authorizer; you'll need to provide the issuer URL and audience from your JWT provider.
  2. Update Resources: Update the resources in your API to use the JWT authorizer.
  3. Test the API: To test the API, you'll need to include a valid JWT token in the Authorization header of your HTTP requests.

Again, the CloudFormation Template:

Resources:
  # ... (same as above)

  JwtAuthorizer:
    Type: 'AWS::ApiGateway::Authorizer'
    Properties:
      Name: JwtAuthorizer
      RestApiId: !Ref MyApi
      Type: TOKEN
      IdentitySource: method.request.header.Authorization
      JwtConfiguration:
        issuer: YOUR_ISSUER_URL
        audience:
          - YOUR_AUDIENCE

  GetMethod:
    Type: 'AWS::ApiGateway::Method'
    Properties:
      # ... (same as above)
      AuthorizationType: CUSTOM
      AuthorizerId: !Ref JwtAuthorizer

Comprehensive Example: CRUD Operations with AWS Cognito

For a more comprehensive example, let's use AWS Cognito for authentication.

  1. Set Up Cognito: First set up a user pool in Cognito. This will be your authentication provider.
  2. Configure Authorizers: In API Gateway, configure an authorizer for your API. This will be a Cognito authorizer; you'll need to provide the user pool ARN.
  3. Update Resources: Update the resources in your API to use the Cognito authorizer.
  4. Test the API: To test the API, you'll need to sign in to Cognito and include the ID token in the Authorization header of your HTTP requests.

The CloudFormation template for this one would be:

Resources:
  # ... (same as above)

  CognitoAuthorizer:
    Type: 'AWS::ApiGateway::Authorizer'
    Properties:
      Name: CognitoAuthorizer
      RestApiId: !Ref MyApi
      Type: COGNITO_USER_POOLS
      IdentitySource: method.request.header.Authorization
      ProviderARNs:
        - YOUR_USER_POOL_ARN

  GetMethod:
    Type: 'AWS::ApiGateway::Method'
    Properties:
      # ... (same as above)
      AuthorizationType: COGNITO_USER_POOLS
      AuthorizerId: !Ref CognitoAuthorizer

*Remember to replace the placeholders with your actual values. Also, these templates only cover the GET method for simplicity. You'll need to add similar configurations for the POST, PUT, and DELETE methods.

Conclusion

In conclusion, AWS API Gateway and DynamoDB offer a powerful combination for serverless backend operations. By directly integrating these services, we can bypass the need for Lambda functions, resulting in a simpler and potentially more cost-effective solution. However, it's important to note that this approach may not be suitable for all use cases, especially those requiring complex business logic. As always, the right solution depends on the specific requirements of your project.