Deno Runtime
Host Deno apps on Witchly.host — a secure-by-default, TypeScript-native JavaScript runtime. URL-based imports, built-in tooling, zero config.
languages (12 articles)
On This Page
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
- dash.witchly.host → Deploy → Languages → Deno Generic.
- On the Startup tab:
Git Repo Address— your repo URLBot js file— entrypoint (e.g.,main.ts,bot.js; defaultbot.js)
- 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
| Variable | Purpose |
|---|---|
GIT_ADDRESS | Git repo URL |
BRANCH | Git branch |
USER_UPLOAD | 1 to skip clone |
JS_FILE | Entrypoint (max 20 chars — use short names) |
USERNAME, ACCESS_TOKEN | Private 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.tslocally to verify.
Next steps
- Bun runtime for comparison
- Redis as a cache