Claude
Skills
Sign in
Back

serverless-development

Included with Lifetime
$97 forever

AWS Lambda, Vercel Edge Functions, Cloudflare Workers, cold starts, deployment patterns, and infrastructure as code (SST, Serverless Framework). Use when building serverless applications or optimizing function-based architectures.

Cloud & DevOps

What this skill does


# Serverless Development

## Overview

This skill covers building applications on serverless compute platforms where infrastructure management is abstracted away. It addresses AWS Lambda (Node.js, Python, Go runtimes), Vercel Edge Functions and Serverless Functions, Cloudflare Workers, cold start optimization, serverless design patterns (fan-out, step functions, event-driven), database connection management, deployment with SAM/SST/Serverless Framework, monitoring, and cost optimization.

Use this skill when building APIs, webhooks, background processing, scheduled tasks, or any workload that benefits from auto-scaling, pay-per-use pricing, and zero infrastructure management.

---

## Core Principles

1. **Design for statelessness** - Every function invocation is independent. Never store state in global variables between invocations (though you can reuse connections). Use external state stores (databases, caches, queues) for persistence.
2. **Minimize cold starts** - Cold starts add 100ms-10s of latency on first invocation. Keep bundles small, use lightweight runtimes, and consider provisioned concurrency for latency-sensitive paths.
3. **Connection management is critical** - Serverless functions can spawn thousands of concurrent instances. Each opening its own database connection will exhaust connection pools. Use connection pooling (RDS Proxy, Prisma Data Proxy, PgBouncer).
4. **Pay-per-invocation thinking** - You pay for execution time and memory. Optimize hot paths, choose appropriate memory sizes (more memory = more CPU = potentially faster = sometimes cheaper), and batch operations where possible.
5. **Embrace the constraints** - Function timeouts, payload limits, and concurrency limits are features, not bugs. Design around them: use queues for long tasks, S3 for large payloads, and step functions for complex orchestration.

---

## Key Patterns

### Pattern 1: AWS Lambda with TypeScript

**When to use:** Backend APIs, event processing, scheduled tasks on AWS.

**Implementation:**

```typescript
// handler.ts - Lambda handler with proper typing
import {
  APIGatewayProxyEventV2,
  APIGatewayProxyResultV2,
  SQSEvent,
  ScheduledEvent,
  Context,
} from "aws-lambda";

// Reusable across invocations (connection reuse)
let dbConnection: PrismaClient | null = null;

function getDb(): PrismaClient {
  if (!dbConnection) {
    dbConnection = new PrismaClient({
      datasources: {
        db: { url: process.env.DATABASE_URL },
      },
    });
  }
  return dbConnection;
}

// API Gateway handler
export async function apiHandler(
  event: APIGatewayProxyEventV2,
  context: Context
): Promise<APIGatewayProxyResultV2> {
  // Don't wait for event loop to drain (for connection reuse)
  context.callbackWaitsForEmptyEventLoop = false;

  try {
    const method = event.requestContext.http.method;
    const path = event.rawPath;

    if (method === "GET" && path === "/api/users") {
      const db = getDb();
      const users = await db.user.findMany({ take: 50 });
      return response(200, { users });
    }

    if (method === "POST" && path === "/api/users") {
      const body = JSON.parse(event.body ?? "{}");
      const db = getDb();
      const user = await db.user.create({ data: body });
      return response(201, { user });
    }

    return response(404, { error: "Not found" });
  } catch (error) {
    console.error("Handler error:", error);
    return response(500, { error: "Internal server error" });
  }
}

// SQS event handler (batch processing)
export async function sqsHandler(event: SQSEvent): Promise<{ batchItemFailures: Array<{ itemIdentifier: string }> }> {
  const failures: string[] = [];

  for (const record of event.Records) {
    try {
      const body = JSON.parse(record.body);
      await processMessage(body);
    } catch (error) {
      console.error(`Failed to process message ${record.messageId}:`, error);
      failures.push(record.messageId);
    }
  }

  // Partial batch failure reporting (only retry failed messages)
  return {
    batchItemFailures: failures.map((id) => ({ itemIdentifier: id })),
  };
}

// Scheduled event handler (cron)
export async function cronHandler(event: ScheduledEvent): Promise<void> {
  console.log("Running scheduled task at:", event.time);
  const db = getDb();

  // Clean up expired sessions
  const deleted = await db.session.deleteMany({
    where: { expiresAt: { lt: new Date() } },
  });

  console.log(`Cleaned up ${deleted.count} expired sessions`);
}

function response(statusCode: number, body: Record<string, unknown>): APIGatewayProxyResultV2 {
  return {
    statusCode,
    headers: {
      "Content-Type": "application/json",
      "Access-Control-Allow-Origin": process.env.ALLOWED_ORIGIN ?? "*",
    },
    body: JSON.stringify(body),
  };
}
```

