1. AWS Lambda with Node
A Lambda function is a single handler AWS runs in response to an event and stops — you're billed only for the milliseconds it actually executes, and nothing runs (or costs anything) between invocations. That's the opposite trade-off from the always-on, clustered Express process Week 14 built: no idle cost, but also no persistent process to hold a warm database connection pool or in-memory cache between requests.
import type { APIGatewayProxyHandlerV2 } from "aws-lambda";
export const handler: APIGatewayProxyHandlerV2 = async (event) => {
const name = event.queryStringParameters?.name ?? "world";
return {
statusCode: 200,
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ message: `hello, ${name}` }),
};
};
npm run build # esbuild bundles handler.ts + deps into dist/hello.js
zip -j function.zip dist/hello.js
aws lambda create-function \
--function-name hello-api \
--runtime nodejs20.x \
--role arn:aws:iam::123456789012:role/lambda-basic-execution \
--handler hello.handler \
--zip-file fileb://function.zip \
--timeout 10 \
--memory-size 256
A cold start happens when Lambda has to initialize a fresh execution
environment — download your code, start the Node runtime, run any top-level
initialization — before it can handle the first request; a warm
invocation reuses an already-initialized environment and skips all of that. A
function can be triggered by more than an HTTP request — an S3 upload, a message in
an SQS queue, or an EventBridge scheduled rule are all common
event sources, each delivering a differently-shaped
event object to the same handler signature.
Opening a fresh Prisma connection on every invocation adds real latency and can exhaust a database's connection limit under concurrent load. Initialize the Prisma client at module scope, outside the handler function — on a warm invocation, that code doesn't re-run, so the connection is reused across requests, and pairing it with RDS Proxy handles the connection pooling problem at scale.
2. Cloudflare Workers & Vercel Edge Functions
A Lambda function still runs in one AWS region — a request from Tokyo still travels
to us-east-1 if that's where you deployed it. Edge
functions — Cloudflare Workers, Vercel Edge Functions — run your code in
hundreds of points of presence worldwide, executing physically close to whoever made
the request, with startup times measured in single-digit milliseconds rather than
Lambda's cold-start range.
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const url = new URL(request.url);
if (url.pathname === "/api/greeting") {
const name = url.searchParams.get("name") ?? "world";
return Response.json({ message: `hello, ${name}` });
}
return new Response("Not found", { status: 404 });
},
};
That handler signature — fetch(request, env) — is deliberately not
Node's (req, res) Express model at all. Edge runtimes are built on the
standard Web Request/Response APIs (the same objects
fetch() uses in a browser), not Node's http
module, which is why they run in a genuinely different, lighter-weight JavaScript
runtime rather than full Node — no fs, no most of Node's built-in
modules, no arbitrary npm packages that assume Node APIs exist.
npx wrangler deploy
# ~50ms cold starts, deployed to 300+ locations globally, no region to pick
Prisma's default engine relies on a native binary that isn't available in Cloudflare's or Vercel's edge runtime. Reaching a database from edge code means either Prisma's edge-compatible driver adapters (talking to a database over HTTP, like Neon or PlanetScale's HTTP APIs) or accepting that data-heavy logic stays on a regular Node deployment and only latency-sensitive, mostly-stateless logic runs at the edge.
3. What Changes, and When to Reach for It
Three genuinely different runtime models now sit side by side in this course:
- Clustered Express (Week 14) — full Node, every npm package available, a persistent process that can hold connections and in-memory state, running in whichever regions you deploy containers to.
- AWS Lambda — full Node runtime, but no persistent process between invocations; billed per-millisecond, cold starts are a real latency concern, best for event-driven or bursty, infrequent workloads.
- Edge functions — a restricted Web-standard runtime, not full Node, but running physically close to every user worldwide with near-zero cold starts; best for latency-critical, largely stateless logic (auth checks, A/B test routing, simple API responses, redirects).
The honest posture here is the same one Week 18's DevOps counterpart takes toward Kubernetes vs. serverless: none of these replaces the others by default. A typical real system runs its main API as a clustered Node service, offloads specific event-driven jobs (image processing on S3 upload, a webhook receiver) to Lambda, and pushes only latency-critical, stateless logic (auth middleware, feature-flag evaluation, geo-routing) to the edge.
"Serverless" and "edge" both promise less operational overhead, but they trade away real capability to get there — Lambda trades persistent state for pay-per-use, edge functions trade the full Node API surface for global latency. Before moving a piece of logic to either, check whether it genuinely needs what's traded away (a database connection pool, an npm package that touches the filesystem) — if it does, it doesn't belong there regardless of how appealing the deployment model sounds.
4. Hands-on Exercise
Deploy one endpoint to Lambda and one to the edge
Build both ends of the serverless spectrum and compare them directly.
Requirements:
- Deploy a Lambda function behind an API Gateway route, and measure its cold-start latency vs. warm-invocation latency with a simple timing script.
- Add an S3 event source triggering a second Lambda whenever an object is uploaded to a bucket, and confirm it fires by uploading a test file.
- Deploy a Cloudflare Worker (or Vercel Edge Function) implementing a simple, stateless endpoint, and confirm it uses standard
Request/ResponseAPIs rather than an Express-style handler. - Attempt to import a Node-only package (like
fsor your Prisma client directly) into the edge function and observe what breaks — then write a short note on why.
wrangler dev runs a Cloudflare Worker locally against the real edge runtime (not a Node polyfill), which is the fastest way to discover a Node-only API doesn't exist there before deploying and finding out in production.
5. Knowledge Check
Three quick questions. Expand each to check your answer.
Q1
Why should a database connection be initialized outside the Lambda handler function, not inside it?
Why should a database connection be initialized outside the Lambda handler function, not inside it?
Code at module scope only runs once per execution environment, on a cold start; the handler function runs on every invocation. Opening the connection inside the handler means re-establishing it on every single request even when the environment is warm, adding latency and risking exhausting the database's connection limit under load — initializing it at module scope lets a warm invocation reuse the existing connection.
Q2
Why can't an edge function import Node's fs module or use Prisma's default engine directly?
Why can't an edge function import Node's fs module or use Prisma's default engine directly?
Edge runtimes like Cloudflare Workers implement a restricted, Web-standard JavaScript environment built around fetch-style APIs, not full Node.js — there's no filesystem, and most Node-specific built-in modules simply don't exist there. Prisma's default engine depends on a native binary that has nowhere to run in that environment, which is why reaching a database from edge code requires an edge-compatible driver adapter instead.
Q3
What kind of logic is a genuinely good fit for an edge function, versus logic that should stay on a regular clustered Node service?
What kind of logic is a genuinely good fit for an edge function, versus logic that should stay on a regular clustered Node service?
Latency-critical, largely stateless logic — auth checks, feature-flag evaluation, geo-based routing, simple redirects — benefits directly from running physically close to every user with near-zero cold starts. Logic that needs a persistent database connection pool, heavy npm dependencies assuming full Node APIs, or significant CPU-bound computation belongs on a regular clustered Node service instead, since the edge runtime's restrictions would work against it.