Amba

Local Dev Server

Run an Amba function locally with `amba functions dev <file>` — hot reload, .env.local pass-through, and the same bundler your production deploy uses.

amba functions dev <file> spins up a local HTTP server that runs your function with the same export shape and env-binding contract as the production runtime. Save the file and the server rebuilds and reloads — you don't have to restart it.

Quick start

amba functions dev ./functions/hello.ts
✓ Bundled in 124 ms (24 KB)
✓ Local dev server: http://localhost:8787
    Entry: ./functions/hello.ts
    Watching for changes. Ctrl+C to stop.

Hit the endpoint:

curl 'http://127.0.0.1:8787/?name=amba'
{"greeting":"hello, amba"}

Edit the handler, save, and the next request runs the new code.

What it does

  • Same bundler as production. amba functions dev uses the same build pipeline as amba functions deploy — externalization rules, size cap, source maps. Dev → prod parity is automatic.
  • Bound to loopback. The dev server only listens on 127.0.0.1 so another device on your Wi-Fi can't reach your handler or read your .env.local.
  • Hot reload. A file save triggers a rebundle + child-process swap. In-flight requests during the swap queue at the TCP listen backlog and complete against the new bundle.
  • .env.local as env bindings. Variables from your project's .env.local are injected as env.* values — the same parameter the handler receives as its second argument (fetch(req, env, ctx)). CLI invocation overrides win on conflict (MY_KEY=x amba functions dev ./fn.ts overrides the file).
  • Modules export shape. The handler must use export default { async fetch(req, env, ctx) { ... } } — the same shape production requires. fetch, URL, Headers, crypto, TextEncoder and friends are all available.

Flags

  • --port <n> — port to listen on (default 8787).
  • --no-watch — disable file-change hot reload. Useful when you want a stable binding (for example, when piping requests from another process that opens long-lived connections).
amba functions dev ./functions/hello.ts --port 4000
amba functions dev ./functions/hello.ts --no-watch

Handler example with .env.local secret

// functions/echo-secret.ts
export default {
  async fetch(req: Request, env: Env, _ctx: ExecutionContext): Promise<Response> {
    const token = env.ECHO_TOKEN;
    if (!token) return new Response('missing ECHO_TOKEN', { status: 500 });
 
    const auth = req.headers.get('authorization');
    if (auth !== `Bearer ${token}`) return new Response('unauthorized', { status: 401 });
 
    return new Response(JSON.stringify({ ok: true }), {
      headers: { 'content-type': 'application/json' },
    });
  },
};
 
interface Env {
  ECHO_TOKEN: string;
}

.env.local in the same directory:

ECHO_TOKEN=local-dev-token

Run:

amba functions dev ./functions/echo-secret.ts
curl -H 'authorization: Bearer local-dev-token' http://127.0.0.1:8787/
{"ok":true}

The same code, deployed via amba functions deploy, reads its secret from the binding the runtime injects — set the production value with amba secrets set ECHO_TOKEN <prod-value>. See deploy.

Common pitfalls

  • Port already in use — pass --port <free-port> or kill the existing process. The dev server bails fast rather than picking a random port (so its address never moves under you while you debug).
  • No reload after save — confirm you didn't pass --no-watch. Some editors save via "rename + replace" which a few filesystems don't notify on; touch the file (touch ./functions/hello.ts) to force a rebuild.
  • env.X is undefined — confirm X is in your .env.local and that the variable wasn't accidentally set in your parent shell to an empty string. Parent env wins over .env.local.

Next

On this page