Utilizziamo i cookie per migliorare la tua esperienza sul sito
CodeWorlds
Torna alle collezioni
Guide33 min read

Deno

Deno is a secure runtime for JavaScript and TypeScript from the creator of Node.js with native TypeScript support, granular permissions, and modern APIs.

Deno - Bezpieczny Runtime JavaScript i TypeScript

Czym jest Deno?

Deno to nowoczesny runtime JavaScript i TypeScript stworzony przez Ryana Dahla - tego samego programistę, który stworzył Node.js. Po latach doświadczeń z Node.js, Ryan dostrzegł fundamentalne problemy w architekturze Node (brak bezpieczeństwa, package.json, node_modules) i postanowił stworzyć runtime od nowa, naprawiając te błędy.

Deno oferuje natywne wsparcie TypeScript bez konfiguracji, granularne uprawnienia bezpieczeństwa (domyślnie program nie ma dostępu do niczego), wbudowane narzędzia deweloperskie (formatter, linter, test runner) oraz zgodność z Web API. Od wersji 2.0 Deno jest w pełni kompatybilny z npm i Node.js, co pozwala na płynną migrację istniejących projektów.

Nazwa "Deno" to anagram słowa "Node" - symbolizuje to ewolucję i przeprojektowanie koncepcji runtime JavaScript.

Dlaczego Deno?

Kluczowe zalety Deno

  1. Bezpieczeństwo by Default - Program domyślnie nie ma dostępu do sieci, plików ani zmiennych środowiskowych
  2. Natywny TypeScript - Uruchamia .ts bez kompilacji i konfiguracji
  3. Wbudowane narzędzia - Formatter, linter, test runner, bundler, REPL
  4. Web API - Zgodność z przeglądarkowymi API (fetch, URL, Streams)
  5. ESM Only - Tylko nowoczesne moduły ES, bez CommonJS
  6. Single Executable - Kompilacja do jednego pliku wykonywalnego
  7. npm Compatibility - Pełna kompatybilność z pakietami npm
  8. Deno Deploy - Serverless edge deployment w 35+ regionach

Deno vs Node.js vs Bun

CechaDenoNode.jsBun
TypeScriptNatywnieWymaga ts-node/tsxNatywnie
PermissionsGranularneBrak (pełny dostęp)Brak
ES ModulesJedyne wspieraneOpcjonalneDomyślne
CommonJSCzęściowe (compat)NatywneNatywne
package.jsonOpcjonalnyWymaganyWymagany
node_modulesOpcjonalneWymaganeWymagane
Web APIPełna zgodnośćCzęściowaCzęściowa
BundlerWbudowanyWymaga webpack/viteWbudowany
Test RunnerWbudowanyWymaga Jest/VitestWbudowany
npm Support✅ (od 2.0)
SzybkośćSzybkiŚredniNajszybszy
Edge DeployDeno DeployVercel/Cloudflare-

Kiedy wybrać Deno?

Deno jest idealny gdy:

  • Zależy Ci na bezpieczeństwie (sandboxing, audyt)
  • Chcesz natywny TypeScript bez konfiguracji
  • Budujesz edge functions (Deno Deploy)
  • Preferujesz wbudowane narzędzia
  • Chcesz Web-compatible API
  • Tworzysz CLI tools (single executable)

Rozważ alternatywy gdy:

  • Masz duży istniejący projekt Node.js
  • Potrzebujesz specyficznych pakietów npm bez ESM
  • Maksymalna szybkość jest priorytetem (Bun)
  • Twój zespół zna tylko Node.js

Instalacja i konfiguracja

Instalacja Deno

Code
Bash
# macOS / Linux (curl)
curl -fsSL https://deno.land/install.sh | sh

# macOS (Homebrew)
brew install deno

# Windows (PowerShell)
irm https://deno.land/install.ps1 | iex

# Windows (Chocolatey)
choco install deno

# Windows (Scoop)
scoop install deno

# Weryfikacja instalacji
deno --version
# deno 2.x.x
# v8 12.x.x
# typescript 5.x.x

Aktualizacja Deno

Code
Bash
# Aktualizacja do najnowszej wersji
deno upgrade

# Aktualizacja do konkretnej wersji
deno upgrade --version 2.0.0

Konfiguracja VS Code

Code
Bash
# Zainstaluj oficjalne rozszerzenie
code --install-extension denoland.vscode-deno
.vscode/settings.json
JSON
// .vscode/settings.json
{
  "deno.enable": true,
  "deno.lint": true,
  "deno.unstable": false,
  "editor.defaultFormatter": "denoland.vscode-deno",
  "[typescript]": {
    "editor.defaultFormatter": "denoland.vscode-deno"
  },
  "[typescriptreact]": {
    "editor.defaultFormatter": "denoland.vscode-deno"
  }
}

deno.json - Konfiguracja projektu

Code
JSON
{
  "name": "@myorg/my-project",
  "version": "1.0.0",
  "exports": "./mod.ts",

  "tasks": {
    "dev": "deno run --watch --allow-net --allow-read main.ts",
    "test": "deno test --allow-read",
    "lint": "deno lint",
    "fmt": "deno fmt",
    "check": "deno check main.ts",
    "compile": "deno compile --allow-net --allow-read -o app main.ts"
  },

  "imports": {
    "@std/": "jsr:@std/",
    "oak": "jsr:@oak/oak@^17.0.0",
    "zod": "npm:zod@^3.23.0",
    "~/": "./src/"
  },

  "compilerOptions": {
    "strict": true,
    "lib": ["deno.window", "dom"]
  },

  "lint": {
    "include": ["src/"],
    "exclude": ["src/generated/"],
    "rules": {
      "tags": ["recommended"],
      "include": ["no-unused-vars"]
    }
  },

  "fmt": {
    "useTabs": false,
    "lineWidth": 100,
    "indentWidth": 2,
    "semiColons": false,
    "singleQuote": true
  },

  "test": {
    "include": ["tests/"]
  }
}

System uprawnień (Permissions)

Domyślnie program Deno nie ma dostępu do niczego - musi jawnie poprosić o każde uprawnienie.

Dostępne uprawnienia

Code
Bash
# Dostęp do sieci
--allow-net                    # Wszystkie hosty
--allow-net=api.example.com    # Tylko konkretny host
--allow-net=:8080              # Tylko port 8080

# Dostęp do plików
--allow-read                   # Odczyt wszystkich plików
--allow-read=/data,./config    # Tylko konkretne ścieżki
--allow-write                  # Zapis wszystkich plików
--allow-write=/tmp             # Tylko /tmp

# Zmienne środowiskowe
--allow-env                    # Wszystkie zmienne
--allow-env=API_KEY,DB_URL     # Tylko konkretne zmienne

