Skip to content

Serverless Framework — Infrastructure as Code for Serverless

DodaTech Updated 2026-06-28 5 min read

In this tutorial, you will learn about Serverless Framework. We cover key concepts, practical examples, and best practices to help you master this topic.

The Serverless Framework is an open-source CLI tool that defines serverless applications in YAML, managing AWS Lambda functions, API Gateway, DynamoDB tables, and other resources using infrastructure as code.

What You'll Learn

By the end of this lesson you will understand how to use the Serverless Framework to define functions, events, and resources in serverless.yml, deploy to AWS, and manage environments.

Why It Matters

Manually creating Lambda functions, API Gateway routes, and IAM roles in the AWS console is error-prone and not reproducible. The Serverless Framework codifies your entire serverless infrastructure in a version-controlled YAML file, enabling repeatable deployments and team collaboration.

Real-World Use

DodaTech deploys all serverless functions using the Serverless Framework. A single sls deploy command creates the Lambda function, API Gateway endpoints, DynamoDB tables, S3 buckets, and IAM roles -- with separate stages for dev, staging, and production.

flowchart LR
    Y[serverless.yml] --> SLS[sls deploy]
    SLS --> CF[CloudFormation Stack]
    CF --> L[AWS Lambda]
    CF --> AG[API Gateway]
    CF --> D[DynamoDB]
    CF --> IAM[IAM Roles]
    CF --> S3[S3 Bucket]
    style SLS fill:#f90,color:#fff

serverless.yml Structure

The configuration file defines the service name, provider configuration, functions with their events, and resources.

# serverless.yml
service: user-api

provider:
  name: aws
  runtime: python3.9
  region: us-east-1
  stage: ${opt:stage, 'dev'}
  environment:
    TABLE_NAME: ${self:service}-${self:provider.stage}-users
  iamRoleStatements:
    - Effect: Allow
      Action:
        - dynamodb:GetItem
        - dynamodb:PutItem
        - dynamodb:Query
      Resource: !GetAtt UsersTable.Arn

functions:
  createUser:
    handler: handlers/users.create
    events:
      - httpApi:
          method: POST
          path: /users
  getUser:
    handler: handlers/users.get
    events:
      - httpApi:
          method: GET
          path: /users/{id}

resources:
  Resources:
    UsersTable:
      Type: AWS::DynamoDB::Table
      Properties:
        TableName: ${self:service}-${self:provider.stage}-users
        BillingMode: PAY_PER_REQUEST
        AttributeDefinitions:
          - AttributeName: id
            AttributeType: S
        KeySchema:
          - AttributeName: id
            KeyType: HASH

Defining Functions and Events

Each function entry maps a handler file to one or more event triggers.

# handlers/users.py
# Lambda handlers for user CRUD

import json

def create(event, context):
    body = json.loads(event.get("body", "{}"))
    user = {"id": body.get("email"), "name": body.get("name"), "email": body.get("email")}
    print(f"Created user: {user['email']}")
    return {"statusCode": 201, "body": json.dumps(user)}

def get(event, context):
    user_id = event.get("pathParameters", {}).get("id")
    user = {"id": user_id, "name": "Alice", "email": "alice@example.com"}
    return {"statusCode": 200, "body": json.dumps(user)}

# Test
test_create = {"body": json.dumps({"name": "Alice", "email": "alice@example.com"})}
print(create(test_create, None)["body"])
test_get = {"pathParameters": {"id": "alice@example.com"}}
print(get(test_get, None)["body"])

Expected output:

Created user: alice@example.com
{"id": "alice@example.com", "name": "Alice", "email": "alice@example.com"}
{"id": "alice@example.com", "name": "Alice", "email": "alice@example.com"}

Plugins

The Serverless Framework ecosystem includes plugins for offline development, TypeScript support, webpack Bundling, and more.

# plugins.py
# Common Serverless Framework plugins

def list_popular_plugins():
    plugins = [
        ("serverless-offline", "Run Lambda and API Gateway locally"),
        ("serverless-webpack", "Bundle Node.js functions with webpack"),
        ("serverless-python-requirements", "Bundle Python dependencies"),
        ("serverless-prune-plugin", "Remove old function versions"),
        ("serverless-dotenv-plugin", "Load .env files for local development"),
        ("serverless-step-functions", "Define Step Functions in serverless.yml"),
        ("serverless-iam-roles-per-function", "Per-function IAM roles"),
    ]
    
    print(f"{'Plugin':35s} {'Description':50s}")
    print("-" * 85)
    for name, desc in plugins:
        print(f"{name:35s} {desc:50s}")

list_popular_plugins()

Multi-Stage Deployments

Use stages to maintain separate environments with different configurations.

# stages.py
# Multi-environment configuration

def get_stage_config(stage):
    configs = {
        "dev": {
            "table_name": "users-dev",
            "log_level": "DEBUG",
            "api_url": "https://api.dev.example.com"
        },
        "staging": {
            "table_name": "users-staging",
            "log_level": "INFO",
            "api_url": "https://api.staging.example.com"
        },
        "prod": {
            "table_name": "users-prod",
            "log_level": "WARNING",
            "api_url": "https://api.example.com"
        }
    }
    return configs.get(stage, configs["dev"])

def deploy_stage(stage):
    config = get_stage_config(stage)
    print(f"Deploying to {stage}...")
    for key, value in config.items():
        print(f"  {key}: {value}")
    print(f"  sls deploy --stage {stage}")
    print(f"  Stack: user-api-{stage}")
    print(f"  API URL: {config['api_url']}")

deploy_stage("dev")
print()
deploy_stage("prod")

Common Mistakes

  1. Not using stages for environment isolation: Deploying everything to the default stage (dev) risks accidental production updates.

  2. Storing sensitive values in serverless.yml: Use environment variables from AWS SSM or Secrets Manager, not hardcoded in YAML.

  3. Over-provisioning IAM permissions: Wide permissions like dynamodb:* on all tables violate Least Privilege. Scope to specific tables and actions.

  4. Not using the Python requirements plugin: Python Lambda functions need dependencies bundled. The serverless-python-requirements plugin handles this.

  5. Forgetting to prune old versions: Each deployment creates a new Lambda version. Use serverless-prune-plugin to keep only recent versions.

Practice Questions

  1. What is the Serverless Framework? An open-source CLI for defining and deploying serverless applications using infrastructure as code with serverless.yml.

  2. How does the Serverless Framework deploy resources? It generates an AWS CloudFormation template from serverless.yml and deploys it as a CloudFormation stack.

  3. What is serverless-offline? A plugin that emulates Lambda and API Gateway locally for development and testing.

  4. How do you manage multiple environments? Use the --stage parameter to deploy to different environments with environment-specific configuration.

  5. Challenge: Create a serverless.yml for a REST API with three functions (create, list, get) backed by DynamoDB with separate stages for dev and prod.

FAQ

Is the Serverless Framework free?

Yes, it is open-source. You pay only for the AWS resources it creates.

Can I use the Serverless Framework with other cloud providers?

Yes. It supports AWS, Azure, Google Cloud, and other providers via plugins.

How does the Serverless Framework handle dependencies?

Python dependencies are handled by serverless-python-requirements. Node.js uses serverless-webpack or package.json.

Can I define custom resources in serverless.yml?

Yes. Use the resources section to define any CloudFormation resource including DynamoDB, S3, and IAM.

How do I configure custom domains with the Framework?

Use the serverless-domain-manager plugin to configure custom domains in API Gateway.

Mini Project

Create a serverless.yml for a URL shortener API with POST /shorten and GET /{code} functions, a DynamoDB table, and separate dev/prod stages.

import json
import hashlib
import time

store = {}

def shorten(event, context):
    body = json.loads(event.get("body", "{}"))
    url = body.get("url")
    if not url:
        return {"statusCode": 400, "body": json.dumps({"error": "Missing url"})}
    code = hashlib.md5(url.encode()).hexdigest()[:8]
    store[code] = url
    print(f"Shortened {url} -> {code}")
    return {"statusCode": 201, "body": json.dumps({"shortCode": code, "url": f"https://short.ly/{code}"})}

def redirect(event, context):
    code = event.get("pathParameters", {}).get("code")
    url = store.get(code)
    if not url:
        return {"statusCode": 404, "body": json.dumps({"error": "Not found"})}
    return {"statusCode": 301, "headers": {"Location": url}, "body": ""}

print(shorten({"body": json.dumps({"url": "https://example.com"})}, None)["body"])

What's Next

Next: Serverless Offline for local development.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro