🔙 All posts

Dependency Management in Deno

A summary of all the ways (I currently know) to manage dependencies in Deno


Planted January 31, 2025

Dependency Management in Deno

There are lots of ways to manage dependencies in Deno, some better and some worse. I’ve tried to summarise the main point here.

1. Installing Dependencies with deno add

  • If you run deno add <package> without a deno.json or deno.lock, Deno will:
    • Create deno.json (to track dependencies).
    • Create deno.lock (to lock dependency versions).
    • Fetch and install the package into Deno’s global cache (not node_modules).

Example:

Terminal window
deno add jsr:@std/http
  • This updates deno.json:
    {
    "imports": {
    "jsr:@std/http": "jsr:@std/http@0.224.0"
    }
    }
  • The package is now globally cached and version-pinned.

2. Importing Dependencies Without deno add

  • If you import a module in your code without using deno add, the first time you run it:
    • Deno fetches and caches dependencies.
    • Generates a deno.lock file to ensure version consistency.
    • Does not generate a deno.json (since no explicit package management commands were used).

Example:

import { serve } from "https://deno.land/std@0.224.0/http/server.ts";
serve(() => new Response("Hello Deno!"));
  • When run for the first time:
    • deno.lock is created.
    • The module is cached globally.
    • Next runs are instant since it’s already cached.

3. Using JSR Packages (jsr:)

  • JSR (JavaScript Registry for Deno) is the official package registry for Deno.
  • Packages are imported using jsr: prefix.

Example:

import { serve } from "jsr:@std/http";
  • JSR packages do not require URLs, making them more like npm.
  • Versioning works like jsr:@std/http@0.224.0, but deno add simplifies this.

4. Using npm Packages (npm:)

  • Deno supports npm packages without node_modules.
  • Uses npm: prefix to import npm dependencies.

Example:

import express from "npm:express";
const app = express();
  • When you run this for the first time, Deno:
    • Fetches from npm registry.
    • Installs in Deno’s global cache.
    • No node_modules, but deno.lock is updated.
    • Requires --allow-read and --allow-net if needed.

5. Summary of Behaviours

Actiondeno.json Generated?deno.lock Generated?Modules Cached?
deno add <package>✅ Yes✅ Yes✅ Yes
Importing without deno add❌ No✅ Yes✅ Yes
Running a cached module❌ No❌ No (if no changes)✅ Yes

6. Importing from Any URL

Deno allows you to import modules directly from URLs.

Example (Standard Library Import from URL):

import { serve } from "https://deno.land/std@0.224.0/http/server.ts";
serve(() => new Response("Hello Deno!"));
  • This fetches the module once, caches it, and then uses the cached version in subsequent runs.

Example (Third-Party Import from a Raw GitHub URL):

import { nanoid } from "https://raw.githubusercontent.com/ai/nanoid/main/index.js";
console.log(nanoid());
  • This works, but importing from GitHub raw URLs is not ideal since:
    • GitHub URLs can change.
    • There’s no package versioning.
    • The module is not optimized for Deno.

7. Preferred Approaches for URL Imports

While any URL works, best practices suggest using:

Deno’s Standard Library (https://deno.land/std):

import { assertEquals } from "https://deno.land/std@0.224.0/testing/asserts.ts";
  • Maintained by the Deno team.
  • Versioning via @<version> ensures stability.

Deno’s Official Registry (JSR) (jsr:):

import { serve } from "jsr:@std/http";
  • No URL required.
  • Automatically handles versioning and caching.

Deno Third-Party Modules (https://deno.land/x):

import { dayOfYear } from "https://deno.land/x/date_fns@2.22.1/mod.ts";
  • deno.land/x hosts third-party modules with versioning.

CDN Services for npm Modules (e.g., esm.sh):

import { lodash } from "https://esm.sh/lodash";
console.log(lodash.chunk([1, 2, 3, 4], 2));
  • Works for npm packages if you don’t want to use npm:.

8. URL Import Caveats

Avoid importing from raw GitHub URLs unless absolutely necessary.
Avoid unversioned URLs (@version should always be included for stability).
URLs can go down – prefer deno.land/x or jsr: for long-term reliability.


9. URL vs. Local vs. JSR Imports

Import TypeExampleBest Practice?Notes
Deno Standard Libraryhttps://deno.land/std@0.224.0/...✅ YesOfficial, stable, versioned
Deno Third-Party (deno.land/x)https://deno.land/x/oak@v12.6.0/mod.ts✅ YesStable, versioned, community modules
JSR Registry (jsr:)jsr:@std/http✅ YesOfficially recommended for Deno packages
NPM via npm:npm:express✅ YesBest for npm interop
CDN Services (esm.sh)https://esm.sh/lodash⚠️ MaybeOnly if npm: isn’t viable
GitHub Rawhttps://raw.githubusercontent.com/...❌ NoUnstable, not versioned

  • Prefer jsr: for Deno-first modules.

10. Best Practices

  • For reproducibility, use deno add to explicitly track dependencies in deno.json.
  • For simple scripts, direct imports work fine (Deno handles caching automatically).
  • For npm interop, prefix with npm:.
  • For Deno ecosystem packages, prefer jsr: over raw URLs.
  • If you have to use urls, use deno.land/std or deno.land/x for stability.
  • Avoid GitHub raw imports unless unavoidable.

Like what you see?

I send out a (semi) regular newsletter which shares the latest from here and my reading from around the web. Sign up below.

    Your next read?