# Uruchamianie procesów
--allow-run                    # Wszystkie procesy
--allow-run=git,npm            # Tylko git i npm

# FFI (Foreign Function Interface)
--allow-ffi                    # Ładowanie bibliotek natywnych

# System
--allow-sys                    # Informacje o systemie

# High-resolution time
--allow-hrtime                 # Dokładne pomiary czasu

# Wszystkie uprawnienia (development only!)
--allow-all                    # Lub -A

Przykłady użycia uprawnień

Code
Bash
# Serwer HTTP z odczytem plików
deno run --allow-net --allow-read server.ts

# CLI tool z dostępem do git
deno run --allow-run=git --allow-read=. deploy.ts

# API client z konkretnym hostem
deno run --allow-net=api.github.com github-client.ts

# Skrypt z dostępem do .env
deno run --allow-read=.env --allow-env config.ts

Programowe sprawdzanie uprawnień

Code
TypeScript
// Sprawdź uprawnienie przed użyciem
const netStatus = await Deno.permissions.query({
  name: "net",
  host: "api.example.com"
})

if (netStatus.state === "granted") {
  // Mamy dostęp
  await fetch("https://api.example.com/data")
} else if (netStatus.state === "prompt") {
  // Możemy poprosić
  const result = await Deno.permissions.request({
    name: "net",
    host: "api.example.com"
  })
  if (result.state === "granted") {
    await fetch("https://api.example.com/data")
  }
}

Natywny TypeScript

Zero konfiguracji

TSmain.ts
TypeScript
// main.ts - po prostu uruchom: deno run main.ts
interface User {
  id: number
  name: string
  email: string
  createdAt: Date
}

interface ApiResponse<T> {
  data: T
  status: number
  message: string
}

async function fetchUser(id: number): Promise<ApiResponse<User>> {
  const response = await fetch(`https://api.example.com/users/${id}`)
  const data = await response.json()
  return {
    data: data as User,
    status: response.status,
    message: response.ok ? "Success" : "Error"
  }
}

const result = await fetchUser(1)
console.log(result.data.name)
Code
Bash
# Uruchom bez żadnej konfiguracji
deno run --allow-net main.ts

Type checking

Code
Bash
# Sprawdź typy bez uruchamiania
deno check main.ts

# Sprawdź typy w trybie watch
deno check --watch main.ts

