Back to Blog
5 min readGuide

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() with import
  • 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