Deno 2.0: Compatible with Node (Finally)
Deno 2.0 was released in October 2024. The biggest change: full Node.js compatibility. You can now run most npm packages without modification. This removes the primary barrier to Deno adoption.
What’s New in 2.0
Node Compatibility
// This now works in Deno 2.0
import express from "npm:express";
const app = express();
app.get("/", (req, res) => {
res.send("Hello from Deno!");
});
app.listen(3000);
npm packages just work.
package.json Support
{
"name": "my-deno-app",
"dependencies": {
"express": "^4.18.0",
"lodash": "^4.17.0"
}
}
// Import from package.json
import express from "express";
import _ from "lodash";
node_modules
# Optionally use node_modules
deno install
# Directory structure works like Node
node_modules/
├── express/
├── lodash/
└── ...
Migration from Node
Before (Node.js)
// server.js
const express = require('express');
const { Pool } = require('pg');
const pool = new Pool({
connectionString: process.env.DATABASE_URL
});
const app = express();
app.get('/users', async (req, res) => {
const result = await pool.query('SELECT * FROM users');
res.json(result.rows);
});
app.listen(3000);
After (Deno 2.0)
// server.ts
import express from "npm:express";
import pg from "npm:pg";
const pool = new pg.Pool({
connectionString: Deno.env.get("DATABASE_URL")
});
const app = express();
app.get('/users', async (req, res) => {
const result = await pool.query('SELECT * FROM users');
res.json(result.rows);
});
app.listen(3000);
Minimal changes required.
Why Deno Now
Security by Default
# Explicit permissions required
deno run --allow-net --allow-read server.ts
# Or grant all (like Node)
deno run -A server.ts
TypeScript Native
// No tsconfig, no transpilation step
interface User {
id: number;
name: string;
}
function greet(user: User): string {
return `Hello, ${user.name}!`;
}
Just run .ts files directly.
Built-in Tooling
deno fmt # Format code
deno lint # Lint code
deno test # Run tests
deno bench # Benchmarks
deno compile # Create executable
deno doc # Generate docs
No additional dev dependencies.
Standard Library
import { serve } from "https://deno.land/std/http/server.ts";
import { join } from "https://deno.land/std/path/mod.ts";
import { parse } from "https://deno.land/std/flags/mod.ts";
// Stable, versioned, audited
Comparison
| Feature | Node.js | Deno 2.0 |
|---|---|---|
| npm packages | Native | ✅ Full support |
| TypeScript | Requires setup | Native |
| Security | All permissions | Opt-in |
| Built-in tools | Separate | Included |
| URL imports | No | Yes |
| package.json | Required | Optional |
Deno Deploy
Serverless edge deployment:
// Simple edge function
Deno.serve((req) => {
return new Response("Hello from the edge!");
});
# Deploy
deno deploy
Global edge network, automatic HTTPS.
Testing
// user_test.ts
import { assertEquals } from "https://deno.land/std/assert/mod.ts";
import { getUser } from "./user.ts";
Deno.test("getUser returns user by id", async () => {
const user = await getUser(1);
assertEquals(user.name, "Alice");
});
Deno.test("getUser throws on invalid id", async () => {
await assertRejects(
() => getUser(-1),
Error,
"Invalid user ID"
);
});
deno test
Coverage
deno test --coverage=coverage
deno coverage coverage --lcov
Key Patterns
Fresh (Web Framework)
// routes/index.tsx
import { Handlers, PageProps } from "$fresh/server.ts";
export const handler: Handlers = {
async GET(req, ctx) {
const data = await fetchData();
return ctx.render(data);
},
};
export default function Home({ data }: PageProps) {
return <div>{data.message}</div>;
}
Islands architecture, no build step.
Oak (Express-like)
import { Application, Router } from "https://deno.land/x/oak/mod.ts";
const router = new Router();
router.get("/", (ctx) => {
ctx.response.body = "Hello Oak!";
});
const app = new Application();
app.use(router.routes());
await app.listen({ port: 8000 });
Compiling to Executables
# Create standalone executable
deno compile --allow-net --allow-read server.ts
# Output: server (or server.exe on Windows)
./server
Single binary, no runtime needed.
Configuration
// deno.json
{
"compilerOptions": {
"strict": true
},
"imports": {
"std/": "https://deno.land/std@0.208.0/"
},
"tasks": {
"dev": "deno run --watch --allow-net server.ts",
"test": "deno test --allow-read"
}
}
Should You Switch?
Yes, If
- Starting new project
- Want simpler tooling
- Security matters
- TypeScript-first team
Not Yet, If
- Large existing Node codebase
- Heavily dependent on Node-specific APIs
- Team unfamiliar with Deno
- Edge cases with npm compatibility
Final Thoughts
Deno 2.0 removes the “can’t use npm packages” objection. With full Node compatibility, the choice is now about trade-offs rather than limitations.
For new projects, Deno’s security model and built-in tooling are compelling. For existing Node projects, migration is now actually feasible.
The modern JavaScript runtime grows up.