Serverless Framework: Build and Deploy Serverless Apps
The Serverless Framework is an open-source CLI tool that simplifies building and deploying serverless applications — defining functions, events, and infrastructure in a single serverless.yml file and deploying to AWS, Azure, GCP, or other providers.
What You’ll Learn
- Installing and configuring the Serverless Framework CLI
- Defining functions, events, and resources in serverless.yml
- Working with HTTP, S3, and SQS event triggers
- Using plugins for advanced functionality
- Managing stages (dev/staging/prod) and local development
- Monitoring with Dashbird and Lumigo
Why Serverless Framework Matters
Writing raw CloudFormation or Terraform for Lambda functions, API Gateway, S3 buckets, and IAM roles is verbose and error-prone. The Serverless Framework abstracts all of that — a 20-line serverless.yml can deploy a complete serverless API with monitoring, logging, and staging environments. DodaTech uses the Serverless Framework to deploy Durga Antivirus Pro’s malware signature processing pipeline — Lambda functions triggered by S3 uploads process new signatures, update the database, and push updates to edge servers.
flowchart LR
A[Cloud & AWS Lambda] --> B[Serverless Framework]
B --> C[CLI & serverless.yml]
B --> D[Functions]
B --> E[Events]
B --> F[Plugins]
C --> G[Deploy / Invoke / Logs]
D --> H[Lambda + IAM]
E --> I[HTTP / S3 / SQS / Schedule]
style B fill:#fd5750,color:#fff
Installation and Setup
# Install Serverless Framework globally
npm install -g serverless
# Verify installation
serverless --version
# Output:
# Framework Core: 3.38.0
# Plugin: 7.2.0
# SDK: 4.3.2
# Configure AWS credentials
serverless config credentials \
--provider aws \
--key AKIA123456789EXAMPLE \
--secret abc123def456
# Create a new service
serverless create --template aws-nodejs --path my-serverless-app
cd my-serverless-app
# Output:
# Serverless: Generating boilerplate...
# - 100% completed
# Serverless: Your new project is ready to deployserverless.yml Configuration
# serverless.yml
service: dodatech-api
frameworkVersion: '3'
provider:
name: aws
runtime: nodejs20.x
region: us-east-1
stage: ${opt:stage, 'dev'} # Default: dev
environment:
NODE_ENV: ${self:provider.stage}
TABLE_NAME: ${self:service}-${self:provider.stage}-items
iam:
role:
statements:
- Effect: Allow
Action:
- dynamodb:Query
- dynamodb:PutItem
- dynamodb:GetItem
Resource: !Get AttachmentsTable.Arn
functions:
createItem:
handler: handlers/create.create
events:
- http:
path: /items
method: post
cors: true
getItem:
handler: handlers/get.get
events:
- http:
path: /items/{id}
method: get
cors: true
processUpload:
handler: handlers/s3.processUpload
events:
- s3:
bucket: dodatech-uploads-${self:provider.stage}
event: s3:ObjectCreated:*
existing: true
processQueue:
handler: handlers/sqs.processQueue
events:
- sqs:
arn: !Get AttachmentsQueue.Arn
batchSize: 10
resources:
Resources:
AttachmentsTable:
Type: AWS::DynamoDB::Table
Properties:
TableName: ${self:provider.environment.TABLE_NAME}
BillingMode: PAY_PER_REQUEST
AttributeDefinitions:
- AttributeName: id
AttributeType: S
KeySchema:
- AttributeName: id
KeyAttributeType: HASH
AttachmentsQueue:
Type: AWS::SQS::Queue
Properties:
QueueName: ${self:service}-${self:provider.stage}-attachments
VisibilityTimeout: 60
plugins:
- serverless-offline # Local development
- serverless-plugin-aws-alerts # CloudWatch alarmsFunction Handlers
// handlers/create.js
const AWS = require('aws-sdk');
const dynamodb = new AWS.DynamoDB.DocumentClient();
const { v4: uuidv4 } = require('uuid');
module.exports.create = async (event) => {
try {
const body = JSON.parse(event.body);
const item = {
id: uuidv4(),
...body,
createdAt: new Date().toISOString(),
};
await dynamodb.put({
TableName: process.env.TABLE_NAME,
Item: item,
}).promise();
return {
statusCode: 201,
headers: { 'Access-Control-Allow-Origin': '*' },
body: JSON.stringify(item),
};
} catch (error) {
return {
statusCode: 500,
body: JSON.stringify({ error: error.message }),
};
}
};// handlers/s3.js
const AWS = require('aws-sdk');
module.exports.processUpload = async (event) => {
for (const record of event.Records) {
const bucket = record.s3.bucket.name;
const key = decodeURIComponent(record.s3.object.key.replace(/\+/g, ' '));
console.log(`Processing upload: s3://${bucket}/${key}`);
// In production: scan file, update database, push to edge
// This pattern is used by Durga Antivirus Pro to process
// new malware signatures uploaded to S3
}
return { statusCode: 200 };
};Deploying and Invoking
# Deploy to dev
serverless deploy
# Output:
# Deploying dodatech-api to stage dev (us-east-1)
# ✔ Service deployed to stack dodatech-api-dev (36s)
# endpoints:
# POST - https://abc123.execute-api.us-east-1.amazonaws.com/dev/items
# GET - https://abc123.execute-api.us-east-1.amazonaws.com/dev/items/{id}
# functions:
# createItem: dodatech-api-dev-createItem
# getItem: dodatech-api-dev-getItem
# processUpload: dodatech-api-dev-processUpload
# Deploy to production
serverless deploy --stage prod
# Invoke a function locally
serverless invoke local --function createItem \
--data '{"body": "{\"name\":\"test\"}"}'
# Invoke a deployed function
serverless invoke --function getItem \
--data '{"pathParameters": {"id": "abc123"}}'
# View logs
serverless logs --function createItem --tail
# Output:
# 2024-06-20 10:30:00 START RequestId: ...
# 2024-06-20 10:30:00 { "id": "abc123", "createdAt": "2024-06-20T10:30:00Z" }
# 2024-06-20 10:30:00 END RequestId: ...Stages and Environments
# serverless.yml — stage-specific config
custom:
stages:
dev:
domain: dev-api.dodatech.com
memorySize: 256
tracing: false
prod:
domain: api.dodatech.com
memorySize: 1024
tracing: true
alarms:
- functionErrors
- functionThrottles
provider:
stage: ${opt:stage, 'dev'}
memorySize: ${self:custom.stages.${self:provider.stage}.memorySize}
tracing:
apiGateway: ${self:custom.stages.${self:provider.stage}.tracing}Local Development
# Install serverless-offline plugin
npm install --save-dev serverless-offline
# Start local server
serverless offline
# Output:
# Starting Offline: dev/us-east-1
# Offline [http for lambda] listening on http://localhost:3002
# Function names exposed:
# createItem: http://localhost:3000/dev/items
# getItem: http://localhost:3000/dev/items/{id}
# Test locally
curl -X POST http://localhost:3000/dev/items \
-H "Content-Type: application/json" \
-d '{"name": "test item"}'Monitoring with Dashbird and Lumigo
# Install Dashbird plugin
npm install --save-dev @dashbird/serverless-plugin
# serverless.yml
plugins:
- '@dashbird/serverless-plugin'
custom:
dashbird:
apiKey: ${env:DASHBIRD_API_KEY}Or configure Lumigo for distributed tracing:
# serverless.yml
plugins:
- lumigo-plugin
custom:
lumigo:
token: ${env:LUMIGO_TOKEN}# View monitoring data
serverless dashbird
# Or deploy with Lumigo auto-instrumentation
serverless deployCommon Mistakes
Not separating stages: Running dev and prod in the same AWS account without stage isolation causes accidental data corruption. Always use separate stages with unique resource names.
Forgetting IAM permissions: Lambda functions need explicit permissions to access DynamoDB, S3, SQS, etc. The error
AccessDeniedExceptionis almost always missing IAM permissions.No dead-letter queue for async functions: Failed SQS or S3-triggered invocations are lost without a DLQ. Configure
onError: arn:aws:sqs:::dlqto capture failures.Over-provisioning memory: Lambda pricing scales with memory. Most Node.js/Python functions need 256-512MB. Use 1024MB+ only for CPU-intensive tasks. Test with minimal memory and increase as needed.
Not setting API Gateway timeout: API Gateway has a 29-second timeout. If your function takes longer, use an async pattern (SQS → Lambda → WebSocket callback).
Practice Questions
What does the Serverless Framework do? Answer: It abstracts CloudFormation to define serverless infrastructure in a simple YAML file. It handles packaging, deploying, and managing Lambda functions, API Gateway, and other AWS resources.
How do you handle different stages (dev/staging/prod)? Answer: Use
--stage prodflag orprovider.stagevariable. Resources are named with the stage suffix. Custom stage-specific config (memory, domains, alarms) can be defined in thecustomblock.What is the purpose of the serverless-offline plugin? Answer: It emulates API Gateway and Lambda locally, allowing you to develop and test functions without deploying to AWS. Speeds up development significantly.
How does the Serverless Framework handle IAM permissions? Answer: You define IAM role statements in the
provider.iam.role.statementsblock. The framework creates a single role with those permissions and attaches it to all functions.
Challenge
Build and deploy a complete serverless application: create a serverless API with CRUD endpoints backed by DynamoDB, add an S3-triggered function that processes uploaded images (generate thumbnails), configure an SQS queue for async processing with a dead-letter queue, deploy to dev and prod stages with different memory sizes, set up CloudWatch alarms for error rates, and enable distributed tracing with Lumigo.
FAQ
Mini Project: Serverless URL Shortener
# serverless.yml
service: url-shortener
provider:
name: aws
runtime: nodejs20.x
iam:
role:
statements:
- Effect: Allow
Action: dynamodb:PutItem
Resource: !Get ShortlinksTable.Arn
functions:
create:
handler: handler.create
events:
- http:
path: /shorten
method: post
redirect:
handler: handler.redirect
events:
- http:
path: /{code}
method: get
resources:
Resources:
ShortlinksTable:
Type: AWS::DynamoDB::Table
Properties:
TableName: shortlinks-${self:provider.stage}
BillingMode: PAY_PER_REQUEST
AttributeDefinitions:
- AttributeName: code
AttributeType: S
KeySchema:
- AttributeName: code
KeyAttributeType: HASH// handler.js
const AWS = require('aws-sdk');
const crypto = require('crypto');
const dynamodb = new AWS.DynamoDB.DocumentClient();
module.exports.create = async (event) => {
const { url } = JSON.parse(event.body);
const code = crypto.randomBytes(4).toString('hex');
await dynamodb.put({
TableName: process.env.TABLE_NAME,
Item: { code, url, createdAt: Date.now() },
}).promise();
return {
statusCode: 201,
body: JSON.stringify({ shortUrl: `https://dodatech.link/${code}` }),
};
};
module.exports.redirect = async (event) => {
const { code } = event.pathParameters;
const result = await dynamodb.get({
TableName: process.env.TABLE_NAME,
Key: { code },
}).promise();
if (!result.Item) {
return { statusCode: 404, body: 'Not found' };
}
return {
statusCode: 301,
headers: { Location: result.Item.url },
};
};What’s Next
| Topic | Description |
|---|---|
| Testing resilience | |
| Handling production outages |
Related topics: AWS Lambda, AWS, Serverless Computing, CI/CD
What’s Next
Congratulations on completing this Serverless Framework tutorial! Here’s where to go from here:
- Practice daily — Deploy a simple serverless API today
- Build a project — Create a URL shortener with DynamoDB and API Gateway
- Explore related topics — Check out chaos engineering and incident response
Remember: every expert was once a beginner. Keep coding!
Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro