Skip to content
Learni
View all tutorials
Deno

How to Deploy an App on Deno Deploy in 2026

Lire en français

Introduction

Deno Deploy is Deno's serverless edge computing platform, designed to simplify global deployments without any server setup. In 2026, it stands out for its blazing speed (cold starts under 1ms), seamless integration with the secure Deno runtime, and a free tier for small projects. Unlike Vercel or Cloudflare Workers, Deno Deploy runs TypeScript/Deno directly without bundling and supports KV, D1, and queues.

Why use it? For scalable REST APIs distributed across 40+ global edge locations with no upfront costs. Picture an API responding in 10ms from Paris or Tokyo. This beginner-friendly tutorial takes you from the basics (simple HTTP server) to advanced features (routes with persistent KV), using fully functional code. By the end, your app will be live in 10 minutes—bookmark-worthy for any Deno developer.

Prerequisites

  • Deno 2.0+ installed (run deno --version to check)
  • Free account on deno.com and a Deploy project created (dashboard.deno.com > Deployments > New Project)
  • API Token: dashboard > Settings > API Tokens > Create Token (name it "deploy-token")
  • Git installed (optional, for CI/CD)
  • Editor like VS Code with the Deno extension

Install deployctl

install.sh
curl -fsSL https://deno.land/x/install/install.sh | sh

# Restart the terminal or source the profile

# Install deployctl (official Deno Deploy CLI)
denop install -A -f --unstable https://deno.land/x/deploy@0.8.6/deployctl.ts

# Verify
 deployctl --version

This script installs Deno if it's missing, then deployctl for managing deployments. The --unstable flag is required for current edge features. Stick to the pure Deno ecosystem and avoid global npm versions; always verify with --version.

Set Up Your Local Project

Create an empty folder called mon-app-deno and navigate into it. Copy the following code snippets. Deno Deploy runs directly from a .ts file without building, but a deno.json config optimizes imports and locks versions for reproducibility.

Configure deno.json

deno.json
{
  "name": "mon-app-deno-deploy",
  "version": "0.1.0",
  "lock": true,
  "tasks": {
    "start": "deno run --allow-net --allow-env main.ts",
    "deploy": "deployctl deploy --project=monprojet main.ts"
  },
  "imports": {
    "$std/http/": "https://deno.land/std@0.224.0/http/"
  }
}

This file locks std dependencies to prevent version breaks on Deploy. The tasks make it easy to run deno task start locally or deploy. Replace monprojet with your dashboard project name; --lock true ensures identical deployments.

Basic HTTP Server

main.ts
import { serve } from "https://deno.land/std@0.224.0/http/server.ts";

serve((req: Request) => {
  const url = new URL(req.url);
  if (url.pathname === "/") {
    return new Response(JSON.stringify({ message: "Hello Deno Deploy!", timestamp: new Date().toISOString() }), {
      headers: { "Content-Type": "application/json" },
    });
  }
  return new Response("Not Found", { status: 404 });
}, { port: 8000 });

console.log("Serveur local sur http://localhost:8000");

This minimal handler uses std's serve for an HTTP server. It returns JSON on / with a live timestamp. Test locally with deno task start (requires --allow-net). Common pitfall: forgetting JSON headers causes fetch clients to fail; port 8000 avoids conflicts.

Test Locally

Run deno task start (or deno run --allow-net main.ts). Open http://localhost:8000. You'll see {"message":"Hello Deno Deploy!","timestamp":"2026-..."}. Stop with Ctrl+C. This step validates your code before deployment, saving costly iterations.

Add API Routes

main.ts
import { serve } from "https://deno.land/std@0.224.0/http/server.ts";

serve((req: Request) => {
  const url = new URL(req.url);
  const pathname = url.pathname;

  if (pathname === "/api/users") {
    if (req.method === "GET") {
      return new Response(JSON.stringify([
        { id: 1, name: "Alice" },
        { id: 2, name: "Bob" }
      ]), {
        headers: { "Content-Type": "application/json" },
      });
    }
    if (req.method === "POST") {
      const body = await req.json();
      return new Response(JSON.stringify({ message: "User créé", user: body }), {
        status: 201,
        headers: { "Content-Type": "application/json" },
      });
    }
  }

  if (pathname === "/") {
    return new Response(JSON.stringify({ message: "API prête !" }), {
      headers: { "Content-Type": "application/json" },
    });
  }

  return new Response("Not Found", { status: 404 });
});

Adds /api/users GET (mock list) and POST (echoes body). Uses req.json() parsing for inputs. Think of it like Express but native to Deno with zero dependencies. Pitfall: strictly handle req.method; skipping await on POST leads to empty body crashes.

Integrate KV for Persistence

main.ts
import { serve } from "https://deno.land/std@0.224.0/http/server.ts";

// KV on Deno Deploy (auto-created or via dashboard)
const kv = await Deno.openKv();

serve(async (req: Request) => {
  const url = new URL(req.url);
  const pathname = url.pathname;

  if (pathname === "/api/users" && req.method === "GET") {
    const users: Record<string, any>[] = [];
    for await (const entry of kv.list({ prefix: ["users"] })) {
      users.push(entry.value);
    }
    return new Response(JSON.stringify(users), {
      headers: { "Content-Type": "application/json" },
    });
  }

  if (pathname === "/api/users" && req.method === "POST") {
    const body = await req.json();
    const id = crypto.randomUUID();
    await kv.set(["users", id], { id, ...body });
    return new Response(JSON.stringify({ message: "User créé", id }), {
      status: 201,
      headers: { "Content-Type": "application/json" },
    });
  }

  return new Response(JSON.stringify({ error: "Not Found" }), { status: 404 });
});

Deno KV provides global, persistent, edge-native storage for users. Deno.openKv() works automatically on Deploy. GET iterates and lists; POST generates UUID and sets. Like Redis but serverless and free. Pitfall: handler must be async for KV; skipping await kv.set loses data.

Deploy to Deno Deploy

deploy.sh
# Set env vars (add to .env or shell)
export DENO_DEPLOY_TOKEN="your_dashboard_token"
export DENO_PROJECT_NAME="monprojet"

# Dry-run to validate
 deployctl deploy --project=$DENO_PROJECT_NAME main.ts --dry-run

# Deploy (excludes node_modules, .git)
 deployctl deploy --project=$DENO_PROJECT_NAME main.ts --exclude="*.md,node_modules"

# Live URL: https://monprojet.deno.dev

# Logs
 deployctl logs --project=$DENO_PROJECT_NAME --watch

deployctl pushes to the edge in seconds. Use --dry-run to validate without deploying. Keep the token secure in env vars. Auto URL: projet.deno.dev. Pitfall: expired token causes 401; --exclude skips unnecessary files, shrinking bundle size by 90%.

Best Practices

  • Locks and versions: Always use deno.json with lock: true for stable deployments.
  • Minimal permissions: Local uses --allow-net --allow-env; Deploy handles it automatically.
  • CORS headers: Add Access-Control-Allow-Origin: * for frontend APIs.
  • Monitoring: Use deployctl logs --watch and dashboard metrics.
  • CI/CD: Integrate GitHub Actions with deployctl for automated deploys.

Common Errors to Avoid

  • Invalid token: 401 on deploy → recreate with Deploy scopes.
  • Broken imports: Changing std versions → lock with deno.json.
  • Slow cold starts: Avoid heavy deps; std is edge-optimized.
  • Non-async KV: Forgetting await → data not persisted, sync handler crashes.

Next Steps

Master advanced Deno Deploy: queues, D1 SQL, custom domains. Check the official Deno Deploy docs. For expert training, explore our Learni courses on Deno and serverless. Next level: full-stack APIs with Fresh + Deploy.