•5 min read•Guide
Migrating from Node.js to Deno: A Complete Guide
Step-by-step guide to migrating your Node.js applications to Deno. Learn the differences, challenges, and best practices
Migrating from Node.js to Deno
Deno offers built-in TypeScript, better security, and modern standards. Migrating from Node.js is straightforward once you understand the key differences. Here's your complete migration guide.
Key Differences
1. No package.json or node_modules
// ❌ Node.js
import express from 'express'; // Looks in node_modules
// ✅ Deno
import { Application } from "https://deno.land/x/oak@v12.6.1/mod.ts";
2. Permissions System
# Node.js - unlimited access
node server.js
# Deno - explicit permissions
deno run --allow-net --allow-read server.ts
3. Built-in TypeScript
// Node.js requires setup
// Deno runs TypeScript natively
interface User {
id: number;
name: string;
}
const user: User = { id: 1, name: "Alice" };
4. URL Imports
// Deno uses URLs instead of package names
import { serve } from "https://deno.land/std@0.208.0/http/server.ts";
5. Web Standard APIs
// Deno uses web standards
const response = await fetch("https://api.example.com");
const text = await Deno.readTextFile("./file.txt");
Migration Strategy
Phase 1: Assessment
# Analyze your Node.js project
1. List all dependencies
2. Check compatibility with Deno
3. Identify Node.js-specific APIs
4. Plan migration order (start with utilities, then core logic)
Phase 2: Setup Deno
# Install Deno
curl -fsSL https://deno.land/install.sh | sh
# Verify installation
deno --version
Phase 3: Create deno.json
{
"tasks": {
"start": "deno run --allow-net --allow-read --allow-env server.ts",
"dev": "deno run --allow-net --allow-read --allow-env --watch server.ts",
"test": "deno test --allow-read"
},
"imports": {
"@std/": "https://deno.land/std@0.208.0/",
"oak": "https://deno.land/x/oak@v12.6.1/mod.ts"
},
"compilerOptions": {
"strict": true,
"allowJs": true
}
}
Phase 4: Migrate Dependencies
// Node.js equivalents in Deno
// Express → Oak
// Before (Node.js)
import express from 'express';
const app = express();
app.get('/', (req, res) => res.send('Hello'));
app.listen(3000);
// After (Deno)
import { Application, Router } from "oak";
const app = new Application();
const router = new Router();
router.get("/", (ctx) => { ctx.response.body = "Hello"; });
app.use(router.routes());
await app.listen({ port: 3000 });
// dotenv → Built-in
// Before (Node.js)
require('dotenv').config();
const PORT = process.env.PORT;
// After (Deno)
const PORT = Deno.env.get("PORT");
// fs → Built-in
// Before (Node.js)
const fs = require('fs/promises');
const data = await fs.readFile('file.txt', 'utf8');
// After (Deno)
const data = await Deno.readTextFile("file.txt");
// path → std/path
// Before (Node.js)
const path = require('path');
const fullPath = path.join(__dirname, 'file.txt');
// After (Deno)
import { join } from "@std/path";
const fullPath = join(Deno.cwd(), "file.txt");
Common Migration Patterns
HTTP Server
// Node.js (Express)
const express = require('express');
const app = express();
app.use(express.json());
app.get('/users/:id', async (req, res) => {
const user = await getUser(req.params.id);
res.json(user);
});
app.listen(3000);
// Deno (Oak)
import { Application, Router } from "oak";
const app = new Application();
const router = new Router();
router.get("/users/:id", async (ctx) => {
const user = await getUser(ctx.params.id);
ctx.response.body = user;
});
app.use(router.routes());
await app.listen({ port: 3000 });
File Operations
// Node.js
const fs = require('fs/promises');
const path = require('path');
async function readConfig() {
const configPath = path.join(__dirname, 'config.json');
const data = await fs.readFile(configPath, 'utf8');
return JSON.parse(data);
}
// Deno
import { join, dirname, fromFileUrl } from "@std/path";
async function readConfig() {
const configPath = join(dirname(fromFileUrl(import.meta.url)), "config.json");
const data = await Deno.readTextFile(configPath);
return JSON.parse(data);
}
Environment Variables
// Node.js
require('dotenv').config();
const DB_URL = process.env.DATABASE_URL;
// Deno (with .env file)
import { load } from "@std/dotenv";
const env = await load();
const DB_URL = env.DATABASE_URL;
// Or use built-in
const DB_URL = Deno.env.get("DATABASE_URL");
Database Connection
// Node.js (Prisma)
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
// Deno (Prisma for Deno)
import { PrismaClient } from "https://deno.land/x/prisma@0.4.0/mod.ts";
const prisma = new PrismaClient();
// Or PostgreSQL directly
import { Client } from "https://deno.land/x/postgres@v0.17.0/mod.ts";
const client = new Client({
user: "user",
database: "test",
hostname: "localhost",
port: 5432,
});
await client.connect();
Migration Checklist
- Install Deno
- Create deno.json
- Map dependencies to Deno alternatives
- Replace
require()withimport - Update file system operations
- Replace process.env with Deno.env
- Update __dirname and __filename
- Add permission flags
- Migrate tests
- Update CI/CD configuration
- Update deployment setup
Testing Migration
// Node.js (Jest)
const { add } = require('./math');
test('adds numbers', () => {
expect(add(1, 2)).toBe(3);
});
// Deno (Built-in)
import { assertEquals } from "@std/assert";
import { add } from "./math.ts";
Deno.test("adds numbers", () => {
assertEquals(add(1, 2), 3);
});
Deployment
# Dockerfile for Deno
FROM denoland/deno:1.38.5
WORKDIR /app
COPY . .
RUN deno cache server.ts
EXPOSE 8000
CMD ["deno", "run", "--allow-net", "--allow-read", "--allow-env", "server.ts"]
Common Pitfalls
1. Missing Permissions
# ❌ Error: Requires read access
deno run server.ts
# ✅ Add required permissions
deno run --allow-read --allow-net server.ts
2. Import Extensions
// ❌ Missing .ts extension
import { helper } from "./utils";
// ✅ Include extension
import { helper } from "./utils.ts";
3. __dirname Replacement
// ❌ __dirname doesn't exist
const configPath = path.join(__dirname, 'config.json');
// ✅ Use import.meta.url
import { dirname, fromFileUrl } from "@std/path";
const __dirname = dirname(fromFileUrl(import.meta.url));
Conclusion
Migrating to Deno offers:
- Better security with permissions
- Built-in TypeScript
- Modern standard APIs
- No node_modules
- Better developer experience
Start with small projects, learn the patterns, then migrate larger applications. The future is Deno!
👨💻
Jordan Patel
Web Developer & Technology Enthusiast