# Sprawdź wszystkie pliki
deno check **/*.ts

Import i zarządzanie zależnościami

Import z różnych źródeł

Code
TypeScript
// Standard library (JSR)
import { join } from "jsr:@std/path"
import { serve } from "jsr:@std/http"
import { assertEquals } from "jsr:@std/assert"

// npm packages
import { z } from "npm:zod@^3.23.0"
import express from "npm:express@^4.18.0"
import _ from "npm:lodash"

// Direct URL (legacy, ale nadal wspierane)
import { Application } from "https://deno.land/x/oak@v17.0.0/mod.ts"

// Lokalne pliki
import { config } from "./config.ts"
import { utils } from "~/utils/mod.ts"  // Z import map

// Node.js built-in modules
import process from "node:process"
import fs from "node:fs"
import path from "node:path"

Import Maps w deno.json

Code
JSON
{
  "imports": {
    // Standard library
    "@std/": "jsr:@std/",

    // npm packages
    "zod": "npm:zod@^3.23.0",
    "hono": "npm:hono@^4.0.0",
    "drizzle-orm": "npm:drizzle-orm@^0.30.0",

    // Aliasy lokalne
    "~/": "./src/",
    "@components/": "./src/components/",
    "@utils/": "./src/utils/",

    // Konkretne wersje
    "lodash": "npm:lodash@4.17.21"
  }
}
Code
TypeScript
// Teraz możesz importować krócej
import { z } from "zod"
import { Hono } from "hono"
import { UserService } from "~/services/user.ts"
import { Button } from "@components/Button.tsx"

JSR - JavaScript Registry

JSR to nowoczesny registry dla pakietów JavaScript/TypeScript, stworzony przez zespół Deno.

Code
Bash
# Publikowanie pakietu na JSR
deno publish

# Dodawanie pakietu z JSR
deno add @std/http
deno add @oak/oak
Code
TypeScript
// Import z JSR
import { serve } from "jsr:@std/http@1.0.0"
import { assertEquals } from "jsr:@std/assert@1.0.0"

Web API i Deno API

Fetch API (zgodne z przeglądarką)

Code
TypeScript
// GET request
const response = await fetch("https://api.example.com/users")
const users = await response.json()

// POST request z JSON
const createResponse = await fetch("https://api.example.com/users", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "Authorization": `Bearer ${token}`
  },
  body: JSON.stringify({
    name: "Jan Kowalski",
    email: "jan@example.com"
  })
})

// Streaming response
const streamResponse = await fetch("https://api.example.com/stream")
const reader = streamResponse.body?.getReader()

while (true) {
  const { done, value } = await reader!.read()
  if (done) break
  console.log(new TextDecoder().decode(value))
}

URL API

Code
TypeScript
const url = new URL("https://example.com/path?foo=bar")

url.searchParams.set("page", "2")
url.searchParams.append("filter", "active")

console.log(url.href)      // https://example.com/path?foo=bar&page=2&filter=active
console.log(url.pathname)  // /path
console.log(url.hostname)  // example.com

Web Streams

Code
TypeScript
// ReadableStream
const readable = new ReadableStream({
  start(controller) {
    controller.enqueue("Hello ")
    controller.enqueue("World!")
    controller.close()
  }
})

// TransformStream
const uppercase = new TransformStream({
  transform(chunk, controller) {
    controller.enqueue(chunk.toUpperCase())
  }
})

// Pipe streams
const result = readable.pipeThrough(uppercase)

for await (const chunk of result) {
  console.log(chunk)  // "HELLO " "WORLD!"
}

Deno-specific API

Code
TypeScript
// Odczyt plików
const content = await Deno.readTextFile("./config.json")
const bytes = await Deno.readFile("./image.png")

// Zapis plików
await Deno.writeTextFile("./output.txt", "Hello World")
await Deno.writeFile("./data.bin", new Uint8Array([1, 2, 3]))

// Informacje o pliku
const stat = await Deno.stat("./file.txt")
console.log(stat.isFile, stat.size, stat.mtime)

// Lista plików
for await (const entry of Deno.readDir("./src")) {
  console.log(entry.name, entry.isDirectory)
}

// Zmienne środowiskowe
const apiKey = Deno.env.get("API_KEY")
Deno.env.set("DEBUG", "true")

// Argumenty CLI
const args = Deno.args  // ["--port", "3000"]

// Uruchamianie procesów
const command = new Deno.Command("git", {
  args: ["status"],
  stdout: "piped",
  stderr: "piped"
})

const { code, stdout, stderr } = await command.output()
console.log(new TextDecoder().decode(stdout))

HTTP Server

Deno.serve (wbudowany)

Code
TypeScript
// Najprostszy serwer
Deno.serve((req: Request) => {
  return new Response("Hello Deno!")
})

// Z konfiguracją
Deno.serve({
  port: 3000,
  hostname: "0.0.0.0",
  onListen: ({ port, hostname }) => {
    console.log(`Server running at http://${hostname}:${port}`)
  }
}, handler)

// Handler z routingiem
function handler(req: Request): Response {
  const url = new URL(req.url)

  switch (url.pathname) {
    case "/":
      return new Response("Home")

    case "/api/users":
      if (req.method === "GET") {
        return Response.json([{ id: 1, name: "Jan" }])
      }
      if (req.method === "POST") {
        return Response.json({ created: true }, { status: 201 })
      }
      return new Response("Method not allowed", { status: 405 })

    default:
      return new Response("Not found", { status: 404 })
  }
}

Streaming Response

Code
TypeScript
Deno.serve((req: Request) => {
  const body = new ReadableStream({
    async start(controller) {
      for (let i = 0; i < 5; i++) {
        controller.enqueue(new TextEncoder().encode(`Event ${i}\n`))
        await new Promise(r => setTimeout(r, 1000))
      }
      controller.close()
    }
  })

  return new Response(body, {
    headers: {
      "Content-Type": "text/event-stream",
      "Cache-Control": "no-cache"
    }
  })
})

Server-Sent Events

Code
TypeScript
Deno.serve((req: Request) => {
  const url = new URL(req.url)

  if (url.pathname === "/events") {
    const stream = new ReadableStream({
      start(controller) {
        const encoder = new TextEncoder()

        const interval = setInterval(() => {
          const event = `data: ${JSON.stringify({ time: new Date() })}\n\n`
          controller.enqueue(encoder.encode(event))
        }, 1000)

        // Cleanup on close
        req.signal.addEventListener("abort", () => {
          clearInterval(interval)
          controller.close()
        })
      }
    })

    return new Response(stream, {
      headers: {
        "Content-Type": "text/event-stream",
        "Cache-Control": "no-cache",
        "Connection": "keep-alive"
      }
    })
  }

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

Frameworki Web

Hono (rekomendowany)

TSmain.ts
TypeScript
// main.ts
import { Hono } from "npm:hono"
import { cors } from "npm:hono/cors"
import { logger } from "npm:hono/logger"
import { z } from "npm:zod"
import { zValidator } from "npm:@hono/zod-validator"

const app = new Hono()

// Middleware
app.use("*", logger())
app.use("/api/*", cors())

// Routes
app.get("/", (c) => c.text("Hello Hono!"))

app.get("/api/users", async (c) => {
  const users = await db.query.users.findMany()
  return c.json(users)
})

// Walidacja z Zod
const createUserSchema = z.object({
  name: z.string().min(2),
  email: z.string().email()
})

app.post(
  "/api/users",
  zValidator("json", createUserSchema),
  async (c) => {
    const body = c.req.valid("json")
    const user = await db.insert(users).values(body).returning()
    return c.json(user, 201)
  }
)

// Groups
const api = new Hono()
api.get("/posts", (c) => c.json([]))
api.get("/posts/:id", (c) => c.json({ id: c.req.param("id") }))
app.route("/api", api)

// Error handling
app.onError((err, c) => {
  console.error(err)
  return c.json({ error: "Internal Server Error" }, 500)
})

// Start
Deno.serve(app.fetch)

Oak

Code
TypeScript
import { Application, Router } from "jsr:@oak/oak"

const router = new Router()

router.get("/", (ctx) => {
  ctx.response.body = "Hello Oak!"
})

router.get("/api/users", async (ctx) => {
  const users = await fetchUsers()
  ctx.response.body = users
})

router.post("/api/users", async (ctx) => {
  const body = await ctx.request.body.json()
  const user = await createUser(body)
  ctx.response.status = 201
  ctx.response.body = user
})

const app = new Application()

// Logger middleware
app.use(async (ctx, next) => {
  const start = Date.now()
  await next()
  const ms = Date.now() - start
  console.log(`${ctx.request.method} ${ctx.request.url} - ${ms}ms`)
})

app.use(router.routes())
app.use(router.allowedMethods())

console.log("Server running on http://localhost:8000")
await app.listen({ port: 8000 })

Fresh (Full-stack framework)

Fresh to oficjalny full-stack framework od Deno z island architecture i zero JS by default.

Code
Bash
# Tworzenie projektu Fresh
deno run -A -r https://fresh.deno.dev my-project
cd my-project
deno task start
TSroutes/index.tsx
TypeScript
// routes/index.tsx
import { Handlers, PageProps } from "$fresh/server.ts"

interface Data {
  posts: Array<{ id: number; title: string }>
}

export const handler: Handlers<Data> = {
  async GET(req, ctx) {
    const posts = await fetchPosts()
    return ctx.render({ posts })
  }
}

export default function Home({ data }: PageProps<Data>) {
  return (
    <div>
      <h1>Blog</h1>
      <ul>
        {data.posts.map((post) => (
          <li key={post.id}>
            <a href={`/posts/${post.id}`}>{post.title}</a>
          </li>
        ))}
      </ul>
    </div>
  )
}

// islands/Counter.tsx - Interactive component (hydrated)
import { useState } from "preact/hooks"

export default function Counter() {
  const [count, setCount] = useState(0)

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>+</button>
    </div>
  )
}

Testowanie

Wbudowany test runner

TSmath.ts
TypeScript
// math.ts
export function add(a: number, b: number): number {
  return a + b
}

export function divide(a: number, b: number): number {
  if (b === 0) throw new Error("Division by zero")
  return a / b
}

// math_test.ts
import { assertEquals, assertThrows } from "jsr:@std/assert"
import { add, divide } from "./math.ts"

Deno.test("add function", () => {
  assertEquals(add(2, 3), 5)
  assertEquals(add(-1, 1), 0)
  assertEquals(add(0, 0), 0)
})

Deno.test("divide function", () => {
  assertEquals(divide(10, 2), 5)
  assertEquals(divide(7, 2), 3.5)
})

Deno.test("divide by zero throws", () => {
  assertThrows(
    () => divide(1, 0),
    Error,
    "Division by zero"
  )
})

// Async test
Deno.test("async fetch", async () => {
  const response = await fetch("https://api.example.com/data")
  assertEquals(response.status, 200)
})

// Test z cleanup
Deno.test({
  name: "database test",
  async fn() {
    const db = await connectDb()
    try {
      const result = await db.query("SELECT 1")
      assertEquals(result.rows.length, 1)
    } finally {
      await db.close()
    }
  },
  sanitizeResources: false,
  sanitizeOps: false
})
Code
Bash
# Uruchom wszystkie testy
deno test

# Testy z uprawnieniami
deno test --allow-net --allow-read

# Konkretny plik
deno test math_test.ts

# Watch mode
deno test --watch

# Coverage
deno test --coverage=coverage
deno coverage coverage

Mocking

Code
TypeScript
import { stub, assertSpyCalls } from "jsr:@std/testing/mock"

Deno.test("mocking fetch", async () => {
  const fetchStub = stub(
    globalThis,
    "fetch",
    () => Promise.resolve(new Response(JSON.stringify({ id: 1 })))
  )

  try {
    const user = await fetchUser(1)
    assertEquals(user.id, 1)
    assertSpyCalls(fetchStub, 1)
  } finally {
    fetchStub.restore()
  }
})

Wbudowane narzędzia

Formatter

Code
Bash
# Formatuj wszystkie pliki
deno fmt

# Sprawdź bez modyfikacji
deno fmt --check

# Konkretne pliki
deno fmt src/main.ts

# Ignoruj foldery
deno fmt --ignore=vendor,node_modules

Linter

Code
Bash
# Lint wszystkich plików
deno lint

# Konkretne pliki
deno lint src/

# Pokaż reguły
deno lint --rules

Bundler

Code
Bash
# Bundle do jednego pliku
deno bundle main.ts bundle.js

# Bundle z minifikacją (użyj esbuild)
deno run -A npm:esbuild main.ts --bundle --minify --outfile=out.js

Kompilacja do wykonywalnego pliku

Code
Bash
# Kompiluj z embedded permissions
deno compile --allow-net --allow-read main.ts

# Z nazwą i target
deno compile --output app --target x86_64-unknown-linux-gnu main.ts

# Dostępne targets:
# x86_64-unknown-linux-gnu
# x86_64-pc-windows-msvc
# x86_64-apple-darwin
# aarch64-apple-darwin

Deno Deploy

Deno Deploy to serverless edge platform od twórców Deno.

Deploy z CLI

Code
Bash
# Zaloguj się
deno install -gArf jsr:@deno/deployctl

# Deploy
deployctl deploy --project=my-project main.ts

# Z entrypoint
deployctl deploy --project=my-project --entrypoint=server.ts

Deploy z GitHub Actions

.github/workflows/deploy.yml
YAML
# .github/workflows/deploy.yml
name: Deploy

on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    permissions:
      id-token: write
      contents: read

    steps:
      - uses: actions/checkout@v4

      - name: Install Deno
        uses: denoland/setup-deno@v1

      - name: Deploy to Deno Deploy
        uses: denoland/deployctl@v1
        with:
          project: my-project
          entrypoint: main.ts

Konfiguracja projektu dla Deploy

TSmain.ts
TypeScript
// main.ts - kompatybilny z Deno Deploy
Deno.serve((req: Request) => {
  const url = new URL(req.url)

  if (url.pathname === "/api/hello") {
    return Response.json({ message: "Hello from the edge!" })
  }

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

Deno KV (Key-Value Store)

Code
TypeScript
// Otwórz KV store (lokalnie lub na Deno Deploy)
const kv = await Deno.openKv()

// Set
await kv.set(["users", "user-123"], {
  name: "Jan",
  email: "jan@example.com"
})

// Get
const result = await kv.get<User>(["users", "user-123"])
console.log(result.value)  // { name: "Jan", ... }

// Delete
await kv.delete(["users", "user-123"])

// List z prefixem
const entries = kv.list<User>({ prefix: ["users"] })
for await (const entry of entries) {
  console.log(entry.key, entry.value)
}

// Atomic operations
const res = await kv.atomic()
  .check({ key: ["users", "user-123"], versionstamp: result.versionstamp })
  .set(["users", "user-123"], { ...result.value, name: "Jan Updated" })
  .commit()

if (!res.ok) {
  console.log("Conflict - someone else modified the data")
}

Kompatybilność z Node.js

Import Node.js modules

JSNode.js
JavaScript
// Node.js built-in modules
import process from "node:process"
import fs from "node:fs/promises"
import path from "node:path"
import { Buffer } from "node:buffer"

// Użycie
console.log(process.env.HOME)
const content = await fs.readFile("./file.txt", "utf-8")
const fullPath = path.join(process.cwd(), "data")

npm packages

Code
TypeScript
// Import z npm:
import express from "npm:express@4"
import _ from "npm:lodash"
import { PrismaClient } from "npm:@prisma/client"

// W deno.json (preferowane)
{
  "imports": {
    "express": "npm:express@^4.18.0",
    "lodash": "npm:lodash@^4.17.0"
  }
}

// Potem import normalnie
import express from "express"

Migracja z Node.js

Code
Bash
# Uruchom Node.js projekt w Deno
DENO_FUTURE=1 deno run --allow-all npm:your-script

# Lub użyj compatibility mode
deno run --node-modules-dir --allow-all main.ts

Best Practices

Struktura projektu

Code
TEXT
my-deno-project/
├── deno.json           # Konfiguracja
├── deno.lock           # Lockfile
├── main.ts             # Entry point
├── deps.ts             # (opcjonalne) Centralne zależności
├── src/
│   ├── mod.ts          # Public API
│   ├── routes/
│   │   ├── users.ts
│   │   └── posts.ts
│   ├── services/
│   │   └── database.ts
│   └── utils/
│       └── helpers.ts
├── tests/
│   ├── routes_test.ts
│   └── services_test.ts
└── scripts/
    └── seed.ts

Centralizacja zależności (opcjonalne)

TSdeps.ts
TypeScript
// deps.ts - jeden plik ze wszystkimi zewnętrznymi zależnościami
export { z } from "npm:zod@^3.23.0"
export { Hono } from "npm:hono@^4.0.0"
export { assertEquals, assertThrows } from "jsr:@std/assert"
export { join, dirname } from "jsr:@std/path"

// Używanie w innych plikach
import { z, Hono } from "./deps.ts"

Type-safe environment variables

TSenv.ts
TypeScript
// env.ts
import { z } from "npm:zod"

const envSchema = z.object({
  DATABASE_URL: z.string().url(),
  API_KEY: z.string().min(1),
  PORT: z.coerce.number().default(3000),
  NODE_ENV: z.enum(["development", "production", "test"]).default("development")
})

export const env = envSchema.parse(Deno.env.toObject())

FAQ - Najczęściej zadawane pytania

Czy Deno może zastąpić Node.js?

Dla nowych projektów - tak. Deno 2.0 jest w pełni kompatybilny z npm i Node.js API, więc migracja jest możliwa. Dla istniejących dużych projektów Node.js migracja może wymagać wysiłku.

Dlaczego domyślnie brak dostępu do niczego?

To fundamentalna zasada bezpieczeństwa. Skrypt pobrany z internetu nie powinien mieć automatycznie dostępu do Twoich plików czy sieci. Musisz jawnie przyznać uprawnienia.

Czy Deno jest szybszy od Node.js?

W większości przypadków Deno i Node.js mają podobną wydajność. Bun jest szybszy od obu. Deno wyróżnia się natywnym TypeScript i wbudowanymi narzędziami.

Czy mogę używać pakietów npm w Deno?

Tak! Od Deno 2.0 jest pełna kompatybilność z npm. Używaj npm:package-name lub skonfiguruj import maps w deno.json.

Co to jest JSR?

JSR (JavaScript Registry) to nowy registry pakietów od zespołu Deno. Wspiera TypeScript natywnie i oferuje lepsze DX niż npm. Pakiety publikujesz przez deno publish.

Podsumowanie

Deno to nowoczesny runtime JavaScript/TypeScript, który naprawia fundamentalne problemy Node.js:

  • Bezpieczeństwo - Granularne uprawnienia, sandboxing by default
  • TypeScript - Natywne wsparcie bez konfiguracji
  • Web Standards - Zgodność z przeglądarkowymi API
  • Wbudowane narzędzia - Formatter, linter, test runner, bundler
  • npm Compatibility - Pełna kompatybilność od wersji 2.0
  • Deno Deploy - Edge deployment w 35+ regionach

Deno jest idealny dla nowych projektów, CLI tools, edge functions i wszędzie tam, gdzie zależy Ci na bezpieczeństwie i nowoczesnym DX.


Deno - secure JavaScript and TypeScript runtime

What is Deno?

Deno is a modern JavaScript and TypeScript runtime created by Ryan Dahl - the same developer who created Node.js. After years of experience with Node.js, Ryan identified fundamental problems in Node's architecture (lack of security, package.json, node_modules) and decided to build a runtime from scratch, fixing those mistakes.

Deno offers native TypeScript support without configuration, granular security permissions (by default a program has no access to anything), built-in developer tools (formatter, linter, test runner), and Web API compatibility. Since version 2.0 Deno is fully compatible with npm and Node.js, which allows for a smooth migration of existing projects.

The name "Deno" is an anagram of the word "Node" - symbolizing the evolution and redesign of the JavaScript runtime concept.

Why Deno?

Key advantages of Deno

  1. Security by default - A program has no access to the network, files, or environment variables by default
  2. Native TypeScript - Runs .ts files without compilation or configuration
  3. Built-in tools - Formatter, linter, test runner, bundler, REPL
  4. Web API - Compatibility with browser APIs (fetch, URL, Streams)
  5. ESM only - Only modern ES modules, no CommonJS
  6. Single executable - Compile to a single executable file
  7. npm compatibility - Full compatibility with npm packages
  8. Deno Deploy - Serverless edge deployment in 35+ regions

Deno vs Node.js vs Bun

FeatureDenoNode.jsBun
TypeScriptNativeRequires ts-node/tsxNative
PermissionsGranularNone (full access)None
ES ModulesOnly supportedOptionalDefault
CommonJSPartial (compat)NativeNative
package.jsonOptionalRequiredRequired
node_modulesOptionalRequiredRequired
Web APIFull compatibilityPartialPartial
BundlerBuilt-inRequires webpack/viteBuilt-in
Test RunnerBuilt-inRequires Jest/VitestBuilt-in
npm Support✅ (since 2.0)
SpeedFastMediumFastest
Edge DeployDeno DeployVercel/Cloudflare-

When to choose Deno?

Deno is ideal when:

  • Security matters to you (sandboxing, auditing)
  • You want native TypeScript without configuration
  • You are building edge functions (Deno Deploy)
  • You prefer built-in tools
  • You want Web-compatible APIs
  • You are creating CLI tools (single executable)

Consider alternatives when:

  • You have a large existing Node.js project
  • You need specific npm packages without ESM support
  • Maximum speed is a priority (Bun)
  • Your team only knows Node.js

Installation and configuration

Installing Deno

Code
Bash
# macOS / Linux (curl)
curl -fsSL https://deno.land/install.sh | sh

# macOS (Homebrew)
brew install deno

# Windows (PowerShell)
irm https://deno.land/install.ps1 | iex

# Windows (Chocolatey)
choco install deno

# Windows (Scoop)
scoop install deno

# Verify installation
deno --version
# deno 2.x.x
# v8 12.x.x
# typescript 5.x.x

Updating Deno

Code
Bash
# Update to the latest version
deno upgrade

# Update to a specific version
deno upgrade --version 2.0.0

VS Code configuration

Code
Bash
# Install the official extension
code --install-extension denoland.vscode-deno
.vscode/settings.json
JSON
// .vscode/settings.json
{
  "deno.enable": true,
  "deno.lint": true,
  "deno.unstable": false,
  "editor.defaultFormatter": "denoland.vscode-deno",
  "[typescript]": {
    "editor.defaultFormatter": "denoland.vscode-deno"
  },
  "[typescriptreact]": {
    "editor.defaultFormatter": "denoland.vscode-deno"
  }
}

deno.json - project configuration

Code
JSON
{
  "name": "@myorg/my-project",
  "version": "1.0.0",
  "exports": "./mod.ts",

  "tasks": {
    "dev": "deno run --watch --allow-net --allow-read main.ts",
    "test": "deno test --allow-read",
    "lint": "deno lint",
    "fmt": "deno fmt",
    "check": "deno check main.ts",
    "compile": "deno compile --allow-net --allow-read -o app main.ts"
  },

  "imports": {
    "@std/": "jsr:@std/",
    "oak": "jsr:@oak/oak@^17.0.0",
    "zod": "npm:zod@^3.23.0",
    "~/": "./src/"
  },

  "compilerOptions": {
    "strict": true,
    "lib": ["deno.window", "dom"]
  },

  "lint": {
    "include": ["src/"],
    "exclude": ["src/generated/"],
    "rules": {
      "tags": ["recommended"],
      "include": ["no-unused-vars"]
    }
  },

  "fmt": {
    "useTabs": false,
    "lineWidth": 100,
    "indentWidth": 2,
    "semiColons": false,
    "singleQuote": true
  },

  "test": {
    "include": ["tests/"]
  }
}

Permissions system

By default, a Deno program has no access to anything - it must explicitly request every permission.

Available permissions

Code
Bash
# Network access
--allow-net                    # All hosts
--allow-net=api.example.com    # Only a specific host
--allow-net=:8080              # Only port 8080

# File access
--allow-read                   # Read all files
--allow-read=/data,./config    # Only specific paths
--allow-write                  # Write all files
--allow-write=/tmp             # Only /tmp

# Environment variables
--allow-env                    # All variables
--allow-env=API_KEY,DB_URL     # Only specific variables

# Running processes
--allow-run                    # All processes
--allow-run=git,npm            # Only git and npm

# FFI (Foreign Function Interface)
--allow-ffi                    # Load native libraries

# System
--allow-sys                    # System information

# High-resolution time
--allow-hrtime                 # Precise time measurements

# All permissions (development only!)
--allow-all                    # Or -A

Permission usage examples

Code
Bash
# HTTP server with file reading
deno run --allow-net --allow-read server.ts

# CLI tool with git access
deno run --allow-run=git --allow-read=. deploy.ts

# API client with a specific host
deno run --allow-net=api.github.com github-client.ts

# Script with .env access
deno run --allow-read=.env --allow-env config.ts

Programmatic permission checking

Code
TypeScript
const netStatus = await Deno.permissions.query({
  name: "net",
  host: "api.example.com"
})

if (netStatus.state === "granted") {
  await fetch("https://api.example.com/data")
} else if (netStatus.state === "prompt") {
  const result = await Deno.permissions.request({
    name: "net",
    host: "api.example.com"
  })
  if (result.state === "granted") {
    await fetch("https://api.example.com/data")
  }
}

Native TypeScript

Zero configuration

TSmain.ts
TypeScript
// main.ts - just run: deno run main.ts
interface User {
  id: number
  name: string
  email: string
  createdAt: Date
}

interface ApiResponse<T> {
  data: T
  status: number
  message: string
}

async function fetchUser(id: number): Promise<ApiResponse<User>> {
  const response = await fetch(`https://api.example.com/users/${id}`)
  const data = await response.json()
  return {
    data: data as User,
    status: response.status,
    message: response.ok ? "Success" : "Error"
  }
}

const result = await fetchUser(1)
console.log(result.data.name)
Code
Bash
# Run without any configuration
deno run --allow-net main.ts

Type checking

Code
Bash
# Check types without running
deno check main.ts

# Check types in watch mode
deno check --watch main.ts

# Check all files
deno check **/*.ts

