DEV Community

Cover image for Running AWS SAM CLI Locally Before Deploying to Cloudflare Workers
Nathan Cross
Nathan Cross

Posted on

Running AWS SAM CLI Locally Before Deploying to Cloudflare Workers

Running AWS SAM CLI Locally Before Deploying to Cloudflare Workers

When you're building serverless APIs that might start on Lambda but need to run at the edge later, local dev feedback loops matter. I've been using aws-sam-cli not because I deploy everything to Lambda, but because its local emulation layer catches integration bugs before they hit production.

The Cold-Start Problem AWS SAM Exposes Early

Most serverless frameworks let you deploy blind. SAM forces you to think about:

  • Event schema validation — Your API Gateway or ALB payload won't magically match Hono's c.req.json()
  • Environment variable injection — If your KV namespace bindings aren't set, your code fails fast locally instead of erroring at deploy-time
  • Timeout boundaries — Lambda's 29-second ALB limit vs Cloudflare's 30-second Worker CPU limit

I run sam local start-api against a template.yaml that mimics my actual routing, then port the logic to Hono when it's validated.

Real Pattern: Validating Structured Input Before Edge Deployment

Here's a Hono.js router I validated locally with SAM first, then deployed to Cloudflare Workers:

import { Hono } from 'hono'
import { validator } from 'hono/validator'
import { z } from 'zod'

const app = new Hono()

const webhookSchema = z.object({
  event: z.enum(['charge.succeeded', 'charge.failed']),
  amount: z.number().positive(),
  customer_id: z.string().min(1)
})

app.post(
  '/webhook/stripe',
  validator('json', (value, c) => {
    const parsed = webhookSchema.safeParse(value)
    if (!parsed.success) {
      return c.json({ error: parsed.error.flatten() }, 400)
    }
    return parsed.data
  }),
  async (c) => {
    const payload = c.req.valid('json')

    // Write to KV with TTL
    await c.env.EVENTS_KV.put(
      `evt:${Date.now()}:${payload.customer_id}`,
      JSON.stringify(payload),
      { expirationTtl: 86400 }
    )

    return c.json({ received: true })
  }
)

export default app
Enter fullscreen mode Exit fullscreen mode

The Wrangler Config That Actually Works

This wrangler.jsonc sets up KV bindings and secret injection without surprises:

{
  "name": "webhook-receiver",
  "main": "src/index.ts",
  "compatibility_date": "2024-01-15",
  "node_compat": false,
  "kv_namespaces": [
    {
      "binding": "EVENTS_KV",
      "id": "a1b2c3d4e5f6",
      "preview_id": "a1b2c3d4e5f6"
    }
  ],
  "vars": {
    "ENVIRONMENT": "production"
  },
  "routes": [
    {
      "pattern": "api.example.com/webhook/*",
      "zone_name": "example.com"
    }
  ],
  "limits": {
    "cpu_ms": 50
  }
}
Enter fullscreen mode Exit fullscreen mode

Key details:

  • node_compat: false keeps bundle size under 1MB (learned this after hitting Cloudflare's upload limit)
  • preview_id mirrors production KV so wrangler dev doesn't write to live data
  • limits.cpu_ms enforces 50ms ceiling during dev — if local exceeds this, production will fail

War Story: Connection Pool Exhaustion on Serverless Postgres

When I first deployed a Workers-to-Neon Postgres integration, everything worked in dev. Production died under 500 req/s.

The issue: each Worker invocation opened a new connection, but Neon's free tier caps at 100 concurrent connections. Pooling layers like PgBouncer don't work at the edge because Workers have no persistent TCP.

Solution: switched to Neon's HTTP API (@neondatabase/serverless) which uses stateless HTTPS. Zero connection pooling needed. Latency went from p99 800ms to p99 120ms.

What I Validate with SAM Before Edge Deploy

  1. Schema drift — Does my Zod schema actually parse production payloads?
  2. Timeout math — If SAM local times out at 3s, my Worker will too
  3. Error format — Does my 400 response match what the client expects?

Then I run wrangler deploy --dry-run to catch bundle size blowups before real traffic hits.

Takeaway

AWS SAM CLI isn't just for Lambda. Its local emulation catches integration bugs that Cloudflare's wrangler dev misses — especially around event shape validation and timeout boundaries. Use it as a pre-flight gate, then ship to the edge with confidence.

Full code: validation happens once locally, deploys everywhere without surprises.

Top comments (0)