What an edge function is good for
An edge function is a small, stateless piece of code that runs on demand, close to your users, and returns a response. It's ideal for API routes that are lightweight and latency-sensitive: auth checks, redirects, form handlers, webhook receivers, lightweight JSON APIs, and personalization.
It's the wrong tool for: long-running jobs, persistent WebSocket connections, heavy CPU work, or anything that needs a big bundle of native dependencies. Those belong in a container app. Knowing the boundary saves you a lot of frustration.
Edge function vs. container app
| Property | Edge function | Container app |
|---|---|---|
| Lifecycle | Per-request, stateless | Long-running process |
| Best for | Light API routes, webhooks | Full backends, workers, sockets |
| Cold start | Possible on first hit | Warm (or scale-to-zero) |
| State | None (use external store) | In-memory + external |
| Long connections | No | Yes |
A minimal edge function
Edge functions take a request and return a response. A simple JSON API route:
// hello.js
export default async function handler(req) {
const { searchParams } = new URL(req.url);
const name = searchParams.get("name") ?? "world";
return new Response(JSON.stringify({ message: `hello, ${name}` }), {
status: 200,
headers: { "content-type": "application/json" },
});
}The Web Request/Response API is the common interface across edge runtimes, so code stays portable.
A webhook receiver
A very common edge use case — receive a webhook, verify its signature, do something light, return fast:
import { createHmac, timingSafeEqual } from "node:crypto";
export default async function handler(req) {
const body = await req.text();
const sig = req.headers.get("x-signature") ?? "";
const expected = createHmac("sha256", process.env.WEBHOOK_SECRET)
.update(body).digest("hex");
if (!timingSafeEqual(Buffer.from(sig), Buffer.from(expected))) {
return new Response("invalid signature", { status: 401 });
}
// do the lightweight thing (enqueue, update a flag, etc.)
return new Response("ok", { status: 200 });
}Verify signatures with a constant-time comparison (timingSafeEqual) — a plain === leaks timing information.
Calling a database from the edge
Edge functions can hit a database, but be careful: many edge runtimes don't keep TCP pools warm between invocations, so a raw pg pool can exhaust connections fast. Options:
- Use an HTTP-based / serverless database driver that connects per request over HTTP.
- Put a connection pooler (e.g., PgBouncer) in front of Postgres.
- For heavy DB work, call a container app from the edge function instead.
For light reads, a per-request HTTP query is fine; for anything write-heavy or transactional, route to a container backend.
Environment variables and secrets
Edge functions read secrets from env vars just like any other deploy. Never bake secrets into the bundle:
const apiKey = process.env.THIRD_PARTY_KEY;Set these in the platform's environment settings, scoped to the function.
Deploying on PandaStack
PandaStack includes edge functions (available on all tiers, including free). To deploy an API route:
- 1In the dashboard, create a new Edge Function (or add one to your project).
- 2Point it at your handler file / repo.
- 3Set the route path (e.g.,
/api/hello) and any environment variables/secrets. - 4Deploy. Edge functions support multiple runtimes (Node.js, Python, Go).
- 5Use function tokens for authenticated invocation where needed.
Functions can be invoked via their route or directly, and you get live logs for debugging.
Testing your route
curl "https://your-app.example.com/api/hello?name=ajay"
# {"message":"hello, ajay"}
# webhook with a signature header
curl -X POST https://your-app.example.com/api/webhook \
-H "x-signature: <hmac>" \
-d '{"event":"test"}'Constraints to design around
- Cold starts. The first invocation after idle may be slower. Keep handlers small to minimize this.
- Execution time limits. Edge functions are meant to be fast; long work should be enqueued to a worker.
- No persistent connections. No WebSockets, no holding a socket open — use a container app for that.
- Stateless. Don't store anything in memory between invocations; use Redis or a database.
- Bundle size. Big native dependencies often don't run at the edge — keep dependencies lean.
When to graduate to a container
If your "edge function" starts pulling in a heavy ORM, needs transactions, runs for seconds, or wants to hold connections, that's the signal to move it into a container app. Edge functions and containers complement each other: edge for the fast front door, containers for the heavy lifting behind it.
References
- MDN Request interface: https://developer.mozilla.org/en-US/docs/Web/API/Request
- MDN Response interface: https://developer.mozilla.org/en-US/docs/Web/API/Response
- Web Crypto / HMAC (Node crypto): https://nodejs.org/api/crypto.html#cryptocreatehmacalgorithm-key-options
- Serverless cold starts (overview): https://aws.amazon.com/blogs/compute/operating-lambda-performance-optimization-part-1/
- Apache OpenWhisk (edge/serverless model): https://openwhisk.apache.org/documentation.html
---
Want low-latency API routes without standing up a whole backend? PandaStack includes edge functions on every tier, even free. Deploy one at https://dashboard.pandastack.io