Imports and dependency management

Importing from various sources

Code
TypeScript
// Standard library (JSR)
import { join } from "jsr:@std/path"
import { serve } from "jsr:@std/http"
import { assertEquals } from "jsr:@std/assert"

// npm packages
import { z } from "npm:zod@^3.23.0"
import express from "npm:express@^4.18.0"
import _ from "npm:lodash"

// Direct URL (legacy, but still supported)
import { Application } from "https://deno.land/x/oak@v17.0.0/mod.ts"

// Local files
import { config } from "./config.ts"
import { utils } from "~/utils/mod.ts"  // With import map

// Node.js built-in modules
import process from "node:process"
import fs from "node:fs"
import path from "node:path"

Import maps in deno.json

Code
JSON
{
  "imports": {
    // Standard library
    "@std/": "jsr:@std/",

    // npm packages
    "zod": "npm:zod@^3.23.0",
    "hono": "npm:hono@^4.0.0",
    "drizzle-orm": "npm:drizzle-orm@^0.30.0",

    // Local aliases
    "~/": "./src/",
    "@components/": "./src/components/",
    "@utils/": "./src/utils/",

    // Specific versions
    "lodash": "npm:lodash@4.17.21"
  }
}
Code
TypeScript
// Now you can use shorter imports
import { z } from "zod"
import { Hono } from "hono"
import { UserService } from "~/services/user.ts"
import { Button } from "@components/Button.tsx"

