---
title: Deno / Deno Deploy
description: Run a Go HTTP server on Deno and Deno Deploy with the worker-go template.
---

[Deno](https://deno.com/) is a modern JavaScript/TypeScript runtime, and [Deno Deploy](https://deno.com/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):

```bash
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:

```bash
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`:

```go
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`).

```go
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:

```js
// 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`:

```go
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.
