Skip to content
Serverless Framework: Build and Deploy Serverless Apps

Serverless Framework: Build and Deploy Serverless Apps

DodaTech Updated Jun 20, 2026 8 min read

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
  
Prerequisites: Basic AWS Lambda knowledge. Familiarity with Node.js or Python. An AWS account with credentials configured.

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 deploy

serverless.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 alarms

Function 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 deploy

Common Mistakes

  1. 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.

  2. Forgetting IAM permissions: Lambda functions need explicit permissions to access DynamoDB, S3, SQS, etc. The error AccessDeniedException is almost always missing IAM permissions.

  3. No dead-letter queue for async functions: Failed SQS or S3-triggered invocations are lost without a DLQ. Configure onError: arn:aws:sqs:::dlq to capture failures.

  4. 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.

  5. 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

  1. 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.

  2. How do you handle different stages (dev/staging/prod)? Answer: Use --stage prod flag or provider.stage variable. Resources are named with the stage suffix. Custom stage-specific config (memory, domains, alarms) can be defined in the custom block.

  3. 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.

  4. How does the Serverless Framework handle IAM permissions? Answer: You define IAM role statements in the provider.iam.role.statements block. 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

Is the Serverless Framework free?
: Yes, the CLI and framework are open-source (MIT license). You pay only for the AWS resources you deploy (Lambda, API Gateway, S3, etc.).
Can I use the Serverless Framework with Azure or GCP?
: Yes. It supports AWS, Azure, GCP, and other providers. Change provider.name to azure or google. AWS has the most plugin support.
What is the difference between Serverless Framework and SAM?
: SAM is AWS-specific with CloudFormation syntax. Serverless Framework is multi-provider with simpler YAML syntax and a larger plugin ecosystem.
How do I handle environment variables?
: Define them in provider.environment or function-level environment. Use ${env:MY_VAR} to reference system environment variables. Use SSM for secrets.
What is the maximum Lambda function size?
: 250MB (zipped, including dependencies). For larger dependencies, use Lambda Layers or container images (10GB limit).

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

TopicDescription
Chaos Engineering
Testing resilience
Incident Response
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