JSR - JavaScript Registry

JSR is a modern registry for JavaScript/TypeScript packages, created by the Deno team.

Code
Bash
# Publishing a package to JSR
deno publish

# Adding a package from JSR
deno add @std/http
deno add @oak/oak
Code
TypeScript
// Import from JSR
import { serve } from "jsr:@std/http@1.0.0"
import { assertEquals } from "jsr:@std/assert@1.0.0"

Web API and Deno API

Fetch API (browser-compatible)

Code
TypeScript
// GET request
const response = await fetch("https://api.example.com/users")
const users = await response.json()

// POST request with JSON
const createResponse = await fetch("https://api.example.com/users", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "Authorization": `Bearer ${token}`
  },
  body: JSON.stringify({
    name: "Jan Kowalski",
    email: "jan@example.com"
  })
})

// Streaming response
const streamResponse = await fetch("https://api.example.com/stream")
const reader = streamResponse.body?.getReader()

while (true) {
  const { done, value } = await reader!.read()
  if (done) break
  console.log(new TextDecoder().decode(value))
}

URL API

Code
TypeScript
const url = new URL("https://example.com/path?foo=bar")

url.searchParams.set("page", "2")
url.searchParams.append("filter", "active")

console.log(url.href)      // https://example.com/path?foo=bar&page=2&filter=active
console.log(url.pathname)  // /path
console.log(url.hostname)  // example.com

