Skip to content

foad/lambda_boilerplate

Repository files navigation

Lambda Boilerplate

CI/CD Pipeline

A boilerplate repository that can be cloned and modified to quickly spin up nearly-free APIs using AWS Lambda.

Follow the Setup Guide for detailed instructions on using this repo to spin up an API.

πŸš€ Features

  • Serverless Architecture: Built with AWS Lambda, DynamoDB, and API Gateway
  • TypeScript: Full type safety with modern ES6+ features
  • Cost Optimized: Pay-per-use model with minimal idle costs (~$0.00-0.02/month)
  • Local Development: Complete LocalStack environment for offline development
  • Automated CI/CD: GitHub Actions with AWS OIDC authentication
  • Infrastructure as Code: Terraform for reproducible deployments
  • Comprehensive Testing: Unit tests, integration tests, and coverage reporting
  • CORS Enabled: Ready for frontend integration

πŸ“‹ API Documentation

Base URL

  • Production: https://{api-id}.execute-api.{region}.amazonaws.com/production
  • Development: https://{api-id}.execute-api.{region}.amazonaws.com/development
  • Local: http://localhost:4566/restapis/{api-id}/production/_user_request_

Authentication

All API endpoints require authentication using AWS Cognito User Pool. You need to include a valid JWT token in the Authorization header.

Authentication Header:

Authorization: Bearer <jwt-token>

Getting a JWT Token:

You can obtain a JWT token by authenticating with the Cognito User Pool using AWS SDK or AWS CLI:

# Using AWS CLI to authenticate and get tokens
aws cognito-idp initiate-auth \
  --auth-flow USER_PASSWORD_AUTH \
  --client-id <your-client-id> \
  --auth-parameters USERNAME=<username>,PASSWORD=<password>

Endpoints

Create Todo

  • Method: POST
  • Path: /todos
  • Description: Creates a new todo item for the authenticated user
  • Authentication: Required

Request Headers:

Content-Type: application/json
Authorization: Bearer <jwt-token>

Request Body:

{
  "title": "Buy groceries"
}

Success Response (201):

{
  "data": {
    "id": "123e4567-e89b-12d3-a456-426614174000",
    "title": "Buy groceries",
    "status": "pending",
    "userId": "user-123",
    "createdAt": "2024-01-15T10:30:00.000Z",
    "updatedAt": "2024-01-15T10:30:00.000Z"
  }
}

Error Response (400):

{
  "error": {
    "message": "Validation failed",
    "code": "VALIDATION_ERROR",
    "details": {
      "errors": ["Title is required"]
    }
  }
}

Error Response (401):

{
  "error": {
    "message": "Unauthorized",
    "code": "UNAUTHORIZED"
  }
}

Get All Todos

  • Method: GET
  • Path: /todos
  • Description: Retrieves all todo items for the authenticated user
  • Authentication: Required

Request Headers:

Authorization: Bearer <jwt-token>

Success Response (200):

{
  "data": [
    {
      "id": "123e4567-e89b-12d3-a456-426614174000",
      "title": "Buy groceries",
      "status": "pending",
      "userId": "user-123",
      "createdAt": "2024-01-15T10:30:00.000Z",
      "updatedAt": "2024-01-15T10:30:00.000Z"
    },
    {
      "id": "987fcdeb-51a2-43d1-9c4f-123456789abc",
      "title": "Walk the dog",
      "status": "completed",
      "userId": "user-123",
      "createdAt": "2024-01-15T09:15:00.000Z",
      "updatedAt": "2024-01-15T11:45:00.000Z"
    }
  ]
}

Empty Response (200):

{
  "data": []
}

Complete Todo

  • Method: PUT
  • Path: /todos/{id}/complete
  • Description: Marks a todo as completed for the authenticated user
  • Authentication: Required

Request Headers:

Authorization: Bearer <jwt-token>

Success Response (200):

{
  "data": {
    "id": "123e4567-e89b-12d3-a456-426614174000",
    "title": "Buy groceries",
    "status": "completed",
    "userId": "user-123",
    "createdAt": "2024-01-15T10:30:00.000Z",
    "updatedAt": "2024-01-15T12:00:00.000Z"
  }
}

Not Found Response (404):

{
  "error": {
    "message": "Todo not found",
    "code": "NOT_FOUND"
  }
}

Example Usage

Using curl

