본문으로 건너뛰기 Deno 2 Complete Guide | Node.js Compatibility, npm, and JSR

Deno 2 Complete Guide | Node.js Compatibility, npm, and JSR

Deno 2 Complete Guide | Node.js Compatibility, npm, and JSR

이 글의 핵심

Deno 2 is a secure JavaScript runtime that now runs Node.js and npm packages natively. With built-in TypeScript, a permissions model, and the new JSR package registry, it's a compelling alternative to Node.js for new projects.

What Changed in Deno 2

Deno 2 (released October 2024) made Node.js compatibility a first-class feature:

FeatureDeno 1Deno 2
npm packagesPartialFull native support
node_modules/Not createdCreated automatically
package.jsonIgnoredFully supported
require()NoYes
Node built-insPartialFull (node:fs, node:path, etc.)
JSRNoYes

Installation

# macOS / Linux
curl -fsSL https://deno.land/install.sh | sh

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

# Homebrew
brew install deno

# Check version
deno --version

1. Running Code

# Run a local file
deno run server.ts

# Run with permissions
deno run --allow-net --allow-read --allow-env server.ts

# Run a URL directly
deno run https://deno.land/std/examples/welcome.ts

# Run npm package
deno run npm:cowsay "Hello Deno"

# Watch mode (re-run on changes)
deno run --watch server.ts

Permission flags

--allow-net              # all network access
--allow-net=api.com      # only api.com
--allow-read             # all filesystem reads
--allow-read=./data      # only ./data directory
--allow-write            # all filesystem writes
--allow-env              # all env vars
--allow-env=API_KEY      # only API_KEY
--allow-run              # spawn subprocesses
--allow-all              # all permissions (like Node.js — use carefully)
-A                       # shorthand for --allow-all

2. TypeScript — Zero Config

Deno runs TypeScript natively without compilation:

// server.ts — just run it, no tsconfig needed
interface User {
  id: number
  name: string
  email: string
}

async function fetchUser(id: number): Promise<User> {
  const res = await fetch(`https://jsonplaceholder.typicode.com/users/${id}`)
  if (!res.ok) throw new Error(`HTTP ${res.status}`)
  return res.json()
}

const user = await fetchUser(1)
console.log(`Hello, ${user.name}!`)
deno run --allow-net server.ts

3. npm Compatibility

// Use npm packages with npm: prefix
import express from 'npm:express@4'
import { z } from 'npm:zod'
import axios from 'npm:axios'

const app = express()

app.get('/', (req, res) => {
  res.json({ message: 'Express on Deno!' })
})

app.listen(3000)
deno run --allow-net server.ts
# Creates node_modules/ automatically — no npm install needed

Using package.json (Node.js project)

// package.json — Deno 2 respects this
{
  "dependencies": {
    "express": "^4.18.0",
    "zod": "^3.22.0"
  }
}
# Install (like npm install but with Deno)
deno install

# Run
deno run --allow-net server.ts

4. JSR — JavaScript Registry

JSR is a TypeScript-first registry that works across runtimes:

// Import from JSR (TypeScript-native packages)
import { Hono } from 'jsr:@hono/hono'
import { join } from 'jsr:@std/path'
import { assertEquals } from 'jsr:@std/assert'

const app = new Hono()
app.get('/', (c) => c.text('Hello!'))

Deno.serve(app.fetch)
// deno.json — lock JSR versions
{
  "imports": {
    "@std/path": "jsr:@std/path@^1.0.0",
    "@std/assert": "jsr:@std/assert@^1.0.0",
    "hono": "jsr:@hono/hono@^4.0.0",
    "zod": "npm:zod@^3.22.0"
  }
}

Publishing to JSR:

# Publish your package to JSR
deno publish

5. Standard Library

Deno’s standard library (@std) covers common needs:

import { join, dirname, basename } from 'jsr:@std/path'
import { exists, readTextFile, writeTextFile } from 'jsr:@std/fs'
import { serve } from 'jsr:@std/http'
import { assertEquals, assertThrows } from 'jsr:@std/assert'
import { delay } from 'jsr:@std/async'
import { parse } from 'jsr:@std/csv'
import { encodeBase64, decodeBase64 } from 'jsr:@std/encoding'

// File operations
const content = await Deno.readTextFile('./config.json')
const config = JSON.parse(content)

await Deno.writeTextFile('./output.txt', 'Hello, Deno!')

// Directory
for await (const entry of Deno.readDir('./src')) {
  console.log(entry.name, entry.isFile ? 'file' : 'dir')
}

// Path
const full = join(Deno.cwd(), 'src', 'index.ts')

6. HTTP Server