Web Streams

Code
TypeScript
// ReadableStream
const readable = new ReadableStream({
  start(controller) {
    controller.enqueue("Hello ")
    controller.enqueue("World!")
    controller.close()
  }
})

// TransformStream
const uppercase = new TransformStream({
  transform(chunk, controller) {
    controller.enqueue(chunk.toUpperCase())
  }
})

// Pipe streams
const result = readable.pipeThrough(uppercase)

for await (const chunk of result) {
  console.log(chunk)  // "HELLO " "WORLD!"
}

Deno-specific API

Code
TypeScript
// Reading files
const content = await Deno.readTextFile("./config.json")
const bytes = await Deno.readFile("./image.png")

// Writing files
await Deno.writeTextFile("./output.txt", "Hello World")
await Deno.writeFile("./data.bin", new Uint8Array([1, 2, 3]))

// File information
const stat = await Deno.stat("./file.txt")
console.log(stat.isFile, stat.size, stat.mtime)

// Listing files
for await (const entry of Deno.readDir("./src")) {
  console.log(entry.name, entry.isDirectory)
}

// Environment variables
const apiKey = Deno.env.get("API_KEY")
Deno.env.set("DEBUG", "true")

// CLI arguments
const args = Deno.args  // ["--port", "3000"]

// Running processes
const command = new Deno.Command("git", {
  args: ["status"],
  stdout: "piped",
  stderr: "piped"
})

const { code, stdout, stderr } = await command.output()
console.log(new TextDecoder().decode(stdout))

HTTP server

Deno.serve (built-in)

Code
TypeScript
// Simplest server
Deno.serve((req: Request) => {
  return new Response("Hello Deno!")
})

// With configuration
Deno.serve({
  port: 3000,
  hostname: "0.0.0.0",
  onListen: ({ port, hostname }) => {
    console.log(`Server running at http://${hostname}:${port}`)
  }
}, handler)

