Deno / Deno Deploy
Run a Go HTTP server on Deno and Deno Deploy with the worker-go template.
Deno is a modern JavaScript/TypeScript runtime, and Deno Deploy is its hosted edge platform. The worker-go template compiles your http.Handler to Wasm and serves it through Deno.serve — the same workers.Serve() API as on the other platforms.
Requirements
- Deno 2.4.2 or later (required for
deno deploy) - Go 1.24.0 or later
Create a project
Scaffold the template with npx degit (requires Node.js and npm just for this step):
npx degit github:syumai/workers-go/_templates/deno/worker-go my-app
cd my-app
go mod init
go mod tidy
deno task build
Start the dev server:
deno task dev
curl http://localhost:8000/hello # outputs "Hello!"
How it works
deno task build runs workers-assets-gen -runtime=deno -mode=go to emit the runtime assets into ./build, then go build compiles your Go code to ./build/app.wasm. Unlike the Cloudflare and browser runtimes, Deno gets its own entry point: main.mjs, which simply calls Deno.serve on the worker’s fetch handler. runtime.mjs loads the Wasm binary via Deno.readFile for file: URLs and WebAssembly.compileStreaming when served over HTTP.
The runtime context exposes Deno environment variables through the same env shape used by the Cloudflare runtime, so cloudflare.Getenv keeps working — the proxy defers Deno.env.get calls, so no --allow-env permission is needed unless a variable is actually read.
On the Go side it is an ordinary http.Handler:
func main() {
http.HandleFunc("/hello", func(w http.ResponseWriter, req *http.Request) {
w.Write([]byte("Hello!"))
})
workers.Serve(nil) // if nil is given, http.DefaultServeMux is used
}
Deno runtime APIs
The experimental exp/deno package provides Go bindings for Deno runtime APIs (Deno.openKv, Deno.env, Deno.cron, system information, and more), generated by scripts/gen-deno from deno doc --json output (make gen-deno).
kv, err := deno.OpenKv(":memory:")
if err != nil {
panic(err)
}
defer kv.Close()
Locally, Deno.openKv requires the --unstable-kv flag (add it to the dev task in deno.json). On Deno Deploy, unstable APIs are gated by the unstable field in deno.json; the template already sets "unstable": ["kv", "cron"] and adds ./deno.json to deploy.include so the deployed runtime reads it.
Cron jobs
Deno Deploy discovers cron jobs by evaluating a module’s top-level JavaScript at deployment time — a Deno.cron() call made from Go (via deno.Cron) runs only once a request boots the Wasm module, so it is never seen by the scheduler. Declare each job’s schedule in crons.mjs at the project root instead; workers-assets-gen copies it into build/ next to main.mjs, which imports it:
// crons.mjs (project root)
import worker from "./worker.mjs";
Deno.cron("my-job", "0 * * * *", () => worker.cron("my-job"));
and register the handler in Go with deno.OnCron:
deno.OnCron("my-job", func() {
// runs every hour
})
Locally, Deno.cron requires the --unstable-cron flag (add it to the dev task in deno.json). deno.Cron still works under deno run for jobs you only need locally, but only the crons.mjs + deno.OnCron pair works on Deno Deploy.
Commands
| Command | Description |
|---|---|
deno task dev |
Run the dev server (deno run --allow-net --allow-read --allow-env build/main.mjs) |
deno task build |
Generate runtime assets and build the Go Wasm binary |
deno task deploy |
Deploy to Deno Deploy (deno deploy) |
To deploy, replace "org": "<TBD>" in deno.json with your Deno Deploy organization slug (or leave it and let deno deploy prompt you), then run deno task build && deno task deploy. On the first run, deno deploy interactively lets you select or create an app and saves deploy.app back into deno.json. Use deno deploy --prod to deploy to production.