Deno Runtime

Host Deno apps on Witchly.host — a secure-by-default, TypeScript-native JavaScript runtime. URL-based imports, built-in tooling, zero config.

Deno Runtime

Deno is a modern JavaScript runtime built by the creator of Node.js, Ryan Dahl. It’s secure by default (permissions opt-in), TypeScript native, and uses URL-based imports instead of node_modules / package.json. Excellent for clean, modern TS/JS apps without npm baggage.

Why Deno?

  • TypeScript works out of the box — no tsc, no transpile step.
  • No node_modules — imports are URLs or JSR/npm packages, cached globally.
  • Secure by default — file, network, and env access must be explicitly granted (--allow-net, --allow-read, etc.).
  • Modern standard library — one curated, versioned deno-lib at jsr:@std.
  • Built-in tooling — formatter, linter, test runner, bundler, LSP.

Quick deploy

  1. dash.witchly.hostDeployLanguagesDeno Generic.
  2. On the Startup tab:
    • Git Repo Address — your repo URL
    • Bot js file — entrypoint (e.g., main.ts, bot.js; default bot.js)
  3. Start.

Startup flow

./deno run {{JS_FILE}}

Note: the default startup doesn’t include --allow-* flags, which Deno requires for any non-trivial app. You’ll almost certainly want to edit the startup command to add permissions:

./deno run --allow-net --allow-read --allow-env {{JS_FILE}}

Or, for development only, ./deno run -A {{JS_FILE}} to allow everything.

Variables reference

VariablePurpose
GIT_ADDRESSGit repo URL
BRANCHGit branch
USER_UPLOAD1 to skip clone
JS_FILEEntrypoint (max 20 chars — use short names)
USERNAME, ACCESS_TOKENPrivate repo credentials

Dependencies

Deno imports from:

  • JSR — the modern, verified registry: import { serve } from "jsr:@std/http"
  • npm — prefixed: import express from "npm:express"
  • URL — legacy but still works: import { foo } from "https://deno.land/x/foo/mod.ts"

Dependencies are cached automatically on first run. Commit a deno.json to pin versions:

{
  "tasks": {
    "start": "deno run --allow-net main.ts"
  },
  "imports": {
    "@std/http": "jsr:@std/http@^1.0.0"
  }
}

HTTP server example

// main.ts
Deno.serve({ port: Number(Deno.env.get("SERVER_PORT")) }, (req) => {
  return new Response("Hello from Deno!")
})

Deploy, set JS_FILE = main.ts, and edit the startup to ./deno run --allow-net --allow-env main.ts.

Discord bot with Discordeno

// main.ts
import { createBot, Intents, startBot } from "https://deno.land/x/discordeno/mod.ts"

const bot = createBot({
  token: Deno.env.get("DISCORD_TOKEN")!,
  intents: Intents.Guilds | Intents.GuildMessages | Intents.MessageContent,
  events: {
    ready: () => console.log("Ready!"),
    messageCreate: (b, msg) => {
      if (msg.content === "!ping") b.helpers.sendMessage(msg.channelId, { content: "pong" })
    },
  },
})

await startBot(bot)

Startup: ./deno run --allow-net --allow-env main.ts.

Permissions granularity

Don’t use -A in production. Grant narrow permissions:

--allow-net=api.github.com,discord.com:443
--allow-read=./config,./data
--allow-write=./data
--allow-env=DISCORD_TOKEN

This is Deno’s security selling point — take advantage of it.

Troubleshooting

  • “PermissionDenied” — add the matching --allow-* flag to your startup command.
  • Can’t import npm package — some npm packages with native bindings don’t work; try a JSR alternative.
  • Startup fails on import — check for TypeScript errors; deno check main.ts locally to verify.

Next steps