// Native Deno HTTP (recommended for new projects)
const handler = async (request: Request): Promise<Response> => {
  const url = new URL(request.url)

  if (url.pathname === '/') {
    return new Response('Hello from Deno!', {
      headers: { 'Content-Type': 'text/plain' },
    })
  }

  if (url.pathname === '/api/users' && request.method === 'GET') {
    const users = await fetchUsers()
    return Response.json(users)
  }

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

// Deno.serve — the recommended way
Deno.serve({ port: 3000 }, handler)
import { Hono } from 'jsr:@hono/hono'
import { cors } from 'jsr:@hono/hono/cors'
import { jwt } from 'jsr:@hono/hono/jwt'

const app = new Hono()

app.use('*', cors())

app.get('/api/health', (c) => c.json({ status: 'ok' }))

app.get('/api/users/:id', async (c) => {
  const id = c.req.param('id')
  const user = await getUser(id)
  if (!user) return c.json({ error: 'Not found' }, 404)
  return c.json(user)
})

Deno.serve({ port: 3000 }, app.fetch)

7. Built-in Tooling

Deno ships with everything built in — no npm install needed:

# Format
deno fmt                     # format all .ts/.js files
deno fmt src/                # format directory
deno fmt --check             # check without modifying

# Lint
deno lint                    # lint all files
deno lint src/

# Test
deno test                    # run all *_test.ts files
deno test --watch            # watch mode
deno test src/user_test.ts  # specific file

# Benchmark
deno bench                   # run all *_bench.ts files

# Type check
deno check src/index.ts

# Bundle
deno compile --allow-net src/cli.ts  # compile to single binary

# REPL
deno

Testing

// user_test.ts
import { assertEquals, assertRejects } from 'jsr:@std/assert'

Deno.test('adds two numbers', () => {
  assertEquals(1 + 2, 3)
})

Deno.test('fetches user', async () => {
  const user = await fetchUser(1)
  assertEquals(user.id, 1)
  assertEquals(typeof user.name, 'string')
})

Deno.test('throws on invalid id', async () => {
  await assertRejects(
    () => fetchUser(-1),
    Error,
    'Invalid user ID'
  )
})

// Group tests
Deno.test('UserService', async (t) => {
  await t.step('create', async () => {
    const user = await createUser({ name: 'Alice' })
    assertEquals(user.name, 'Alice')
  })

  await t.step('delete', async () => {
    await deleteUser(user.id)
    // ...
  })
})

8. deno.json Configuration

{
  "tasks": {
    "start": "deno run --allow-net --allow-env src/main.ts",
    "dev": "deno run --watch --allow-net --allow-env src/main.ts",
    "test": "deno test --allow-net",
    "lint": "deno lint",
    "fmt": "deno fmt"
  },
  "imports": {
    "hono": "jsr:@hono/hono@^4.0.0",
    "zod": "npm:zod@^3.22.0",
    "@std/assert": "jsr:@std/assert@^1.0.0"
  },
  "compilerOptions": {
    "strict": true,
    "lib": ["deno.window"]
  },
  "fmt": {
    "lineWidth": 100,
    "indentWidth": 2,
    "singleQuote": true
  },
  "lint": {
    "rules": {
      "include": ["no-unused-vars", "eqeqeq"]
    }
  }
}
# Run tasks
deno task start
deno task dev
deno task test

Migrating from Node.js

// Node.js
import { readFileSync } from 'fs'
import { join } from 'path'
import { createServer } from 'http'

// Deno 2 (Node.js compat)
import { readFileSync } from 'node:fs'
import { join } from 'node:path'
import { createServer } from 'node:http'
// Same code works! Just add "node:" prefix to built-ins
# Quick compatibility check
deno run --allow-all server.js  # try running existing Node.js file

Deno vs Node.js vs Bun

Deno 2Node.js 22Bun 1.x
TypeScriptNativeVia tsx/ts-nodeNative
npm compatFullNativeFull
SecurityPermissions modelFull accessFull access
Built-in toolsfmt, lint, test, benchNoneBundler, test
PerformanceFastFastFastest
Ecosystemnpm + JSRnpmnpm
Best forSecurity-conscious, new projectsExisting ecosystemMax speed

Key Takeaways

  • Deno 2 runs Node.js and npm packages natively — npm:express, node:fs
  • No install stepdeno run fetches dependencies on first run
  • Permissions model — explicit flags required for network, filesystem, env access
  • JSR — TypeScript-first registry, works across Deno/Node/Bun/browsers
  • Built-in toolingdeno fmt, deno lint, deno test, deno compile (no config needed)
  • deno.json — replaces package.json, tsconfig.json, .eslintrc, .prettierrc

Frequently Asked Questions (FAQ)

Q. When would I use this in practice?

A. Deno 2 runs Node.js code and npm packages natively. Covers the Deno runtime, npm compatibility, JSR (JavaScript Registry…

Q. What should I read before this?

A. Follow the previous article or related articles links at the bottom of each post to learn in sequence.

Q. Where can I study this more deeply?

A. Check cppreference and the relevant library’s official documentation. The reference links at the end of the article are also worth using.


Other articles related to this topic.


Keywords Covered in This Article (Related Search Terms)

This article covers Deno, JavaScript, TypeScript, Node.js, Runtime, Backend.