正在加载,请稍候…

Deno 2.0: Modern JavaScript Runtime vs Node.js - Migration and Comparison

Complete guide to Deno 2.0: secure-by-default permissions, native TypeScript, built-in tooling, Node.js compatibility, npm package support, and real-world migration from Express/Node to Deno.

Deno 2.0: The Modern JavaScript Runtime

Deno 2.0 represents a significant maturation: full Node.js and npm compatibility while maintaining its security-first, standards-based design. It is now a realistic alternative to Node.js for new projects.

What Makes Deno Different

Feature Node.js Deno 2.0
TypeScript Requires ts-node/esbuild Native, zero config
Security Full system access Deny by default
Package manager npm/yarn/pnpm npm + jsr: imports
Tooling Separate tools needed Built-in (fmt, lint, test, bench)
Standards Node-specific APIs Web Platform APIs
Node compat Native 95%+ via node: prefix

Getting Started

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

# Run TypeScript directly - no compilation step
deno run main.ts

# With permissions
deno run --allow-net --allow-read main.ts

# REPL
deno

Security Model

Deno denies all system access by default:

// This will FAIL without --allow-net
const response = await fetch('https://api.example.com/data');

// This will FAIL without --allow-read
const text = await Deno.readTextFile('./config.json');

// Granular permissions
// --allow-net=api.example.com    (only this host)
// --allow-read=/app/config       (only this path)
// --allow-env=DATABASE_URL       (only this env var)

// deno.json - production permissions
// {
//   'tasks': {
//     'start': 'deno run --allow-net=:8000 --allow-env=DATABASE_URL --allow-read=./static main.ts'
//   }
// }

Modern HTTP Server

// server.ts - no dependencies needed!
import { serveFile } from 'jsr:@std/http/file-server';

const handler = async (req: Request): Promise<Response> => {
    const url = new URL(req.url);

    if (url.pathname === '/api/users') {
        const users = await getUsers();
        return Response.json(users);
    }

    if (url.pathname.startsWith('/static/')) {
        return serveFile(req, '.' + url.pathname);
    }

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

Deno.serve({ port: 8000, hostname: '0.0.0.0' }, handler);
console.log('Listening on http://localhost:8000');

Using npm Packages

Deno 2.0 supports npm packages natively:

// Use npm packages directly
import express from 'npm:express@4';
import { z } from 'npm:zod';
import Stripe from 'npm:stripe';

// Or with deno.json imports map
// deno.json:
// {
//   'imports': {
//     'express': 'npm:express@4',
//     'zod': 'npm:zod@3'
//   }
// }
import express from 'express';
import { z } from 'zod';

const app = express();
const UserSchema = z.object({
    name: z.string().min(1),
    email: z.string().email(),
    age: z.number().int().min(0).max(150)
});

app.post('/users', async (req, res) => {
    const result = UserSchema.safeParse(req.body);
    if (!result.success) return res.status(400).json(result.error);
    // ...
});

Built-in Toolchain

# Format (replaces prettier)
deno fmt
deno fmt --check  # CI check

# Lint (replaces eslint for common rules)
deno lint

# Test (replaces jest/mocha)
deno test
deno test --coverage
deno coverage --html  # HTML coverage report

# Benchmark
deno bench

# Type check only
deno check main.ts

# Bundle for browser
deno bundle main.ts output.js

# Compile to single executable
deno compile --allow-net --allow-read main.ts

Testing

// user.test.ts
import { assertEquals, assertRejects } from 'jsr:@std/assert';
import { getUser, createUser } from './user.ts';

Deno.test('getUser returns user by id', async () => {
    const user = await getUser('123');
    assertEquals(user.id, '123');
    assertEquals(user.name, 'Alice');
});

Deno.test('createUser rejects invalid email', async () => {
    await assertRejects(
        () => createUser({ name: 'Bob', email: 'not-an-email' }),
        Error,
        'Invalid email'
    );
});

// Grouped tests with setup/teardown
Deno.test('user service', async (t) => {
    const db = await createTestDatabase();
    await t.step('creates user', async () => {
        const user = await createUser(db, { name: 'Test' });
        assertEquals(user.name, 'Test');
    });
    await t.step('lists users', async () => {
        const users = await listUsers(db);
        assertEquals(users.length, 1);
    });
    await db.close();
});

Migrating from Node.js

// Node.js - before
// const fs = require('fs/promises');
// const path = require('path');

// Deno - after (option 1: node: prefix)
import { readFile } from 'node:fs/promises';
import { join } from 'node:path';
const text = await readFile(join('./data', 'file.txt'), 'utf-8');

// Deno - after (option 2: native Deno APIs)
const text2 = await Deno.readTextFile('./data/file.txt');

// Environment variables
// Node: process.env.DATABASE_URL
// Deno: Deno.env.get('DATABASE_URL')

// Process exit
// Node: process.exit(1)
// Deno: Deno.exit(1)

Deploy with Deno Deploy

// Runs globally on Deno Deploy's edge network (35+ regions)
// No configuration needed for basic deployments

// deploy.ts - uploaded directly
const kv = await Deno.openKv(); // Built-in distributed KV store

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

    if (req.method === 'POST' && url.pathname === '/count') {
        const key = ['visits', url.hostname];
        const current = (await kv.get<number>(key)).value ?? 0;
        await kv.set(key, current + 1);
        return Response.json({ count: current + 1 });
    }

    return new Response('Hello from edge!', {
        headers: { 'Content-Type': 'text/plain' }
    });
});

Should You Migrate?

Migrate to Deno for new projects when:

  • TypeScript is required (zero-config is a real advantage)
  • Security model matters (microservices, processing user uploads)
  • You want fewer dev dependencies (no eslint, prettier, ts-node, nodemon)

Stay with Node.js when:

  • Existing large codebase with deep npm ecosystem dependencies
  • Team expertise and existing tooling is heavily Node-based
  • You need native addons (.node files)

Deno 2.0 is production-ready. Its standards-based approach means your code works in browsers and edge runtimes without modification.