# First, get a JWT token (replace with your Cognito User Pool details)
JWT_TOKEN=$(aws cognito-idp initiate-auth \
  --auth-flow USER_PASSWORD_AUTH \
  --client-id <your-client-id> \
  --auth-parameters USERNAME=<username>,PASSWORD=<password> \
  --query 'AuthenticationResult.AccessToken' \
  --output text)

# Create a todo
curl -X POST https://your-api-url/todos \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $JWT_TOKEN" \
  -d '{"title": "Learn serverless architecture"}'

# Get all todos
curl https://your-api-url/todos \
  -H "Authorization: Bearer $JWT_TOKEN"

# Complete a todo
curl -X PUT https://your-api-url/todos/123e4567-e89b-12d3-a456-426614174000/complete \
  -H "Authorization: Bearer $JWT_TOKEN"

Using JavaScript/Fetch

// Assume you have obtained a JWT token from Cognito
const jwtToken = "your-jwt-token-here";

// Create a todo
const response = await fetch("https://your-api-url/todos", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    Authorization: `Bearer ${jwtToken}`,
  },
  body: JSON.stringify({
    title: "Learn serverless architecture",
  }),
});
const newTodo = await response.json();

// Get all todos
const todosResponse = await fetch("https://your-api-url/todos", {
  headers: {
    Authorization: `Bearer ${jwtToken}`,
  },
});
const todos = await todosResponse.json();

// Complete a todo
const completeResponse = await fetch(
  `https://your-api-url/todos/${todoId}/complete`,
  {
    method: "PUT",
    headers: {
      Authorization: `Bearer ${jwtToken}`,
    },
  }
);
const completedTodo = await completeResponse.json();

Manual Testing with Postman

  1. Get JWT Token: Use AWS CLI or Cognito SDK to authenticate and get a token
  2. Set Authorization Header: In Postman, add Authorization: Bearer <your-jwt-token>
  3. Make Requests: All API calls will now include user context automatically

πŸ› οΈ Local Development with LocalStack

Prerequisites

  • Docker and Docker Compose - For running LocalStack
  • Node.js 22+ - Runtime environment
  • AWS CLI - For LocalStack interaction (optional)

Quick Start

  1. Install dependencies:

    npm install
  2. Start LocalStack and deploy functions:

    npm run local:setup
  3. Run integration tests:

    npm run test:integration
  4. Stop LocalStack:

    npm run stop-local

Available Scripts

Script Description
npm run start-local Start LocalStack and initialize DynamoDB table
npm run deploy-local Build and deploy Lambda functions to LocalStack
npm run stop-local Stop LocalStack containers
npm run local:setup Complete setup (start + deploy)
npm test Run unit tests
npm run test:integration Run integration tests against LocalStack
npm run test:smoke Run smoke tests against deployed API
npm run test:coverage Run tests with coverage report
npm run build Build TypeScript and package Lambda functions
npm run lint Run ESLint on source code

Local Environment Configuration

The local development environment uses:

  • LocalStack Endpoint: http://localhost:4566
  • DynamoDB Table: todos
  • AWS Region: eu-west-2
  • AWS Credentials: test/test (LocalStack defaults)
  • API Gateway: Auto-generated endpoint URL

Testing Your Local API

Once LocalStack is running, you can test the API:

# Get the API Gateway URL from LocalStack logs
# The URL format is: http://localhost:4566/restapis/{api-id}/production/_user_request_

# Create a todo
curl -X POST http://localhost:4566/restapis/{api-id}/production/_user_request_/todos \
  -H "Content-Type: application/json" \
  -d '{"title": "Test local development"}'

# Get all todos
curl http://localhost:4566/restapis/{api-id}/production/_user_request_/todos

πŸš€ Deployment

Initial Setup

1. AWS OIDC Configuration

Follow the comprehensive setup guide in .github/DEPLOYMENT_SETUP.md to configure:

  • AWS OIDC Identity Provider
  • IAM roles for GitHub Actions
  • Required permissions and policies

2. GitHub Repository Configuration

Required Secrets (Settings β†’ Secrets and variables β†’ Actions):

  • AWS_ROLE_ARN_DEV: Development deployment role ARN
  • AWS_ROLE_ARN_PROD: Production deployment role ARN

Required Variables:

  • AWS_REGION: AWS region for deployment (default: eu-west-2)

3. Remote State Setup

# Run the setup script for each environment
./scripts/setup-remote-state.sh development
./scripts/setup-remote-state.sh production

See .github/REMOTE_STATE_SETUP.md for detailed instructions.

Deployment Workflow

Automatic Deployments

  1. Development: Push to develop branch

  2. Production: Merge a PR into the main branch

Accessing Deployment URLs

After successful deployments, API URLs are available on the GitHub Deployments Page:

  • Visit https://github.com/foad/lambda_boilerplate/deployments
  • Click on any deployment to see the environment URL

Manual Operations

  • Destroy Infrastructure: Use the "Destroy Infrastructure" workflow in GitHub Actions
  • Manual Deploy: Trigger workflows manually from the Actions tab
Destroying Infrastructure

The "Destroy Infrastructure" workflow allows you to tear down AWS resources for a specific environment. Here's what you need to know:

What Gets Destroyed:

  • All Lambda functions
  • API Gateway REST API
  • DynamoDB tables
  • IAM roles
  • Cognito User Pool (and all user accounts)
  • CloudWatch log groups and their contents

What Gets Preserved by Default:

  • Terraform state storage (S3 bucket and DynamoDB locking table)
  • The state file itself (marked as destroyed but preserved for audit)

How to Use:

  1. Go to Actions β†’ Destroy Infrastructure in GitHub
  2. Click Run workflow
  3. Select the environment (development or production)
  4. Type destroy to confirm
  5. Optionally check "Also destroy Terraform state storage" if you want to delete the state file

Complete Cleanup:

If you want to completely remove everything including shared state storage:

  1. Destroy both environments with state storage option enabled

  2. Manually delete shared resources (only after both environments are destroyed):

    # Delete the S3 bucket
    aws s3 rb s3://terraform-state-serverless-todo-api --force
    
    # Delete the DynamoDB locking table
    aws dynamodb delete-table --table-name terraform-state-lock-serverless-todo-api

⚠️ Important Notes:

  • Each environment has its own state file - destroying development state doesn't affect production and vice-versa
  • The S3 bucket and DynamoDB table are shared between environments (only deleted in manual cleanup)
  • Without state files, Terraform can't track remaining resources
  • Always destroy development before production if doing complete cleanup

Cost Estimation

Component Idle Cost Light Usage (1000 requests/month)
API Gateway $0.00 ~$0.0035
Lambda Functions $0.00 ~$0.0001
DynamoDB $0.00-0.02 ~$0.25
Cognito User Pool $0.00 ~$0.00
CloudWatch Logs ~$0.01 ~$0.50
Total ~$0.01-0.03/month ~$0.75/month

Notes:

  • Costs scale with usage (pay-per-request model)
  • No charges for idle time on Lambda and API Gateway
  • DynamoDB uses on-demand billing
  • Free tier includes 50,000 Monthly Active Users (MAUs) for Cognito
  • Development environment has similar costs

πŸ—οΈ Architecture

High-Level Architecture

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚   API Gateway   │───▢│ Lambda Functions │───▢│    DynamoDB     β”‚
β”‚                 β”‚    β”‚                  β”‚    β”‚                 β”‚
β”‚ β€’ REST API      β”‚    β”‚ β€’ Create Todo    β”‚    β”‚ β€’ todos table   β”‚
β”‚ β€’ CORS enabled  β”‚    β”‚ β€’ Read Todos     β”‚    β”‚ β€’ Pay-per-req   β”‚
β”‚ β€’ Regional      β”‚    β”‚ β€’ Update Todo    β”‚    β”‚ β€’ Encrypted     β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

πŸ§ͺ Development & Testing

Prerequisites

  • Node.js 18+ - JavaScript runtime
  • Docker & Docker Compose - For LocalStack (local development)
  • AWS CLI - For deployment (optional for local dev)

Development Workflow

  1. Setup:

    # Clone and install dependencies
    git clone <repository-url>
    cd lambda_boilerplate
    npm install
  2. Local Development:

    # Start local environment
    npm run local:setup
    
    # Make changes to code
    # Re-deploy to LocalStack
    npm run deploy-local
  3. Testing:

    # Run unit tests
    npm test
    
    # Run integration tests (requires local environment)
    npm run test:integration
    
    # Generate coverage report
    npm run test:coverage
  4. Build:

    # Build for production
    npm run build

Debugging

Local Development Issues:

# Check LocalStack status
curl http://localhost:4566/health

# View LocalStack logs
docker-compose logs localstack

# List DynamoDB tables
aws --endpoint-url=http://localhost:4566 dynamodb list-tables

πŸ“„ License

This project is licensed under the MIT License - see the LICENSE file for details.

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors