DEV Community

Cover image for Building a Serverless CRUD API with AWS Lambda, API Gateway, and DynamoDB
Muhammad Usama Saleem
Muhammad Usama Saleem

Posted on

Building a Serverless CRUD API with AWS Lambda, API Gateway, and DynamoDB

Building an API on AWS is one thing. Understanding what happens between the client request, API Gateway, Lambda, DynamoDB, IAM, and the deployment pipeline is another.

For this hands-on project, I built a complete serverless CRUD API using Amazon API Gateway, AWS Lambda, and Amazon DynamoDB.

The goal was to understand how these services work together and to build the API from the ground up rather than treating the architecture as a black box.


๐Ÿ—๏ธ What I Built

The application follows a simple serverless architecture:

Client
  โ”‚
  โ–ผ
API Gateway
  โ”‚
  โ–ผ
AWS Lambda
  โ”‚
  โ–ผ
DynamoDB
Enter fullscreen mode Exit fullscreen mode

For deployment, GitHub Actions was added to the architecture:

GitHub
  โ”‚
  โ–ผ
GitHub Actions
  โ”‚
  โ–ผ
GitHub OIDC
  โ”‚
  โ–ผ
AWS IAM
  โ”‚
  โ–ผ
AWS Lambda
Enter fullscreen mode Exit fullscreen mode

The result is a REST-style CRUD API without managing servers.


๐Ÿ› ๏ธ AWS Services Used

Service Purpose
Amazon API Gateway Expose HTTP API endpoints
AWS Lambda Run the application logic
Amazon DynamoDB Store application data
AWS IAM Manage permissions and roles
AWS STS Provide temporary credentials through role assumption
GitHub Actions Automate Lambda deployments
GitHub OIDC Authenticate GitHub Actions with AWS

๐Ÿ“Œ API Operations

The API supports the main CRUD operations:

Operation HTTP Method Purpose
Create POST Create a new user
Read GET Retrieve one or all users
Update PUT Update an existing user
Delete DELETE Delete an existing user

For example:

POST /users
GET /users
GET /users/{name}
PUT /users/{name}
DELETE /users/{name}
Enter fullscreen mode Exit fullscreen mode

The exact API Gateway routes are configured in AWS.


๐Ÿ—„๏ธ DynamoDB Design

The project uses a DynamoDB table with:

Partition Key: name
Type: String
Enter fullscreen mode Exit fullscreen mode

A user record looks conceptually like:

{
  "name": "Ali",
  "email": "ali@example.com",
  "followers": 10
}
Enter fullscreen mode Exit fullscreen mode

One important DynamoDB concept I worked through was the role of the primary key.

Because name is the partition key, it uniquely identifies the item in this table.


๐Ÿ”Ž Get One vs Get All

One of the concepts I wanted to understand properly was the difference between retrieving a specific DynamoDB item and scanning the table.

For a single user, Lambda uses a GetCommand.

For retrieving all users, Lambda uses a ScanCommand.

Conceptually:

GET /users/Ali
      โ”‚
      โ–ผ
DynamoDB GetItem
      โ”‚
      โ–ผ
Ali
Enter fullscreen mode Exit fullscreen mode

Whereas:

GET /users
      โ”‚
      โ–ผ
DynamoDB Scan
      โ”‚
      โ–ผ
All users
Enter fullscreen mode Exit fullscreen mode

This distinction became particularly useful while testing the API.


โž• Create-Only POST Requests

I didn't want a POST request to silently overwrite an existing user.

DynamoDB normally allows a PutItem operation to replace an existing item with the same primary key.

To make the endpoint behave like a create operation, I added a condition:

ConditionExpression: "attribute_not_exists(#name)"
Enter fullscreen mode Exit fullscreen mode

Because name is used as the partition key, this condition prevents an existing record from being replaced.

A duplicate request therefore returns:

HTTP 409 Conflict
Enter fullscreen mode Exit fullscreen mode

instead of overwriting the existing record.


โœ๏ธ Updating Records

The PUT endpoint uses DynamoDB's UpdateCommand.

It updates the user's fields and returns the updated item.

For example:

PUT /users/Ali
Enter fullscreen mode Exit fullscreen mode

can update values such as:

{
  "email": "new-email@example.com",
  "followers": 25
}
Enter fullscreen mode Exit fullscreen mode

The updated record is then returned by the API.


๐Ÿ—‘๏ธ Deleting Records

The DELETE endpoint first checks whether the user exists.

If the user exists, Lambda removes the DynamoDB item.

If the user does not exist, the API returns:

HTTP 404 Not Found
Enter fullscreen mode Exit fullscreen mode

This keeps the API behavior predictable for the client.


โšก Lambda

The API logic runs inside AWS Lambda.

The function handles:

  1. Reading the API Gateway request
  2. Identifying the HTTP method
  3. Reading path parameters
  4. Parsing request bodies
  5. Validating input
  6. Performing DynamoDB operations
  7. Returning HTTP responses

The Lambda function uses the AWS SDK for JavaScript.

The project uses the modular AWS SDK v3 packages:

@aws-sdk/client-dynamodb
@aws-sdk/lib-dynamodb
Enter fullscreen mode Exit fullscreen mode

๐ŸŒ API Gateway

Amazon API Gateway provides the HTTP interface for the Lambda function.

The API exposes the CRUD routes and forwards requests to Lambda.

The overall request flow is:

HTTP Request
     โ”‚
     โ–ผ
API Gateway
     โ”‚
     โ–ผ
Lambda
     โ”‚
     โ–ผ
DynamoDB
     โ”‚
     โ–ผ
Lambda Response
     โ”‚
     โ–ผ
API Gateway
     โ”‚
     โ–ผ
Client
Enter fullscreen mode Exit fullscreen mode

I tested the endpoints externally through API Gateway and Postman.


๐Ÿ” IAM Roles

There are two different IAM responsibilities in this project.

Lambda Execution Role

The Lambda execution role allows the function to interact with DynamoDB.

Conceptually:

Lambda
  โ”‚
  โ–ผ
Lambda Execution Role
  โ”‚
  โ–ผ
DynamoDB Permissions
Enter fullscreen mode Exit fullscreen mode

GitHub Deployment Role

GitHub Actions uses a separate IAM role to deploy the Lambda function.

GitHub Actions
  โ”‚
  โ–ผ
GitHub OIDC
  โ”‚
  โ–ผ
Deployment IAM Role
  โ”‚
  โ–ผ
Lambda Deployment
Enter fullscreen mode Exit fullscreen mode

Keeping these roles separate helped reinforce an important AWS principle:

The identity deploying a function does not need to be the same identity used by the function at runtime.


๐Ÿ”‘ GitHub OIDC Deployment

The Lambda deployment was automated using GitHub Actions.

Instead of storing long-lived AWS access keys inside GitHub, the workflow uses GitHub OIDC.

The flow looks like:

GitHub Actions
      โ”‚
      โ–ผ
OIDC Token
      โ”‚
      โ–ผ
AWS IAM
      โ”‚
      โ–ผ
Temporary Credentials
      โ”‚
      โ–ผ
Lambda Deployment
Enter fullscreen mode Exit fullscreen mode

This was one of the main reasons I wanted to include CI/CD in the project.


โš™๏ธ Deployment Workflow

The workflow performs the following steps:

Checkout repository
        โ”‚
        โ–ผ
Install dependencies
        โ”‚
        โ–ผ
Prepare Lambda package
        โ”‚
        โ–ผ
Authenticate with AWS
        โ”‚
        โ–ผ
Deploy Lambda
Enter fullscreen mode Exit fullscreen mode

The Lambda package contains the application code and the required dependencies.

I also separated the deployment package from the rest of the repository so files such as tests, documentation, and GitHub configuration are not unnecessarily uploaded to Lambda.


๐Ÿงช Testing

The API was tested both through API Gateway and locally.

The final CRUD flow was tested as:

GET all users
      โ†“
GET specific user
      โ†“
POST new user
      โ†“
GET new user
      โ†“
PUT user
      โ†“
GET updated user
      โ†“
DELETE user
      โ†“
GET deleted user
      โ†“
404 response
Enter fullscreen mode Exit fullscreen mode

I also tested the duplicate-create scenario.

Attempting to create an existing user returns:

409 Conflict
Enter fullscreen mode Exit fullscreen mode

which confirms that the DynamoDB condition is working as intended.


๐Ÿงฏ Some Problems I Had to Solve

The most useful part of the project was probably the troubleshooting.

Lambda Deployment Permissions

The GitHub deployment role initially did not have all the Lambda permissions required by the deployment action.

The missing permissions were identified from the deployment error and the IAM policy was updated accordingly.

Lambda Package Contents

The first deployment approach packaged the entire repository.

That meant files such as:

README.md
test.js
.github/
Enter fullscreen mode Exit fullscreen mode

could end up inside the Lambda deployment package.

I changed the workflow to create a dedicated lambda-package directory containing only the Lambda code, package metadata, and required dependencies.

ES Modules

The Lambda code uses ES module syntax.

Running the same code locally initially produced an error because Node.js was treating the project as CommonJS.

Adding the appropriate module configuration to package.json allowed the same code to run locally.

Duplicate Data

During testing, attempting to create the same user again correctly returned a 409.

This was a useful reminder that test data can affect subsequent runs and that negative test cases are just as important as successful ones.


๐Ÿ”’ Security Considerations

The project was designed without putting AWS credentials into the GitHub repository.

The main authentication approach is:

  • GitHub OIDC
  • IAM roles
  • Temporary AWS credentials
  • Separate Lambda execution and deployment roles
  • Specific AWS permissions for each role

No access keys, passwords, tokens, or other credentials are included in the repository.


๐Ÿ“ Project Structure

aws-serverless-crud-api/
โ”‚
โ”œโ”€โ”€ .github/
โ”‚   โ””โ”€โ”€ workflows/
โ”‚       โ””โ”€โ”€ deploy.yml
โ”‚
โ”œโ”€โ”€ index.js
โ”œโ”€โ”€ package.json
โ”œโ”€โ”€ package-lock.json
โ”œโ”€โ”€ test.js
โ””โ”€โ”€ README.md
Enter fullscreen mode Exit fullscreen mode

๐Ÿ’ก What I Learned

This project gave me hands-on experience with:

AWS Lambda

  • Lambda functions
  • Event handling
  • Execution roles
  • Error handling
  • Lambda deployment

API Gateway

  • HTTP APIs
  • Routes
  • Path parameters
  • HTTP status codes
  • Connecting API Gateway to Lambda

DynamoDB

  • Tables
  • Partition keys
  • GetItem
  • Scan
  • PutItem
  • UpdateItem
  • DeleteItem
  • Conditional expressions

IAM

  • Trust policies
  • Permission policies
  • Execution roles
  • Deployment roles
  • Least-privilege access

GitHub Actions

  • CI/CD workflows
  • OIDC authentication
  • AWS role assumption
  • Lambda deployment

๐ŸŽฏ Why I Built This

I wanted to go beyond simply learning what Lambda, API Gateway, and DynamoDB are.

The goal was to build something where I had to deal with the actual interactions between these services.

The project also gave me the opportunity to work through IAM permissions, deployment packaging, API error handling, DynamoDB conditions, and CI/CD.

That made the architecture much easier to understand because every part of it was actually configured and tested.


๐Ÿ”ฎ Possible Improvements

There are several directions this project could be taken next:

  • Add API authentication and authorization
  • Add request validation
  • Introduce Amazon Cognito
  • Add CloudWatch dashboards and alarms
  • Add infrastructure as code
  • Add automated API tests to the CI/CD pipeline
  • Add a frontend application
  • Introduce API versioning

These would move the project closer to a production-style serverless application.


๐Ÿ“Œ Final Architecture

                    โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
                    โ”‚     Client       โ”‚
                    โ”‚ Browser / Postmanโ”‚
                    โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                             โ”‚
                             โ–ผ
                    โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
                    โ”‚  API Gateway     โ”‚
                    โ”‚    HTTP API      โ”‚
                    โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                             โ”‚
                             โ–ผ
                    โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
                    โ”‚  AWS Lambda      โ”‚
                    โ”‚  CRUD Logic      โ”‚
                    โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                             โ”‚
                             โ–ผ
                    โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
                    โ”‚    DynamoDB      โ”‚
                    โ”‚   User Records   โ”‚
                    โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜


Deployment:

GitHub
   โ”‚
   โ–ผ
GitHub Actions
   โ”‚
   โ–ผ
GitHub OIDC
   โ”‚
   โ–ผ
AWS IAM
   โ”‚
   โ–ผ
AWS Lambda
Enter fullscreen mode Exit fullscreen mode

๐Ÿ‘จโ€๐Ÿ’ป Project

Built as part of my hands-on AWS learning journey, with a focus on understanding serverless architecture, REST APIs, DynamoDB operations, IAM, and automated AWS deployments.

Top comments (0)