Getting Started with Deno
Overview
Deno is a modern JavaScript runtime built by the original creator of Node.js. It runs JavaScript and TypeScript directly, has no node_modules directory by default, and locks in security through explicit permissions. Where Node.js requires separate tools for formatting, linting, and testing, Deno ships them all built in.
Deno 2 added Node.js and npm compatibility, so existing npm packages work inside Deno. The toolchain stays focused: you get deno run, deno test, deno lint, deno fmt, and deno compile out of the box.
Installation
On macOS and Linux:
curl -fsSL https://deno.land/install.sh | sh
On Windows (PowerShell):
irm https://deno.land/install.ps1 | iex
Or via package managers:
# Homebrew
brew install deno
# Chocolatey
choco install deno
Verify the installation:
deno --version
# deno 2.x.x
Running Your First Script
Create a file called hello.ts:
// hello.ts
const message: string = "Hello from Deno!";
console.log(message);
Run it:
deno run hello.ts
# Hello from Deno!
No build step. No compiler flags. Deno handles TypeScript natively.
Permissions
Deno blocks access to the network, file system, and environment by default. A script that tries to read a file without permission fails:
// read_file.ts — this will fail
const text = await Deno.readTextFile("/etc/passwd");
console.log(text);
deno run read_file.ts
# Error: Permission denied (os error 63)
# Hint: Use --allow-read flag to allow file system access
Grant specific permissions with flags:
deno run --allow-read=/tmp hello.ts
# Only /tmp is readable, other paths are blocked
Common permission flags:
| Flag | What it allows |
|---|---|
--allow-read | File system read |
--allow-write | File system write |
--allow-net | Network access |
--allow-env | Environment variables |
--allow-run | Running sub-processes |
--allow-all | Everything (avoid in production) |
You can also scope permissions to specific hosts:
deno run --allow-net=api.example.com fetch_data.ts
The deno.json Configuration File
The deno.json (or deno.jsonc) file configures your project. Create one in your project root:
{
"compilerOptions": {
"allowJs": true,
"lib": ["deno.window"],
"strict": true
},
"tasks": {
"dev": "deno run --watch --allow-net main.ts",
"start": "deno run --allow-net --allow-env main.ts"
},
"imports": {
"@oak/oak": "jsr:@oak/oak@^14"
},
"exclude": ["**/*.test.ts"]
}
The tasks section defines shortcuts. Instead of typing the full command:
deno task dev
# runs: deno run --watch --allow-net main.ts
The imports section registers JSR or npm specifiers for your project.
Dependencies and JSR
Deno uses JSR, a JavaScript registry with native TypeScript support. Import packages directly from their JSR or npm specifiers:
// Using JSR package
import { oak } from "jsr:@oak/oak@^14";
// Using npm package
import express from "npm:express@4";
No package.json, no node_modules. Deno caches downloads and locks them in deno.lock.
Update the lock on dependency changes:
deno cache --lock=deno.lock --lock-write main.ts
Using npm Packages
Deno 2 supports npm packages directly:
import express from "npm:express@4";
import { z } from "npm:zod@3";
const app = express();
app.get("/", (req, res) => {
res.send("Hello from Deno with npm packages!");
});
app.listen(3000);
Deno installs them into a local node_modules directory when package.json is present. You can also import npm packages directly without a package.json using the npm: specifier.
Running a Web Server
Deno ships with web platform APIs — fetch, Request, Response, Headers — available globally, no imports needed.
// server.ts
const handler = async (req: Request): Promise<Response> => {
const url = new URL(req.url);
if (url.pathname === "/api/hello") {
const name = url.searchParams.get("name") ?? "World";
return Response.json({ message: `Hello, ${name}!` });
}
return new Response("Not found", { status: 404 });
};
Deno.serve({ port: 8000 }, handler);
deno run --allow-net server.ts
# Server running at http://localhost:8000
Deno.serve() is the native HTTP server. There’s no need to import an external package.
File System Access
Read and write files with the Deno global:
// Write to a file
const encoder = new TextEncoder();
const data = encoder.encode("Hello, Deno!\n");
await Deno.writeFile("hello.txt", data);
// Read back
const text = await Deno.readTextFile("hello.txt");
console.log(text); // Hello, Deno!
deno run --allow-write --allow-read write_read.ts
Environment Variables
Read environment variables with Deno.env:
const port = Deno.env.get("PORT") ?? "8000";
const dbUrl = Deno.env.get("DATABASE_URL");
console.log(`Server starting on port ${port}`);
// Only allow specific env vars
// deno run --allow-env=PORT,DATABASE_URL server.ts
Built-in Testing
Write and run tests with Deno.test:
// math.ts
export function add(a: number, b: number): number {
return a + b;
}
// math.test.ts
import { assertEquals } from "jsr:@std/assert";
import { add } from "./math.ts";
Deno.test("adds two numbers", () => {
assertEquals(add(2, 3), 5);
});
Deno.test("handles negatives", () => {
assertEquals(add(-1, 1), 0);
});
Run tests:
deno test
# Check file:///path/to/math.test.ts
# ok ... adds two numbers
# ok ... handles negatives
The @std/assert package provides test assertions. Deno has a built-in test runner with built-in assertion functions — no external test framework required for simple tests.
Lint and Format
Deno includes a linter and formatter:
# Format all TypeScript files
deno fmt
# Check for lint errors
deno lint
Configure linting and formatting rules in deno.json:
{
"lint": {
"include": ["src/"],
"rules": {
"tags": ["recommended"]
}
},
"fmt": {
"useTabs": true,
"lineWidth": 80,
"singleQuote": true
}
}
Hot Reload During Development
Use --watch to restart on file changes:
deno run --watch --allow-net server.ts
The server restarts automatically whenever you save a change.
Compiling to an Executable
Deno can compile your script into a standalone binary:
deno compile --allow-net --allow-env -o my_server server.ts
This produces a native executable with Deno embedded. No runtime needed on the target machine.
Deno vs Node.js
| Feature | Deno | Node.js |
|---|---|---|
| TypeScript | Native, no config | Requires ts-node or compilation |
| package.json | Optional | Required |
| node_modules | Auto-generated, optional | Required |
| Built-in tools | Lint, format, test, compile | None — external packages needed |
| Permissions | Secure by default | Full access by default |
| ESM-first | Yes | CommonJS default |
| npm support | Yes (Deno 2+) | Yes |
See Also
- /guides/javascript-bun-runtime/ — Deno and Bun are both modern JS runtimes; compare their approaches to TypeScript and tooling
- /guides/javascript-vite-guide/ — Vite handles frontend tooling; Deno handles full-stack server-side concerns
- /tutorials/node-getting-started/ — Node.js background helps contrast Deno’s security model and toolchain