Lambda and API Gateway: Serverless Architecture on AWS
Master serverless architecture on AWS with Lambda and API Gateway. Learn how to create Lambda functions in Node.js, configure REST and HTTP API Gateway, integrate DynamoDB, handle environment variables, layers, cold starts, and deploy with SAM and Serverless Framework.
Written by Francisco Zapata
Serverless architecture has transformed how we build applications in the cloud. With AWS Lambda you run code without provisioning or managing servers, and with API Gateway you expose that code as REST or HTTP APIs accessible from any client. In this guide, you will learn how to build a complete serverless API with Node.js, DynamoDB as the database, and production-ready best practices.
What Is AWS Lambda?
Lambda is a serverless compute service that runs your code in response to events. You only pay for the compute time you consume, billed per millisecond of execution. Lambda scales automatically from zero to thousands of concurrent executions without any configuration on your part.
Key Characteristics:
- Supported runtimes: Node.js, Python, Java, Go, .NET, Ruby, and custom runtimes
- Configurable memory: From 128 MB to 10,240 MB
- Maximum timeout: 15 minutes per invocation
- Temporary storage: 512 MB to 10 GB at
/tmp - Deployment package: Up to 50 MB (ZIP) or 250 MB uncompressed
Your First Lambda Function
Let's create a Lambda function in Node.js that handles HTTP requests:
// handler.js
export const handler = async (event) => {
const { httpMethod, path, body, queryStringParameters } = event;
try {
switch (httpMethod) {
case 'GET':
return {
statusCode: 200,
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*'
},
body: JSON.stringify({
message: 'Products retrieved successfully',
data: await getProducts(queryStringParameters)
})
};
case 'POST':
const newProduct = JSON.parse(body);
return {
statusCode: 201,
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*'
},
body: JSON.stringify({
message: 'Product created successfully',
data: await createProduct(newProduct)
})
};
default:
return {
statusCode: 405,
body: JSON.stringify({ message: 'Method not allowed' })
};
}
} catch (error) {
console.error('Error:', error);
return {
statusCode: 500,
body: JSON.stringify({ message: 'Internal server error' })
};
}
};
Creating the Function with AWS CLI
# Zip the code
zip -r function.zip handler.js node_modules/
# Create the Lambda function
aws lambda create-function \
--function-name my-products-api \
--runtime nodejs20.x \
--handler handler.handler \
--zip-file fileb://function.zip \
--role arn:aws:iam::123456789012:role/lambda-execution-role \
--timeout 30 \
--memory-size 256 \
--environment Variables='{
"TABLE_NAME": "products",
"REGION": "us-east-1"
}'
DynamoDB Integration
DynamoDB is the natural choice for databases in serverless architectures. It is a fully managed NoSQL database that scales automatically:
// dynamodb.js
import { DynamoDBClient } from '@aws-sdk/client-dynamodb';
import {
DynamoDBDocumentClient,
GetCommand,
PutCommand,
ScanCommand,
DeleteCommand
} from '@aws-sdk/lib-dynamodb';
const client = new DynamoDBClient({ region: process.env.REGION });
const docClient = DynamoDBDocumentClient.from(client);
const TABLE_NAME = process.env.TABLE_NAME;
export async function getProducts(params) {
const command = new ScanCommand({
TableName: TABLE_NAME,
Limit: params?.limit ? parseInt(params.limit) : 50
});
const result = await docClient.send(command);
return result.Items;
}
export async function createProduct(product) {
const item = {
id: crypto.randomUUID(),
...product,
createdAt: new Date().toISOString()
};
const command = new PutCommand({
TableName: TABLE_NAME,
Item: item
});
await docClient.send(command);
return item;
}
export async function getProductById(id) {
const command = new GetCommand({
TableName: TABLE_NAME,
Key: { id }
});
const result = await docClient.send(command);
return result.Item;
}
API Gateway: REST vs HTTP API
API Gateway offers two API types. Choosing the right one can impact both functionality and costs:
| Feature | REST API | HTTP API |
|---|---|---|
| Pricing | $3.50/million requests | $1.00/million requests |
| Latency | Higher (~10-15ms overhead) | Lower (~5ms overhead) |
| Authorization | IAM, Cognito, Lambda Auth | IAM, Cognito, JWT |
| Caching | Yes (built-in) | No |
| Transformations | Yes (mapping templates) | No |
| WebSocket | Yes | No |
| Recommended for | Complex APIs | Simple APIs, microservices |
For most use cases, HTTP API is sufficient and more cost-effective.
Creating an HTTP API with API Gateway
# Create the HTTP API
aws apigatewayv2 create-api \
--name my-products-api \
--protocol-type HTTP \
--target arn:aws:lambda:us-east-1:123456789012:function:my-products-api
# Create routes
aws apigatewayv2 create-route \
--api-id abc123def \
--route-key "GET /products"
aws apigatewayv2 create-route \
--api-id abc123def \
--route-key "POST /products"
aws apigatewayv2 create-route \
--api-id abc123def \
--route-key "GET /products/{id}"
# Grant API Gateway permission to invoke Lambda
aws lambda add-permission \
--function-name my-products-api \
--statement-id apigateway-invoke \
--action lambda:InvokeFunction \
--principal apigateway.amazonaws.com \
--source-arn "arn:aws:execute-api:us-east-1:123456789012:abc123def/*/*"
Lambda Layers
Layers let you share code and dependencies across multiple Lambda functions:
# Create the layer structure
mkdir -p nodejs/node_modules
cd nodejs
npm install @aws-sdk/client-dynamodb @aws-sdk/lib-dynamodb
cd ..
zip -r dynamodb-layer.zip nodejs/
# Publish the layer
aws lambda publish-layer-version \
--layer-name dynamodb-utils \
--zip-file fileb://dynamodb-layer.zip \
--compatible-runtimes nodejs20.x nodejs22.x
# Attach the layer to the function
aws lambda update-function-configuration \
--function-name my-products-api \
--layers arn:aws:lambda:us-east-1:123456789012:layer:dynamodb-utils:1
Environment Variables and Secrets
# Update environment variables
aws lambda update-function-configuration \
--function-name my-products-api \
--environment Variables='{
"TABLE_NAME": "products",
"REGION": "us-east-1",
"STAGE": "production",
"LOG_LEVEL": "info"
}'
For sensitive secrets, use AWS Secrets Manager or SSM Parameter Store instead of environment variables.
Cold Starts: What They Are and How to Mitigate Them
A cold start happens when Lambda must initialize a new execution environment. This adds extra latency (typically between 100ms and 1s for Node.js). Strategies to reduce them:
1. Provisioned Concurrency: Keeps pre-warmed instances ready
2. Lambda SnapStart: Reduces cold starts to sub-second (available for Java and Node.js)
3. Keep functions small: Fewer dependencies mean faster startup
4. Initialize connections outside the handler: They get reused across invocations
# Configure Provisioned Concurrency
aws lambda put-provisioned-concurrency-config \
--function-name my-products-api \
--qualifier production \
--provisioned-concurrent-executions 5
Deploying with SAM (Serverless Application Model)
AWS SAM streamlines the deployment of serverless applications with a declarative template:
# template.yaml
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: Serverless Products API
Globals:
Function:
Runtime: nodejs20.x
Timeout: 30
MemorySize: 256
Environment:
Variables:
TABLE_NAME: !Ref ProductsTable
REGION: !Ref AWS::Region
Resources:
ProductsFunction:
Type: AWS::Serverless::Function
Properties:
Handler: handler.handler
CodeUri: ./src
Events:
GetProducts:
Type: HttpApi
Properties:
Path: /products
Method: GET
CreateProduct:
Type: HttpApi
Properties:
Path: /products
Method: POST
Policies:
- DynamoDBCrudPolicy:
TableName: !Ref ProductsTable
ProductsTable:
Type: AWS::DynamoDB::Table
Properties:
TableName: products
BillingMode: PAY_PER_REQUEST
AttributeDefinitions:
- AttributeName: id
AttributeType: S
KeySchema:
- AttributeName: id
KeyType: HASH
Outputs:
ApiUrl:
Description: API URL
Value: !Sub "https://${ServerlessHttpApi}.execute-api.${AWS::Region}.amazonaws.com"
# Build and deploy with SAM
sam build
sam deploy --guided
# For subsequent deployments
sam deploy
Lambda Pricing
Lambda offers a generous free tier that never expires:
- 1 million free requests per month
- 400,000 GB-seconds of compute per month
- After that: $0.20 per million requests + $0.0000166667 per GB-second
For a function with 256 MB of memory running 200ms per invocation, you can make roughly 3.2 million free invocations per month.
Conclusion
Lambda and API Gateway enable you to build scalable APIs without managing servers. Combined with DynamoDB, you have a fully serverless architecture that scales automatically and charges only for what you use. SAM simplifies deployment and infrastructure-as-code management.
Comments (0)
Leave a comment
Be the first to comment