DEV Community

myougaTheAxo
myougaTheAxo

Posted on

Designing Serverless with Claude Code: AWS Lambda Cold Start, RDS Proxy, Vercel Edge

Introduction

Lambda cold starts slow response times, PostgreSQL connections get exhausted with every invocation — serverless has unique design patterns. Generate designs with Claude Code.


CLAUDE.md Serverless Rules

## Serverless Design Rules

### Cold Start Mitigation
- Initialize outside the handler (DB connections, config loading)
- Provisioned Concurrency for critical endpoints
- Minimize bundle size (exclude unused dependencies)

### DB Connections
- Don't use connection pools (Lambda = 1 request = 1 instance)
- Connect to PostgreSQL via RDS Proxy (connection limit management)
- Lambda execution environments are reused, but new instances also spin up

### Function Design
- 1 function = 1 responsibility (don't pack routing in)
- Set timeout to minimum needed (default 3s can be too short)
- Design for idempotency (assume retries will happen)

### Vercel Edge Functions
- Node.js APIs not available (Edge Runtime)
- Keep responses lightweight (target under 50ms)
- Specialize in pure computation like JWT verification
Enter fullscreen mode Exit fullscreen mode

Generate Lambda Function

Design a user registration API for AWS Lambda.

Requirements:
- Cold start mitigation (initialize outside handler)
- PostgreSQL connection via RDS Proxy
- Validation with Zod
- Return errors with appropriate HTTP status codes

Generate file: functions/createUser/handler.ts
Enter fullscreen mode Exit fullscreen mode

Generated Lambda Function

// functions/createUser/handler.ts
import { APIGatewayProxyEvent, APIGatewayProxyResult } from 'aws-lambda';
import { PrismaClient } from '@prisma/client';
import { z } from 'zod';

// Initialize outside handler (skipped when Lambda instance is reused)
const prisma = new PrismaClient({
  datasources: {
    db: { url: process.env.DATABASE_URL },
    // For RDS Proxy: ?connection_limit=1&pool_timeout=0
  },
});

const createUserSchema = z.object({
  email: z.string().email(),
  name: z.string().min(1).max(100),
  password: z.string().min(8),
});

// Lambda handler
export const handler = async (
  event: APIGatewayProxyEvent
): Promise<APIGatewayProxyResult> => {
  const requestId = event.requestContext?.requestId ?? crypto.randomUUID();

  try {
    const body = JSON.parse(event.body ?? '{}');
    const result = createUserSchema.safeParse(body);

    if (!result.success) {
      return {
        statusCode: 422,
        headers: { 'Content-Type': 'application/json', 'X-Request-ID': requestId },
        body: JSON.stringify({
          errors: result.error.errors.map(e => ({
            field: e.path.join('.'),
            message: e.message,
          })),
        }),
      };
    }

    const { email, name, password } = result.data;

    // Duplicate check
    const existing = await prisma.user.findUnique({ where: { email } });
    if (existing) {
      return {
        statusCode: 409,
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ error: 'Email already registered' }),
      };
    }

    const hashedPassword = await bcrypt.hash(password, 12);
    const user = await prisma.user.create({
      data: { email, name, passwordHash: hashedPassword },
      select: { id: true, email: true, name: true, createdAt: true },
    });

    return {
      statusCode: 201,
      headers: { 'Content-Type': 'application/json', 'X-Request-ID': requestId },
      body: JSON.stringify(user),
    };

  } catch (err) {
    console.error({ err, requestId }, 'Unexpected error');
    return {
      statusCode: 500,
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ error: 'Internal server error' }),
    };
  }
};
Enter fullscreen mode Exit fullscreen mode

Vercel Edge Functions (JWT Verification)

// middleware.ts (Vercel Edge Runtime)
import { NextRequest, NextResponse } from 'next/server';
import { jwtVerify } from 'jose'; // JWT implementation without Node.js API

const JWT_SECRET = new TextEncoder().encode(process.env.JWT_SECRET!);

export async function middleware(request: NextRequest) {
  const token = request.cookies.get('access_token')?.value;

  if (!token) {
    return NextResponse.redirect(new URL('/login', request.url));
  }

  try {
    const { payload } = await jwtVerify(token, JWT_SECRET);

    // Pass verified user info via headers to backend
    const response = NextResponse.next();
    response.headers.set('X-User-Id', payload.userId as string);
    response.headers.set('X-Tenant-Id', payload.tenantId as string);
    return response;

  } catch (err) {
    // Invalid JWT → redirect to login
    return NextResponse.redirect(new URL('/login', request.url));
  }
}

export const config = {
  matcher: ['/dashboard/:path*', '/api/protected/:path*'],
};
Enter fullscreen mode Exit fullscreen mode

Summary

Design Serverless with Claude Code:

  1. CLAUDE.md — document cold start mitigation, DB connection policy, Edge constraints
  2. Initialize outside handler — reduce initialization cost on Lambda instance reuse
  3. RDS Proxy — prevent DB connection explosion from Lambda
  4. Vercel Edge — don't use Node.js APIs (use Edge-compatible libraries like jose)

Review serverless designs with **Code Review Pack (¥980)* using /code-review at prompt-works.jp*

myouga (@myougatheaxo) — Axolotl VTuber.

Top comments (0)