// Handler with routing
function handler(req: Request): Response {
  const url = new URL(req.url)

  switch (url.pathname) {
    case "/":
      return new Response("Home")

    case "/api/users":
      if (req.method === "GET") {
        return Response.json([{ id: 1, name: "Jan" }])
      }
      if (req.method === "POST") {
        return Response.json({ created: true }, { status: 201 })
      }
      return new Response("Method not allowed", { status: 405 })

    default:
      return new Response("Not found", { status: 404 })
  }
}

Streaming response

Code
TypeScript
Deno.serve((req: Request) => {
  const body = new ReadableStream({
    async start(controller) {
      for (let i = 0; i < 5; i++) {
        controller.enqueue(new TextEncoder().encode(`Event ${i}\n`))
        await new Promise(r => setTimeout(r, 1000))
      }
      controller.close()
    }
  })

  return new Response(body, {
    headers: {
      "Content-Type": "text/event-stream",
      "Cache-Control": "no-cache"
    }
  })
})

Server-Sent Events

Code
TypeScript
Deno.serve((req: Request) => {
  const url = new URL(req.url)

  if (url.pathname === "/events") {
    const stream = new ReadableStream({
      start(controller) {
        const encoder = new TextEncoder()

        const interval = setInterval(() => {
          const event = `data: ${JSON.stringify({ time: new Date() })}\n\n`
          controller.enqueue(encoder.encode(event))
        }, 1000)

        // Cleanup on close
        req.signal.addEventListener("abort", () => {
          clearInterval(interval)
          controller.close()
        })
      }
    })

    return new Response(stream, {
      headers: {
        "Content-Type": "text/event-stream",
        "Cache-Control": "no-cache",
        "Connection": "keep-alive"
      }
    })
  }

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

Web frameworks

Hono (recommended)

TSmain.ts
TypeScript
// main.ts
import { Hono } from "npm:hono"
import { cors } from "npm:hono/cors"
import { logger } from "npm:hono/logger"
import { z } from "npm:zod"
import { zValidator } from "npm:@hono/zod-validator"

const app = new Hono()

// Middleware
app.use("*", logger())
app.use("/api/*", cors())

// Routes
app.get("/", (c) => c.text("Hello Hono!"))

app.get("/api/users", async (c) => {
  const users = await db.query.users.findMany()
  return c.json(users)
})

// Validation with Zod
const createUserSchema = z.object({
  name: z.string().min(2),
  email: z.string().email()
})

app.post(
  "/api/users",
  zValidator("json", createUserSchema),
  async (c) => {
    const body = c.req.valid("json")
    const user = await db.insert(users).values(body).returning()
    return c.json(user, 201)
  }
)

// Groups
const api = new Hono()
api.get("/posts", (c) => c.json([]))
api.get("/posts/:id", (c) => c.json({ id: c.req.param("id") }))
app.route("/api", api)

// Error handling
app.onError((err, c) => {
  console.error(err)
  return c.json({ error: "Internal Server Error" }, 500)
})

// Start
Deno.serve(app.fetch)

Oak

Code
TypeScript
import { Application, Router } from "jsr:@oak/oak"

const router = new Router()

router.get("/", (ctx) => {
  ctx.response.body = "Hello Oak!"
})

router.get("/api/users", async (ctx) => {
  const users = await fetchUsers()
  ctx.response.body = users
})

router.post("/api/users", async (ctx) => {
  const body = await ctx.request.body.json()
  const user = await createUser(body)
  ctx.response.status = 201
  ctx.response.body = user
})

const app = new Application()

// Logger middleware
app.use(async (ctx, next) => {
  const start = Date.now()
  await next()
  const ms = Date.now() - start
  console.log(`${ctx.request.method} ${ctx.request.url} - ${ms}ms`)
})

app.use(router.routes())
app.use(router.allowedMethods())

console.log("Server running on http://localhost:8000")
await app.listen({ port: 8000 })

Fresh (full-stack framework)

Fresh is the official full-stack framework from Deno with island architecture and zero JS by default.

Code
Bash
# Creating a Fresh project
deno run -A -r https://fresh.deno.dev my-project
cd my-project
deno task start
TSroutes/index.tsx
TypeScript
// routes/index.tsx
import { Handlers, PageProps } from "$fresh/server.ts"

interface Data {
  posts: Array<{ id: number; title: string }>
}

export const handler: Handlers<Data> = {
  async GET(req, ctx) {
    const posts = await fetchPosts()
    return ctx.render({ posts })
  }
}

export default function Home({ data }: PageProps<Data>) {
  return (
    <div>
      <h1>Blog</h1>
      <ul>
        {data.posts.map((post) => (
          <li key={post.id}>
            <a href={`/posts/${post.id}`}>{post.title}</a>
          </li>
        ))}
      </ul>
    </div>
  )
}

// islands/Counter.tsx - Interactive component (hydrated)
import { useState } from "preact/hooks"

export default function Counter() {
  const [count, setCount] = useState(0)

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>+</button>
    </div>
  )
}

Testing

Built-in test runner

TSmath.ts
TypeScript
// math.ts
export function add(a: number, b: number): number {
  return a + b
}

export function divide(a: number, b: number): number {
  if (b === 0) throw new Error("Division by zero")
  return a / b
}

// math_test.ts
import { assertEquals, assertThrows } from "jsr:@std/assert"
import { add, divide } from "./math.ts"

Deno.test("add function", () => {
  assertEquals(add(2, 3), 5)
  assertEquals(add(-1, 1), 0)
  assertEquals(add(0, 0), 0)
})

Deno.test("divide function", () => {
  assertEquals(divide(10, 2), 5)
  assertEquals(divide(7, 2), 3.5)
})

Deno.test("divide by zero throws", () => {
  assertThrows(
    () => divide(1, 0),
    Error,
    "Division by zero"
  )
})

// Async test
Deno.test("async fetch", async () => {
  const response = await fetch("https://api.example.com/data")
  assertEquals(response.status, 200)
})

// Test with cleanup
Deno.test({
  name: "database test",
  async fn() {
    const db = await connectDb()
    try {
      const result = await db.query("SELECT 1")
      assertEquals(result.rows.length, 1)
    } finally {
      await db.close()
    }
  },
  sanitizeResources: false,
  sanitizeOps: false
})
Code
Bash
# Run all tests
deno test

# Tests with permissions
deno test --allow-net --allow-read

# Specific file
deno test math_test.ts

# Watch mode
deno test --watch

# Coverage
deno test --coverage=coverage
deno coverage coverage

Mocking

Code
TypeScript
import { stub, assertSpyCalls } from "jsr:@std/testing/mock"

Deno.test("mocking fetch", async () => {
  const fetchStub = stub(
    globalThis,
    "fetch",
    () => Promise.resolve(new Response(JSON.stringify({ id: 1 })))
  )

  try {
    const user = await fetchUser(1)
    assertEquals(user.id, 1)
    assertSpyCalls(fetchStub, 1)
  } finally {
    fetchStub.restore()
  }
})

Built-in tools

Formatter

Code
Bash
# Format all files
deno fmt

# Check without modifying
deno fmt --check

# Specific files
deno fmt src/main.ts

# Ignore folders
deno fmt --ignore=vendor,node_modules

Linter

Code
Bash
# Lint all files
deno lint

# Specific files
deno lint src/

# Show rules
deno lint --rules

Bundler

Code
Bash
# Bundle to a single file
deno bundle main.ts bundle.js

# Bundle with minification (use esbuild)
deno run -A npm:esbuild main.ts --bundle --minify --outfile=out.js

Compiling to an executable

Code
Bash
# Compile with embedded permissions
deno compile --allow-net --allow-read main.ts

# With a name and target
deno compile --output app --target x86_64-unknown-linux-gnu main.ts

# Available targets:
# x86_64-unknown-linux-gnu
# x86_64-pc-windows-msvc
# x86_64-apple-darwin
# aarch64-apple-darwin

Deno Deploy

Deno Deploy is a serverless edge platform from the creators of Deno.

Deploy from CLI

Code
Bash
# Log in
deno install -gArf jsr:@deno/deployctl

# Deploy
deployctl deploy --project=my-project main.ts

# With entrypoint
deployctl deploy --project=my-project --entrypoint=server.ts

Deploy with GitHub Actions

.github/workflows/deploy.yml
YAML
# .github/workflows/deploy.yml
name: Deploy

on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    permissions:
      id-token: write
      contents: read

    steps:
      - uses: actions/checkout@v4

      - name: Install Deno
        uses: denoland/setup-deno@v1

      - name: Deploy to Deno Deploy
        uses: denoland/deployctl@v1
        with:
          project: my-project
          entrypoint: main.ts

Project configuration for Deploy

TSmain.ts
TypeScript
// main.ts - compatible with Deno Deploy
Deno.serve((req: Request) => {
  const url = new URL(req.url)

  if (url.pathname === "/api/hello") {
    return Response.json({ message: "Hello from the edge!" })
  }

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

Deno KV (Key-Value Store)

Code
TypeScript
// Open KV store (locally or on Deno Deploy)
const kv = await Deno.openKv()

// Set
await kv.set(["users", "user-123"], {
  name: "Jan",
  email: "jan@example.com"
})

// Get
const result = await kv.get<User>(["users", "user-123"])
console.log(result.value)  // { name: "Jan", ... }

// Delete
await kv.delete(["users", "user-123"])

// List with prefix
const entries = kv.list<User>({ prefix: ["users"] })
for await (const entry of entries) {
  console.log(entry.key, entry.value)
}

// Atomic operations
const res = await kv.atomic()
  .check({ key: ["users", "user-123"], versionstamp: result.versionstamp })
  .set(["users", "user-123"], { ...result.value, name: "Jan Updated" })
  .commit()

if (!res.ok) {
  console.log("Conflict - someone else modified the data")
}

Node.js compatibility

Importing Node.js modules

JSNode.js
JavaScript
// Node.js built-in modules
import process from "node:process"
import fs from "node:fs/promises"
import path from "node:path"
import { Buffer } from "node:buffer"

// Usage
console.log(process.env.HOME)
const content = await fs.readFile("./file.txt", "utf-8")
const fullPath = path.join(process.cwd(), "data")

npm packages

Code
TypeScript
// Import with npm:
import express from "npm:express@4"
import _ from "npm:lodash"
import { PrismaClient } from "npm:@prisma/client"

// In deno.json (preferred)
{
  "imports": {
    "express": "npm:express@^4.18.0",
    "lodash": "npm:lodash@^4.17.0"
  }
}

// Then import normally
import express from "express"

Migrating from Node.js

Code
Bash
# Run a Node.js project in Deno
DENO_FUTURE=1 deno run --allow-all npm:your-script

# Or use compatibility mode
deno run --node-modules-dir --allow-all main.ts

Best practices

Project structure

Code
TEXT
my-deno-project/
├── deno.json           # Configuration
├── deno.lock           # Lockfile
├── main.ts             # Entry point
├── deps.ts             # (optional) Central dependencies
├── src/
│   ├── mod.ts          # Public API
│   ├── routes/
│   │   ├── users.ts
│   │   └── posts.ts
│   ├── services/
│   │   └── database.ts
│   └── utils/
│       └── helpers.ts
├── tests/
│   ├── routes_test.ts
│   └── services_test.ts
└── scripts/
    └── seed.ts

Centralizing dependencies (optional)

TSdeps.ts
TypeScript
// deps.ts - one file with all external dependencies
export { z } from "npm:zod@^3.23.0"
export { Hono } from "npm:hono@^4.0.0"
export { assertEquals, assertThrows } from "jsr:@std/assert"
export { join, dirname } from "jsr:@std/path"

// Usage in other files
import { z, Hono } from "./deps.ts"

Type-safe environment variables

TSenv.ts
TypeScript
// env.ts
import { z } from "npm:zod"

const envSchema = z.object({
  DATABASE_URL: z.string().url(),
  API_KEY: z.string().min(1),
  PORT: z.coerce.number().default(3000),
  NODE_ENV: z.enum(["development", "production", "test"]).default("development")
})

export const env = envSchema.parse(Deno.env.toObject())

FAQ - frequently asked questions

Can Deno replace Node.js?

For new projects - yes. Deno 2.0 is fully compatible with npm and the Node.js API, so migration is possible. For existing large Node.js projects, migration may require some effort.

Why no access to anything by default?

This is a fundamental security principle. A script downloaded from the internet should not automatically have access to your files or network. You must explicitly grant permissions.

Is Deno faster than Node.js?

In most cases Deno and Node.js have similar performance. Bun is faster than both. Deno stands out with its native TypeScript support and built-in tools.

Can I use npm packages in Deno?

Yes! Since Deno 2.0 there is full npm compatibility. Use npm:package-name or configure import maps in deno.json.

What is JSR?

JSR (JavaScript Registry) is a new package registry from the Deno team. It supports TypeScript natively and offers a better DX than npm. You publish packages via deno publish.

Summary

Deno is a modern JavaScript/TypeScript runtime that fixes the fundamental problems of Node.js:

  • Security - Granular permissions, sandboxing by default
  • TypeScript - Native support without configuration
  • Web standards - Compatibility with browser APIs
  • Built-in tools - Formatter, linter, test runner, bundler
  • npm compatibility - Full compatibility since version 2.0
  • Deno Deploy - Edge deployment in 35+ regions

Deno is ideal for new projects, CLI tools, edge functions, and anywhere you care about security and modern DX.