The basics of Deno
Basic deno commands
deno upgrade: upgrades deno to latest stable versiondeno init <project-name>: scaffolds out a typescript projectdeno uninstall <package>: uninstalls a packagedeno install: installs all dependenciesdeno add <package>: installs a packagedeno remove <package>: removes a package
Running files and permissions
deno run <file>: runs a filedeno run --watch <file>: runs file in watch mode
You also have these security options in place, because by default deno prevents reading and writing from files. To override this behavior, use these flags:
deno run -A <file>: bypasses all securitydeno run -R <file>: allows readingdeno run -W <file>: allows writingdeno run -E <file>: allows access environment variablesdeno run -N <file>: allows access to the network.
To deny reading and writing to specific files or directories, you can use these options:
--deny-read=<filepath>: denies reading the specified filepath or folderpath--deny-write=<filepath>: denies writing to the specified filepath or folderpath
Deno scripts
To run a specific script from your deno.json, you would use the deno run command and then provide the script name:
{
"tasks": {
"dev": "deno run -A --watch main.ts"
},
}
deno task <task-name>
In the above example, you would run deno task dev.
Running tasks asynchronously
You can run two tasks asynchronously or concurrently by connecting them with an ampersand.
# runs task1 and task2 concurrently
deno task task1 & deno task task2
Environment variables
To load environment variables from a .env file into your project so you can access them during runtime, you need to use the --env option when running a file with deno:
deno run --env main.ts
You can get environment variables programmatically with Deno.env.get(var_name).
Format, linting, type checking
deno fmt: formats your codedeno lint: lints your codedeno lint --fix: fixes incompatibility with node modulesdeno check: type checks your code
For linting, you can lint specific files and folders like so:
deno lint <file-or-folder-path>
You can also tell the deno linter to ignore specific files by adding the // deno-lint-ignore-file comment at the top of your file:
// deno-lint-ignore-file
// ... rest of your code
Watch mode
You can supply the --watch flag to deno run, deno test, and deno fmt to enable the built-in file watcher. The watcher enables automatic reloading of your application whenever changes are detected in the source files.
deno run --watch main.ts
deno test --watch
deno fmt --watch
Here are all the options associated with watch mode:
--watch: enables watch mode--watch-exclude=<files>: accepts a comma-separated list of filepaths or glob paths to avoid watching.
When in watch mode, you can also exclude certain files from being watched using the --watch-exclude option:
deno run --watch --watch-exclude=file1.ts,file2.ts main.ts
deno run --watch --watch-exclude='*.js' main.ts
Compile into executable
deno compile
deno compile <file>: compiles a js file into an executable-A: compile with all permissions-o <filename>: rename executable to this filename
deno bundle
The deno bundle command is used to bundle frontend static assets into a single JS file for distribution or works like deno compile if not used for frontend:
- Resolves and inlines all dependencies
- Supports JSX/TSX, TypeScript, and modern JavaScript, including import attributes and CSS
- HTML entrypoint support (Deno 2.5+)
- Optional minification (
--minify) and source maps (--sourcemap) - Code splitting
- Platform targeting (
--platform, supports Deno and browser) - JSX support when configured
CLI
The below example bundles main.ts into an output bundle.js
deno bundle -o bundle.js main.ts
Here are the options:
| Flag | Description |
|---|---|
-o, --output <file> | Write bundled output to a file |
--outdir <dir> | Write bundled output to a directory |
--minify | Minify the output for production |
--format <format> | Output format (esm by default) |
--code-splitting | Enable code splitting |
--platform <platform> | Bundle for browser or deno (default: deno) |
--sourcemap | Include source maps (linked, inline, external) |
--watch | Automatically rebuild on file changes |
--inline-imports | Inline imported modules (true or false) |
HTML entrypoint support
Starting with Deno 2.5, deno bundle supports HTML files as entrypoints.
deno bundle --outdir dist index.html
When you use an HTML file as an entrypoint, deno bundle will:
- Find all script references in the HTML file
- Bundle those scripts and their dependencies
- Update the paths in the HTML file to point to the bundled scripts
- Bundle and inject any imported CSS files into the HTML output
Given an index.tsx file:
import { render } from "npm:preact";
import "./styles.css";
const app = (
<div>
<p>Hello World!</p>
</div>
);
render(app, document.body);
And an HTML file that references it:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Example</title>
<script src="./index.tsx" type="module"></script>
</head>
</html>
Running deno bundle --outdir dist index.html produces:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Example</title>
<script src="./index-2TFDJWLF.js" type="module" crossorigin></script>
<link rel="stylesheet" crossorigin href="./index-EWSJYQGA.css">
</head>
</html>
The bundled output includes content-based hashes for cache-busting and fingerprinting.
NOTE
HTML entrypoints are fully supported in both the CLI and the runtime API.
runtime bundling
The Deno.bundle() method lets you bundle files during runtime:
const result = await Deno.bundle({
entrypoints: ["./index.tsx"],
outputDir: "dist",
platform: "browser",
minify: true,
});
console.log(result);
Deno Config
The deno.json has a bunch of important things that determine the behavior of the deno environment and type checking:
Compiler options
The compiler options change the typescript environment using and other things. It is basically the same thing as the tsconfig.json options:
TypeScript environment
"compilerOptions"."lib": this key specifies the typescript library types to use, and accepts an array of strings that represent the environment types to add:"dom": adds frontend and DOM types"deno.ns": adds types based on theDenonamespace, allowing you to have correct types for when using theDenoclass and any of its methods."deno.worker": adds types for web workers for theWorker()MDN spec"esnext": gives you the types for the latest javascript features.
{
"compilerOptions": {
"lib": ["dom", "deno.ns"] // for SSR apps (frontend + backend)
}
}
To specify the library files to use in a TypeScript file, you can use /// <reference lib="..." /> comments:
/// <reference no-default-lib="true" />
/// <reference lib="dom" />
Standard typescript settings
{
"compilerOptions": {
"noUnusedLocals": true,
"noUnusedParameters": true,
"noImplicitReturns": true
}
}
Linting settings
The "lint" key has settings that affect the behavior of deno lint:
{
"lint": {
"include": ["src/"],
"exclude": ["src/testdata/", "src/fixtures/**/*.ts"],
"rules": {
"tags": ["recommended"],
"include": ["ban-untagged-todo"],
"exclude": ["no-unused-vars"]
}
}
}
"lint"."include": lists the files to include for linting"lint"."exclude": lists the files to exclude for linting
Formatting settings
{
"fmt": {
"useTabs": true,
"lineWidth": 80,
"indentWidth": 4,
"semiColons": true,
"singleQuote": true,
"proseWrap": "preserve",
"include": ["src/"],
"exclude": ["src/testdata/", "src/fixtures/**/*.ts"]
}
}
"fmt"."include": lists the files to include for formatting"fmt"."exclude": lists the files to exclude for formatting
Excluding paths from type checking, linting, formatting
To exclude files and folders from being type checked, linted, or formatted by Deno at all, use the top level "excludes" key:
{
"exclude": [
// exclude the dist folder from all sub-commands and the LSP
"dist/"
]
}
Importing modules
From NPM
When trying to install a package from npm, you have to specify that it's from npm by prefixing the package name with npm:.
deno add npm:chalk
You can then use it like so:
import chalk from "chalk";
console.log(chalk.red("bruh"));
Or you could just import like so, removing the need to explicitly install a package:
import chalk from "npm:chalk";
When downloading type declarations
When you download a package that needs to use type declarations, you can just specify the type declarations package like so instead of separately downloading it.
// @deno-types="npm:@types/express@^4.17"
import express from "npm:express@^4.17";
// @ts-types="npm:@types/lodash"
import * as _ from "npm:lodash";
Importing native node modules
When importing native node modules, you need to prefix them with node:
// ❌
import * as fs from "fs";
import * as http from "http";
// ✅
import * as fs from "node:fs";
import * as http from "node:http";
Importing online modules
When importing packages from an online URL, you can directly import modules inline from the URL or use the "imports" key in the deno.json to refer to that url via a different name:
Deno also supports import statements that reference HTTP/HTTPS URLs, either directly:
import { Application } from "https://deno.land/x/oak/mod.ts";
or part of your deno.json import map:
{
"imports": {
"oak": "https://deno.land/x/oak/mod.ts"
}
}
NOTE
HTTP imports are not supported by deno add/deno install commands.
Import remappings
When using deno add <package-name> you can use the "imports" key in the deno.json as a key-value map between library identifier shorthands and their package resolution url:
{
"imports": {
"@std/assert": "jsr:@std/assert@^1.0.0",
"chalk": "npm:chalk@5"
}
}
Then your script can use the bare specifier std/assert:
import { assertEquals } from "@std/assert";
import chalk from "chalk";
assertEquals(1, 2);
console.log(chalk.yellow("Hello world"));
custom remappings for files
You can have custom remappings just like in tsconfig.json:
The import map in deno.json can be used for more general path mapping of specifiers. You can map an exact specifiers to a third party module or a file directly, or you can map a part of an import specifier to a directory.
{
"imports": {
// Map to an exact file
"foo": "./some/long/path/foo.ts",
// Map to a directory, usage: "bar/file.ts"
"bar/": "./some/folder/bar/"
}
}
Usage:
import * as foo from "foo";
import * as bar from "bar/file.ts";
Custom remapping of folders
Just like in typescript, you can remap entire folderpaths to a different folderpath:
For example:
{
"imports": {
"@/": "./"
}
}
And then you can refer to files under that folder like so:
import { MyUtil } from "@/util.ts";
This causes import specifiers starting with @/ to be resolved relative to the import map's URL or file path.
Importing files
Importing typescript files
When importing TypeScript files, you must add on the .ts extension.
// WRONG: missing file extension
import { add } from "./calc";
// CORRECT: includes file extension
import { add } from "./calc.ts";
Importing asset files
Json files
You can import JSON files as javascript objects like so:
import data from "./data.json" with { type: "json" };
console.log(data.property); // Access JSON data as an object
Text files
All text files (csv, txt, log, etc.) are imported as strings:
import text from "./log.txt" with { type: "text" };
console.log(typeof text === "string");
// true
console.log(text);
// Hello from a text file
media
You can statically import any media asset as a UInt8Array instance:
import bytes from "./image.png" with { type: "bytes" };
console.log(bytes instanceof Uint8Array);
// true
console.log(bytes);
Uint8Array(12) [
// 72, 101, 108, 108, 111,
// 44, 32, 68, 101, 110,
// 111, 33
// ]
Jupyter notebooks in deno
You can add deno kernels to jupyter notebook with the deno jupyter --install command. Once you're there, yoiu can start running cells with Deno
Importing files
The important thing to know about importing TS files into deno is that you can only import them via absolute path (NOT FILE URL).
Development workflow
Deno skills
There are a list of skills the official Deno team supplies so that agents always have an up to date way of working with Deno:
| Skill | Description |
|---|---|
| deno-guidance | Core Deno best practices, JSR packages, CLI commands |
| deno-deploy | Deployment workflows for Deno Deploy |
| deno-frontend | Fresh framework, Preact components, Tailwind CSS |
| deno-sandbox | Safe code execution with @deno/sandbox |
| deno-project-templates | Project scaffolding templates |
| deno-expert | Code review and debugging principles |
Using the npx skills library, you can install the skill like so: |
npx skills add https://github.com/denoland/skills --skill deno-expert
Deno with docker
Dockerfile
Here is a standard image setup for deno with docker
FROM denoland/deno:latest
WORKDIR /app
# Copy manifests first so the dependency install layer caches across
# source-only edits
COPY deno.json deno.lock package.json* ./
RUN deno ci --prod --skip-types
# Then copy the rest of the source
COPY . .
CMD ["deno", "run", "--allow-net", "main.ts"]
To tighten security and run as non-root user, use the following commands:
# Create deno user
RUN addgroup --system deno && \
adduser --system --ingroup deno deno
# Switch to deno user
USER deno
# Continue with rest of Dockerfile
Development with docker compose
services:
app:
build: .
ports:
- "8000:8000"
environment:
- DATABASE_URL=postgres://deno:${POSTGRES_PASSWORD}@db:5432/app
depends_on:
db:
condition: service_healthy
restart: unless-stopped
command:
[
"deno",
"run",
"--allow-net=db:5432",
"--allow-env=DATABASE_URL",
"main.ts",
]
db:
image: postgres:16-alpine
environment:
POSTGRES_USER: deno
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_DB: app
volumes:
- pgdata:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U deno"]
interval: 5s
timeout: 3s
retries: 5
restart: unless-stopped
volumes:
pgdata: