Intermediate

Serverless REST API

Build a complete CRUD API with API Gateway, Lambda, DynamoDB, and Cognito

Project Overview

Create a fully serverless backend API that handles CRUD operations with authentication. Perfect for mobile apps, SPAs, or any application needing a REST backend.

Difficulty: Intermediate
AWS Services: API Gateway, Lambda, DynamoDB, Cognito
Cost: Free Tier eligible

Prerequisites

  • Basic understanding of REST API concepts
  • Python or Node.js programming skills
  • Completed Serverless Contact Form project
  • Understanding of JSON and HTTP methods

Architecture

📱
Client App
Web/Mobile
🔒
Cognito
Auth
🔗
API Gateway
REST API
Lambda
CRUD
🗃
DynamoDB
NoSQL

Fully serverless - pay only for what you use

Step-by-Step Instructions

1

Create DynamoDB Table

  • Go to DynamoDB and create a new table
  • Choose an appropriate partition key (e.g., "id" or "userId")
  • Consider adding a sort key for queries
  • Start with on-demand capacity mode
  • Enable point-in-time recovery for production
2

Create Lambda Functions

  • Create separate functions: createItem, getItem, updateItem, deleteItem, listItems
  • Or create a single function that routes based on HTTP method
  • Use the AWS SDK to interact with DynamoDB
  • Return proper HTTP status codes and JSON responses
  • Attach an IAM role with DynamoDB permissions
3

Set Up API Gateway

  • Create a REST API in API Gateway
  • Create resources (e.g., /items, /items/{id})
  • Add methods: GET, POST, PUT, DELETE
  • Enable Lambda proxy integration
  • Configure request/response models (optional)
4

Configure Cognito Authentication

  • Create a Cognito User Pool
  • Configure sign-up and sign-in options
  • Create an App Client
  • Add a Cognito Authorizer in API Gateway
  • Apply authorizer to protected endpoints
5

Deploy and Configure CORS

  • Enable CORS on API Gateway resources
  • Ensure Lambda returns CORS headers
  • Deploy API to a stage (dev, prod)
  • Note your API endpoint URL
6

Test with Postman or curl

  • Test unauthenticated endpoints first
  • Get a JWT token from Cognito
  • Add Authorization header to requests
  • Test all CRUD operations
  • Verify data in DynamoDB console

Tips

  • Use Lambda Layers for shared code - Share utilities and SDK clients across functions
  • Enable DynamoDB Streams - Trigger additional processing when data changes
  • Implement pagination - Use LastEvaluatedKey for listing endpoints
  • Add request validation - Use API Gateway models to validate input

Code Examples

Lambda CRUD Handler (Python)

lambda_function.py PYTHON
import json
import boto3
import uuid
from decimal import Decimal

dynamodb = boto3.resource('dynamodb')
table = dynamodb.Table('Items')

def lambda_handler(event, context):
    http_method = event['httpMethod']

    try:
        if http_method == 'GET':
            if event.get('pathParameters'):
                # Get single item
                item_id = event['pathParameters']['id']
                response = table.get_item(Key={'id': item_id})
                return build_response(200, response.get('Item'))
            else:
                # List all items
                response = table.scan()
                return build_response(200, response.get('Items', []))

        elif http_method == 'POST':
            body = json.loads(event['body'])
            body['id'] = str(uuid.uuid4())
            table.put_item(Item=body)
            return build_response(201, body)

        elif http_method == 'PUT':
            item_id = event['pathParameters']['id']
            body = json.loads(event['body'])
            body['id'] = item_id
            table.put_item(Item=body)
            return build_response(200, body)

        elif http_method == 'DELETE':
            item_id = event['pathParameters']['id']
            table.delete_item(Key={'id': item_id})
            return build_response(200, {'message': 'Deleted'})

    except Exception as e:
        return build_response(500, {'error': str(e)})

def build_response(status_code, body):
    return {
        'statusCode': status_code,
        'headers': {
            'Content-Type': 'application/json',
            'Access-Control-Allow-Origin': '*'
        },
        'body': json.dumps(body, default=str)
    }

DynamoDB Table (AWS CLI)

Terminal Commands BASH
# Create DynamoDB table
aws dynamodb create-table \
    --table-name Items \
    --attribute-definitions AttributeName=id,AttributeType=S \
    --key-schema AttributeName=id,KeyType=HASH \
    --billing-mode PAY_PER_REQUEST

# Enable point-in-time recovery
aws dynamodb update-continuous-backups \
    --table-name Items \
    --point-in-time-recovery-specification PointInTimeRecoveryEnabled=true

IAM Policy for Lambda

lambda-dynamodb-policy.json JSON
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "dynamodb:GetItem",
                "dynamodb:PutItem",
                "dynamodb:UpdateItem",
                "dynamodb:DeleteItem",
                "dynamodb:Scan",
                "dynamodb:Query"
            ],
            "Resource": "arn:aws:dynamodb:*:*:table/Items"
        }
    ]
}

API Testing with curl

API Tests BASH
API_URL="https://abc123.execute-api.us-east-1.amazonaws.com/prod"

# Create item
curl -X POST "$API_URL/items" \
    -H "Content-Type: application/json" \
    -d '{"name": "Test Item", "price": 29.99}'

# Get all items
curl "$API_URL/items"

# Get single item
curl "$API_URL/items/123-456-789"

# Update item
curl -X PUT "$API_URL/items/123-456-789" \
    -H "Content-Type: application/json" \
    -d '{"name": "Updated Item", "price": 39.99}'

# Delete item
curl -X DELETE "$API_URL/items/123-456-789"

What You'll Learn

  • RESTful API design with API Gateway
  • Lambda function patterns for CRUD operations
  • DynamoDB data modeling and queries
  • User authentication with Cognito
  • API authorization strategies and JWT tokens