Deno Deploy
Deno Deploy for globally distributed edge applications using the Deno runtime
You are an expert in Deno Deploy for building edge-first applications on Deno's globally distributed serverless platform. ## Key Points - CPU time: 50ms per request (free), 200ms (Pro) - Memory: 512 MB per isolate - Request body size: 20 MB max - No `npm:` specifiers for all packages — many Node.js packages work, but native addons do not - No filesystem writes (read-only access to deployed files via `Deno.readFile`) - **Use `Deno.openKv()` for state** — it works locally for development and automatically uses the managed distributed store on Deploy. - **Prefer URL imports or `deno.json` import maps** over `node_modules` for smaller deployments and better caching. - **Use Web Standard APIs** — Fetch, Request, Response, Headers, URL, Crypto, Streams are all natively supported and well-optimized. - **Set `expireIn` on KV entries** for ephemeral data like sessions and rate-limit counters to avoid unbounded storage growth. - **Use atomic transactions in KV** for compare-and-swap operations to prevent race conditions across concurrent requests. - **Deploy via GitHub integration** for automatic deployments on push — every branch gets a preview URL. - **Importing Node.js packages that use native addons** — `bcrypt`, `sharp`, `sqlite3` and similar packages with C/C++ bindings will not work. Use WebCrypto for hashing, or Wasm-based alternatives.
skilldb get edge-computing-skills/Deno DeployFull skill: 268 linesDeno Deploy — Edge Computing
You are an expert in Deno Deploy for building edge-first applications on Deno's globally distributed serverless platform.
Overview
Deno Deploy runs Deno applications across 35+ regions on a global edge network built on V8 isolates. It natively supports TypeScript, ESM imports via URLs, and the Web Standard APIs (Fetch, Streams, Crypto, etc.). Deno Deploy provides zero-config deployments from GitHub, a built-in KV store (Deno KV), and BroadcastChannel for cross-isolate communication.
Key constraints:
- CPU time: 50ms per request (free), 200ms (Pro)
- Memory: 512 MB per isolate
- Request body size: 20 MB max
- No
npm:specifiers for all packages — many Node.js packages work, but native addons do not - No filesystem writes (read-only access to deployed files via
Deno.readFile)
Core Concepts
Serve Handler
The entry point is Deno.serve():
Deno.serve((request: Request) => {
const url = new URL(request.url);
if (url.pathname === "/") {
return new Response("Hello from Deno Deploy", {
headers: { "content-type": "text/plain" },
});
}
return new Response("Not Found", { status: 404 });
});
Deno KV
A built-in, globally replicated key-value store that works both locally and on Deploy:
const kv = await Deno.openKv();
// Write
await kv.set(["users", "user-123"], { name: "Alice", role: "admin" });
// Read
const entry = await kv.get<{ name: string; role: string }>(["users", "user-123"]);
console.log(entry.value); // { name: "Alice", role: "admin" }
// List by prefix
const iter = kv.list<{ name: string }>({ prefix: ["users"] });
for await (const entry of iter) {
console.log(entry.key, entry.value);
}
// Atomic transactions
await kv
.atomic()
.check({ key: ["users", "user-123"], versionstamp: entry.versionstamp })
.set(["users", "user-123"], { name: "Alice", role: "superadmin" })
.commit();
BroadcastChannel
Communicate between isolates in different regions:
const channel = new BroadcastChannel("cache-invalidation");
channel.onmessage = (event: MessageEvent) => {
console.log("Invalidate cache for:", event.data.key);
localCache.delete(event.data.key);
};
// Trigger invalidation across all regions
channel.postMessage({ key: "product-42" });
Implementation Patterns
REST API with Oak
import { Application, Router } from "https://deno.land/x/oak@v12.6.1/mod.ts";
const kv = await Deno.openKv();
const router = new Router();
router.get("/api/items", async (ctx) => {
const items = [];
for await (const entry of kv.list({ prefix: ["items"] })) {
items.push({ id: entry.key[1], ...entry.value as Record<string, unknown> });
}
ctx.response.body = items;
});
router.post("/api/items", async (ctx) => {
const body = await ctx.request.body.json();
const id = crypto.randomUUID();
await kv.set(["items", id], { ...body, createdAt: new Date().toISOString() });
ctx.response.status = 201;
ctx.response.body = { id };
});
router.delete("/api/items/:id", async (ctx) => {
const id = ctx.params.id;
await kv.delete(["items", id]);
ctx.response.status = 204;
});
const app = new Application();
app.use(router.routes());
app.use(router.allowedMethods());
await app.listen({ port: 8000 });
Server-Sent Events
Deno.serve((request: Request) => {
if (new URL(request.url).pathname !== "/events") {
return new Response("Not Found", { status: 404 });
}
const body = new ReadableStream({
start(controller) {
const encoder = new TextEncoder();
let count = 0;
const interval = setInterval(() => {
if (count >= 50) {
clearInterval(interval);
controller.close();
return;
}
controller.enqueue(
encoder.encode(`data: ${JSON.stringify({ count: count++, ts: Date.now() })}\n\n`)
);
}, 1000);
},
});
return new Response(body, {
headers: {
"content-type": "text/event-stream",
"cache-control": "no-cache",
},
});
});
Rate Limiter with Deno KV
const kv = await Deno.openKv();
async function checkRateLimit(ip: string, limit = 60, windowMs = 60_000): Promise<boolean> {
const key = ["ratelimit", ip];
const now = Date.now();
const entry = await kv.get<{ count: number; windowStart: number }>(key);
if (!entry.value || now - entry.value.windowStart > windowMs) {
await kv.set(key, { count: 1, windowStart: now }, { expireIn: windowMs });
return true;
}
if (entry.value.count >= limit) {
return false;
}
await kv
.atomic()
.check(entry)
.set(key, { count: entry.value.count + 1, windowStart: entry.value.windowStart }, {
expireIn: windowMs,
})
.commit();
return true;
}
Deno.serve(async (request: Request) => {
const ip = request.headers.get("x-forwarded-for") ?? "unknown";
if (!(await checkRateLimit(ip))) {
return new Response("Too Many Requests", { status: 429 });
}
return new Response("OK");
});
Static File Serving with Edge Caching
Deno.serve(async (request: Request) => {
const url = new URL(request.url);
const filePath = `./static${url.pathname === "/" ? "/index.html" : url.pathname}`;
try {
const file = await Deno.readFile(filePath);
const ext = filePath.split(".").pop() ?? "";
const contentTypes: Record<string, string> = {
html: "text/html",
css: "text/css",
js: "application/javascript",
json: "application/json",
png: "image/png",
svg: "image/svg+xml",
};
return new Response(file, {
headers: {
"content-type": contentTypes[ext] ?? "application/octet-stream",
"cache-control": ext === "html" ? "no-cache" : "public, max-age=31536000, immutable",
},
});
} catch {
return new Response("Not Found", { status: 404 });
}
});
Best Practices
- Use
Deno.openKv()for state — it works locally for development and automatically uses the managed distributed store on Deploy. - Prefer URL imports or
deno.jsonimport maps overnode_modulesfor smaller deployments and better caching. - Use Web Standard APIs — Fetch, Request, Response, Headers, URL, Crypto, Streams are all natively supported and well-optimized.
- Set
expireInon KV entries for ephemeral data like sessions and rate-limit counters to avoid unbounded storage growth. - Use atomic transactions in KV for compare-and-swap operations to prevent race conditions across concurrent requests.
- Deploy via GitHub integration for automatic deployments on push — every branch gets a preview URL.
Common Pitfalls
- Importing Node.js packages that use native addons —
bcrypt,sharp,sqlite3and similar packages with C/C++ bindings will not work. Use WebCrypto for hashing, or Wasm-based alternatives. - Writing to the filesystem —
Deno.writeFileand similar calls fail on Deploy. Use Deno KV or external storage. - Relying on long-lived in-memory state — Isolates are ephemeral and may be evicted. Any state that must persist needs to go into KV.
- Not handling KV atomic transaction failures —
commit()returns{ ok: false }on version conflicts. Always check the result and retry if needed. - Exceeding CPU time limits — Tight loops over large datasets or expensive crypto operations can hit the 50ms/200ms ceiling. Break work into chunks or offload to queues.
- Assuming global consistency in KV reads — Deno KV provides eventual consistency across regions by default. Use the
consistency: "strong"option when you need read-after-write guarantees, understanding it routes to the primary region.
Core Philosophy
Deno Deploy embraces the Web Standard APIs as the foundation of server-side development. Fetch, Request, Response, URL, Headers, Crypto, and Streams are not polyfills here — they are the native runtime APIs. Writing code against these standards means your application logic is portable across Deno Deploy, Cloudflare Workers, and even browsers. Invest in learning the Web Standards rather than reaching for Node.js-isms.
Deno KV is the natural state layer for Deno Deploy. It works transparently in local development (backed by SQLite) and production (backed by a globally distributed database). Design your application to use KV from the start rather than planning to add persistence later. Atomic transactions, key expiration, and prefix-based listing cover most application state needs without an external database.
URL imports and import maps are Deno's dependency management story. They produce smaller deployments, eliminate node_modules, and make dependency provenance explicit. While npm: specifiers are supported for compatibility, native URL imports with pinned versions are the idiomatic Deno approach and optimize well for edge deployment.
Anti-Patterns
-
Importing npm packages with native addons — packages like
bcrypt,sharp, andsqlite3use C/C++ bindings that cannot run on Deno Deploy; use WebCrypto for hashing and Wasm-based alternatives for image processing. -
Writing to the filesystem —
Deno.writeFileand similar calls fail on Deploy because the filesystem is read-only; use Deno KV or external storage for persistence. -
Relying on long-lived in-memory state — isolates are ephemeral and may be evicted at any time; any state that must survive requests needs to be stored in KV.
-
Not checking KV atomic transaction results —
commit()returns{ ok: false }on version conflicts; ignoring this result silently drops writes in concurrent scenarios. -
Exceeding CPU time limits with tight loops — processing large datasets or performing expensive crypto operations in a single request hits the 50ms/200ms ceiling; break work into chunks or defer to background processing.
Install this skill directly: skilldb add edge-computing-skills
Related Skills
Cloudflare D1
Cloudflare D1 for running SQLite databases at the edge with SQL query support
Cloudflare Kv
Cloudflare Workers KV for globally distributed key-value storage at the edge
Cloudflare Workers
Cloudflare Workers for serverless edge compute using the V8 isolate model
Edge Auth
Authentication and authorization at the edge for securing requests before they reach the origin
Edge Caching
Edge caching strategies for optimizing content delivery and reducing origin load
Geolocation Routing
Geo-based routing and personalization for delivering localized content at the edge