
Table of contents
Open Table of contents
- TL;DR
- Introduction
- Understanding Serverless
- Prerequisites
- Step 1: Install Serverless Framework
- Step 2: Create Your First Serverless Project
- Step 3: Customise Your Express Application
- Step 4: Configure Your Service
- Step 5: Deploy to AWS
- Step 6: Test Your Deployed API
- Understanding the data flow in the architecture.
- Basic Troubleshooting
- Cleaning Up Resources
- Quick Reference
- Conclusion
- What’s Next?
TL;DR
Get started with Serverless Framework and deploy your first Express API to AWS Lambda:
# Install Serverless Framework
$ npm install -g serverless
# Create project using interactive CLI
$ serverless
# Select "AWS - Node.js - Express API" template
# Name your service (e.g., my-serverless-api)
# Navigate to project directory
$ cd my-serverless-api
# Deploy to AWS
$ serverless deploy
What you get:
- Express API running on AWS Lambda
- API Gateway endpoint automatically configured
- CloudFormation stack managing infrastructure
- Ready-to-use project structure
Introduction
Recently, I was working on one of my clients’ projects, and I was using Serverless Framework to deploy my APIs to AWS Lambda. I was very impressed with the ease of use and the power of the framework. I decided to write a series of blog posts to share my experience with the framework.
The Serverless Framework removes deployment complexity on AWS Lambda, allowing you to focus on Express code while it automates setup, deployment, and scaling.
This article shows how to set up and deploy a serverless Express API with a public AWS endpoint.
Series Progress:
- Part 1 (This article): Getting Started - Setup and basic deployment
- Part 2: Development Workflow - Local development and testing
- Part 3: Multi-Stage Deployments & CI/CD - Environment management and CI/CD
- Part 4: Production Monitoring & Security - Monitoring, logging, and security
- Part 5: Performance & Cost Optimization - Performance tuning and cost management
Understanding Serverless
New to serverless? Here’s what makes it special:
Traditional Server vs Serverless
Traditional Server:
- You rent/buy a server that runs 24/7
- You manage OS, security updates, and scaling
- You pay continuously, even when no one uses your API
- You handle server maintenance and monitoring
Serverless (AWS Lambda):
- AWS runs your code only when requests come in
- Automatic scaling from 0 to millions of requests
- You only pay for actual usage (requests + execution time)
- AWS handles all server management and maintenance
Real-World Cost Example
Traditional Server:
- Minimum cost: ~$50/month (even with 0 users)
- Fixed cost regardless of usage
- Additional costs for scaling, monitoring, and backups
Serverless:
- $0.00 with 0 users
- $0.20 for 1 million requests
- Automatic scaling included
- Built-in monitoring and logging
Architecture Overview
Here’s what we’re building in this series:
How it works:
- Client sends HTTP request (GET, POST, etc.)
- API Gateway receives request and triggers Lambda
- Lambda runs your Express.js application code
- Response flows back through API Gateway to client
- CloudFormation manages all AWS resources automatically
Benefits of this architecture:
- Automatic scaling: Handle 1 to 1M+ requests seamlessly
- Pay-per-use: Only pay when requests are actually processed
- Zero server management: AWS handles all infrastructure concerns
- Built-in reliability: AWS provides 99.95% uptime SLA
- Global deployment: Deploy to multiple regions easily
Prerequisites
Before we begin, ensure you have the following setup in place:
1. Node.js (Required)
Node.js 18.x or higher is installed on your system.
# Verify installation
$ node --version
# Should output: v18.x.x or higher
Installation: Download from nodejs.org or use nvm
Note: I will personally recommend using nvm to manage multiple Node.js versions.
2. AWS CLI (Required)
AWS Command Line Interface installed and configured.
# Verify installation
$ aws --version
# Should output: aws-cli/2.x.x or higher
Installation: Follow the AWS CLI installation guide
3. AWS Credentials (Required)
AWS credentials configured with appropriate permissions.
# Configure AWS credentials
$ aws configure
# Or use a named profile
$ aws configure --profile myproject
# Verify configuration
$ aws sts get-caller-identity
Required IAM permissions:
These are very exhaustive permissions, and you can also use the AWS Policy Generator to generate the necessary permissions, which are very specific to your use case and more granular. Refer Serverless Framework IAM policy guide for more details.
lambda:*- Lambda function managementapigateway:*- API Gateway configurationcloudformation:*- Stack managementiam:CreateRole,iam:AttachRolePolicy- IAM role creationlogs:*- CloudWatch Logs access
Step 1: Install Serverless Framework
Install the Serverless Framework globally as recommended in the official documentation:
$ npm install -g serverless
Verify installation:
$ serverless --version
# Should output: Framework Core: 4.x.x or higher
Note: You can also use
npx serverlessif you prefer not to install globally. This will still temporarily install the Serverless Framework.
Step 2: Create Your First Serverless Project
Use the Serverless Framework CLI to create a new project with the recommended template:
# Start the interactive project creation
$ serverless
This command launches an interactive setup process:
Interactive Setup Process
- Select Template: Choose “AWS - Node.js - Express API” from the available templates. You can also choose other templates if you want to.
- Service Name: Enter your service name (e.g.,
my-serverless-api) or press Enter for default - Create Or Select An Existing App: Choose “Skip Adding An App”
The CLI creates a new directory with your service name and generates all necessary files:
my-serverless-api/
├── handler.js # Lambda function with Express app
├── serverless.yml # Service configuration
├── package.json # Node.js dependencies and scripts
├── .gitignore # Git ignore rules
└── README.md # Project documentation
Navigate to your project directory and install the dependencies:
# Navigate to your project directory
$ cd my-serverless-api
# Install dependencies. I am using npm here. You can use yarn or pnpm if you want to.
$ npm install
Understanding the Generated Files
handler.js - Contains your Express application wrapped for Lambda:
const serverless = require("serverless-http");
const express = require("express");
const app = express();
app.get("/", (req, res, next) => {
return res.status(200).json({
message: "Hello from root!",
});
});
// Rest of the routes...
// ......
exports.handler = serverless(app);
serverless.yml - Defines your service configuration:
service: my-serverless-api
provider:
name: aws
runtime: nodejs20.x
region: us-east-1
functions:
api:
handler: handler.handler
events:
- httpApi: "*"
package.json - Includes essential dependencies and scripts:
{
"dependencies": {
"express": "^4.19.2",
"serverless-http": "^3.2.0"
}
}
Step 3: Customise Your Express Application
Let’s enhance the basic Express application with logging and error handling. I’m not going to add more api endpoints as they are just any express routes that you would normally add in your project.
const serverless = require("serverless-http");
const express = require("express");
const app = express();
// Middleware
app.use(express.json());
// Request logging
app.use((req, res, next) => {
console.log(`${req.method} ${req.path}`);
next();
});
// Routes
app.get("/", (req, res, next) => {
return res.status(200).json({
message: "Hello from root!",
});
});
// Rest of the routes...
// ......
// ......
// 404 handler
app.use((req, res) => {
res.status(404).json({ error: "Not Found" });
});
// Error handler
app.use((err, req, res, next) => {
console.error("Error:", err);
res.status(500).json({ error: "Internal Server Error" });
});
// Export handler for Lambda
exports.handler = serverless(app);
Key features added:
- Request logging middleware
- Error handling middleware
Step 4: Configure Your Service
Let’s enhance the basic serverless.yml configuration for better deployment control:
service: my-serverless-api
provider:
name: aws
runtime: nodejs20.x
region: us-east-1
stage: ${opt:stage, 'dev'}
memorySize: 512
timeout: 30
functions:
api:
handler: handler.handler
events:
- httpApi: "*"
Configuration breakdown:
service- Unique identifier for your serverless applicationprovider.runtime- Node.js version (20.x is latest LTS)provider.region- AWS region for deploymentprovider.stage- Environment stage (dev, staging, production) - Dynamic variable that defaults to ‘dev’ if not specifiedprovider.memorySize- Memory allocation for the Lambda function (512MB)provider.timeout- Maximum execution time for the Lambda function (30 seconds)functions.api.handler- Path to Lambda handler functionfunctions.api.events- HTTP API event with wildcard routing
Step 5: Deploy to AWS
Deploy your serverless API to AWS Lambda:
$ serverless deploy
Deployment process:
- Code is packaged and zipped
- CloudFormation stack is created/updated
- Lambda function is deployed
- HTTP API Gateway is configured
- IAM roles are set up
- API endpoint URL is returned
Expected output:
Deploying "my-serverless-api" to stage "dev" (us-east-1)
✔ Service deployed to stack my-serverless-api-dev (112s)
endpoint: ANY - https://abc123xyz.execute-api.us-east-1.amazonaws.com
functions:
api: my-serverless-api-dev-api (1.5 MB)
Step 6: Test Your Deployed API
Test your API endpoints using the provided URL:
# Save endpoint URL from deployment output
ENDPOINT="https://abc123xyz.execute-api.us-east-1.amazonaws.com"
# Test root endpoint
curl $ENDPOINT/
# Test other endpoints with similar commands by changing the path.
Expected responses:
Root endpoint:
{
"message": "Hello from root!"
}
Understanding the data flow in the architecture.
Your deployed application follows this architecture. This is the same architecture that you will use for your production deployment.
Detailed Request Flow:
- Client makes an HTTP request to the API Gateway endpoint
- API Gateway receives the request and triggers the Lambda function
- Lambda receives the event and context from API Gateway
- serverless-http converts the Lambda event to an Express-compatible request
- Express app processes the request through middleware chain
- Route handler executes your business logic
- Express returns the response object
- serverless-http converts Express response to Lambda response format
- Lambda logs the request/response to CloudWatch
- Lambda returns the formatted response to API Gateway
- API Gateway sends the HTTP response back to the client
Basic Troubleshooting
Deployment Issues
Issue: Permission denied errors during deployment.
Solution: Verify AWS credentials and permissions:
# Check your AWS identity
$ aws sts get-caller-identity
# Ensure your IAM user has required permissions
Issue: Service name conflicts.
Solution: Use a unique service name in serverless.yml:
service: my-unique-api-name-${self:provider.stage}
Function Errors
Issue: Function timeout errors.
Solution: Increase timeout in serverless.yml:
provider:
timeout: 60 # seconds
Cleaning Up Resources
When you’re done experimenting, remove all AWS resources:
$ serverless remove
Warning: This permanently deletes all resources including Lambda functions, API Gateway, and CloudWatch logs.
Quick Reference
# Create new project
$ serverless # Interactive project creation
# Deploy
$ serverless deploy # Deploy to default stage
$ serverless deploy --stage production # Deploy to specific stage
# Remove resources
$ serverless remove # Clean up all AWS resources
# Get service info
$ serverless info # Show deployment information
Conclusion
Congratulations! You’ve successfully:
- Set up Serverless Framework
- Created your first Express API
- Deployed to AWS Lambda
- Tested your live API endpoints
- Understood the basic architecture
What’s Next?
Ready to continue? Check out Part 2: Development Workflow to learn about local development strategies and testing approaches.