GitHub - foad/lambda_boilerplate
Lambda Boilerplate
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):
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
- Get JWT Token: Use AWS CLI or Cognito SDK to authenticate and get a token
- Set Authorization Header: In Postman, add
Authorization: Bearer <your-jwt-token> - 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
-
Install dependencies:
-
Start LocalStack and deploy functions:
-
Run integration tests:
-
Stop LocalStack:
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 ARNAWS_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 productionSee .github/REMOTE_STATE_SETUP.md for detailed instructions.
Deployment Workflow
Automatic Deployments
-
Development: Push to
developbranch -
Production: Merge a PR into the
mainbranch
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:
- Go to Actions β Destroy Infrastructure in GitHub
- Click Run workflow
- Select the environment (
developmentorproduction) - Type
destroyto confirm - 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:
-
Destroy both environments with state storage option enabled
-
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
- 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
-
Setup:
# Clone and install dependencies git clone <repository-url> cd lambda_boilerplate npm install
-
Local Development:
# Start local environment npm run local:setup # Make changes to code # Re-deploy to LocalStack npm run deploy-local
-
Testing:
# Run unit tests npm test # Run integration tests (requires local environment) npm run test:integration # Generate coverage report npm run test:coverage
-
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.