```yaml
# template.yaml - AWS SAM template
AWSTemplateFormatVersion: "2010-09-09"
Transform: AWS::Serverless-2016-10-31

Globals:
  Function:
    Runtime: nodejs20.x
    Timeout: 30
    MemorySize: 256
    Environment:
      Variables:
        DATABASE_URL: !Ref DatabaseUrl
        NODE_OPTIONS: "--enable-source-maps"

Resources:
  ApiFunction:
    Type: AWS::Serverless::Function
    Properties:
      Handler: dist/handler.apiHandler
      Events:
        Api:
          Type: HttpApi
          Properties:
            Path: /api/{proxy+}
            Method: ANY
    Metadata:
      BuildMethod: esbuild
      BuildProperties:
        Minify: true
        Target: es2022
        Sourcemap: true
        EntryPoints:
          - src/handler.ts

  QueueProcessor:
    Type: AWS::Serverless::Function
    Properties:
      Handler: dist/handler.sqsHandler
      Events:
        Queue:
          Type: SQS
          Properties:
            Queue: !GetAtt ProcessingQueue.Arn
            BatchSize: 10
            FunctionResponseTypes:
              - ReportBatchItemFailures

  ScheduledCleanup:
    Type: AWS::Serverless::Function
    Properties:
      Handler: dist/handler.cronHandler
      Events:
        Schedule:
          Type: Schedule
          Properties:
            Schedule: rate(1 hour)

  ProcessingQueue:
    Type: AWS::SQS::Queue
    Properties:
      VisibilityTimeout: 180
      RedrivePolicy:
        deadLetterTargetArn: !GetAtt DLQ.Arn
        maxReceiveCount: 3

  DLQ:
    Type: AWS::SQS::Queue
```

**Why:** AWS Lambda with SAM provides infrastructure-as-code, local testing (`sam local invoke`), and automated deployment. Connection reuse via module-level variables avoids the cold start penalty of reconnecting on every invocation. Partial batch failure reporting for SQS ensures only failed messages are retried.

---

### Pattern 2: Vercel Edge Functions

**When to use:** Low-latency API routes and middleware that need to run close to users globally.

**Implementation:**

```typescript
// app/api/geo/route.ts - Edge Function
export const runtime = "edge"; // Run on Vercel Edge Network

export async function GET(request: Request) {
  // Access geo information (available at the edge)
  const country = request.headers.get("x-vercel-ip-country") ?? "US";
  const city = request.headers.get("x-vercel-ip-city") ?? "Unknown";

  // Edge-compatible KV store
  const cachedData = await kv.get(`content:${country}`);
  if (cachedData) {
    return Response.json(cachedData);
  }

  // Lightweight processing at the edge
  const content = await getLocalizedContent(country);
  await kv.set(`content:${country}`, content, { ex: 3600 });

  return Response.json(content, {
    headers: {
      "Cache-Control": "public, s-maxage=3600, stale-while-revalidate=86400",
    },
  });
}
```

```typescript
// middleware.ts - Edge Middleware for auth, redirects, A/B testing
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";

export const config = {
  matcher: ["/dashboard/:path*", "/api/:path*"],
};

export function middleware(request: NextRequest) {
  // Auth check at the edge (fast, runs before serverless function)
  const token = request.coo

Related in Cloud & DevOps