DEV Community

丁久
丁久

Posted on • Originally published at dingjiu1989-hue.github.io

Serverless Framework: From Zero to Production

This article was originally published on AI Study Room. For the full version with working code examples and related articles, visit the original post.

Serverless Framework: From Zero to Production

Serverless Framework: From Zero to Production

Introduction

The Serverless Framework provides a unified experience for deploying functions, APIs, and event-driven architectures across major cloud providers. While serverless eliminates infrastructure management, it introduces challenges around cold starts, observability, and cost control. This guide walks through taking a serverless application from development to production using the Serverless Framework on AWS Lambda.

Project Setup and Structure

A well-structured serverless project separates concerns across functions, layers, and configuration:

serverless.yml

service: order-processor

frameworkVersion: "4"

provider:

name: aws

runtime: nodejs20.x

region: us-east-1

stage: ${opt:stage, 'dev'}

environment:

ORDER_TABLE: ${self:custom.tableName}

QUEUE_URL: !Ref OrderQueue

plugins:

- serverless-webpack

- serverless-offline

- serverless-prune-plugin

custom:

tableName: orders-${self:provider.stage}

webpack:

packager: pnpm

excludeFiles: src/*/.test.ts

prune:

automatic: true

number: 3

functions:

createOrder:

handler: src/handlers/createOrder.handler

events:

- httpApi:

method: POST

path: /orders

timeout: 10

memorySize: 256

iamRoleStatements:

- Effect: Allow

Action: dynamodb:PutItem

Resource: !GetAtt OrdersTable.Arn

Infrastructure as Code

Define resources alongside functions for self-documenting infrastructure:

resources:

Resources:

OrdersTable:

Type: AWS::DynamoDB::Table

Properties:

TableName: orders-${self:provider.stage}

BillingMode: PAY_PER_REQUEST

AttributeDefinitions:

- AttributeName: orderId

AttributeType: S

- AttributeName: status

AttributeType: S

KeySchema:

- AttributeName: orderId

KeyType: HASH

GlobalSecondaryIndexes:

- IndexName: StatusIndex

KeySchema:

- AttributeName: status

KeyType: HASH

Projection:

ProjectionType: ALL

OrderQueue:

Type: AWS::SQS::Queue

Properties:

QueueName: orders-${self:provider.stage}

VisibilityTimeout: 60

RedrivePolicy:

deadLetterTargetArn: !GetAtt DeadLetterQueue.Arn

maxReceiveCount: 3

DeadLetterQueue:

Type: AWS::SQS::Queue

Properties:

QueueName: orders-dlq-${self:provider.stage}

Lambda Handler Implementation

Write handlers with proper error handling and observability:

// src/handlers/createOrder.ts

import { APIGatewayProxyEvent, APIGatewayProxyResult } from "aws-lambda";

import { DynamoDBClient } from "@aws-sdk/client-dynamodb";

import { DynamoDBDocumentClient, PutCommand } from "@aws-sdk/lib-dynamodb";

import { randomUUID } from "crypto";

import { Logger } from "@aws-lambda-powertools/logger";

import { Metrics } from "@aws-lambda-powertools/metrics";

import { Tracer } from "@aws-lambda-powertools/tracer";

const logger = new Logger({ serviceName: "order-processor" });

const metrics = new Metrics({ namespace: "OrderProcessor" });

const tracer = new Tracer({ serviceName: "order-processor" });

const ddb = DynamoDBDocumentClient.from(new DynamoDBClient({}));

export const handler = async (

event: APIGatewayProxyEvent

): Promise => {

try {

const body = JSON.parse(event.body || "{}");

const orderId = randomUUID();

const order = {

orderId,

...body,

status: "PENDING",

createdAt: new Date().toISOString(),

};

await ddb.send(new PutCommand({

TableName: process.env.ORDER_TABLE,

Item: order,

}));

metrics.addMetric("OrderCreated", 1, "Count");

logger.info("Order created", { orderId });

return {

statusCode: 201,

headers: { "Content-Type": "application/json" },

body: JSON.stringify({ orderId, status: "PENDING" }),

};

} catch (error) {

logger.error("Failed to create order", { error });

metrics.addMetric("OrderCreationError", 1, "Count");

return {

statusCode: 500,

body: JSON.stringify({ message: "Internal server error" }),

};

}

};

Local Development

The serverless-offline plugin provides a local Lambda emulator:

Start local API Gateway emulator

serverless offline --stage dev --httpPort 4000

Invoke a function directly

serverless invoke local --function createOrder \

--path test/fixtures/create-order.json

Run with warm container simulation

serverless offline --stage dev \

--noPrependStageInUrl \

--reloadHandler

Cold Start Optimization

Cold starts add latency when Lambda scales up a new execution environment:

Optimize for cold starts

provider:

Use AWS Graviton for better price/performance

architecture: arm64

Increase memory speeds up CPU allocation

(and proportionally reduces cold start time)

memorySize: 1024

functions:

latencyCritical:

handler: src/handlers/critical.handler

Provisioned concurrency for critical paths

provisionedConcurrency: 5

Reserve concurrency to prevent throttling

reservedConcurrency: 20

Code-level optimizations:

// Cold start optimization techniques

// 1. Lazy initialization outside handler (reused across invocations)

let client: DynamoDBDocumentClient;

function getClient(): D


Read the full article on AI Study Room for complete code examples, comparison tables, and related resources.

Found this useful? Check out more developer guides and tool comparisons on AI Study Room.

Top comments (0)