Skip to main content

Guide

depd vs deprecation vs process.emitWarning in 2026

How should you deprecate APIs in your Node.js library? We compare depd, the deprecation package, and Node.js built-in process.emitWarning — patterns and output.

·PkgPulse Team·
0

TL;DR

process.emitWarning() is the built-in Node.js approach and the right default for most libraries in 2026 — zero dependencies, standard output, integrates with --trace-warnings. depd is the battle-tested package that Express and many Connect-based middleware use — it deduplicates warnings (fires once per call site) and supports NODE_ENV=production silencing. deprecation is a lightweight alternative focused on typed warnings. For new libraries, start with process.emitWarning; use depd when you need per-call-site deduplication.

Key Takeaways

  • process.emitWarning(): Built-in Node.js API, no deps, fires every time (no deduplication)
  • depd: 45M+ weekly downloads, fires once per call site (deduplication), used by Express/Connect ecosystem
  • deprecation: 8M+ weekly downloads, typed warnings, used by Octokit/GitHub libraries
  • Deduplication is the key differentiator — depd only warns once per unique call site
  • Node.js 22+: --trace-deprecation and --throw-deprecation flags work with all three approaches
  • For Express middleware: depd is the standard (ecosystem convention)

The Problem

When you maintain a library, you need to signal that an API will change or be removed. Do it wrong and you either:

  • Flood users' console with repeated warnings (every render, every request)
  • Silently break their code in the next major version with no heads-up

Good deprecation warnings fire at the right time, give actionable information, and don't drown out other output.


process.emitWarning(): The Built-In Way

Node.js ships with a deprecation warning system. No npm install needed:

// Basic deprecation warning
function oldMethod(data: unknown) {
  process.emitWarning(
    "oldMethod() is deprecated. Use newMethod() instead.",
    {
      type: "DeprecationWarning",
      code: "MY_LIB_DEP001",
    }
  );
  return newMethod(data);
}

Output

(node:12345) [MY_LIB_DEP001] DeprecationWarning: oldMethod() is deprecated. Use newMethod() instead.

Runtime Flags

# Show stack traces for deprecation warnings
node --trace-deprecation app.js

# Turn deprecation warnings into thrown errors (useful in tests)
node --throw-deprecation app.js

# Silence all deprecation warnings
node --no-deprecation app.js

Programmatic Handling

// Users can catch deprecation warnings programmatically
process.on("warning", (warning) => {
  if (warning.name === "DeprecationWarning" && warning.code === "MY_LIB_DEP001") {
    // Log to monitoring, suppress output, etc.
  }
});

The Problem: No Deduplication

function getUser(id: string) {
  process.emitWarning("getUser() is deprecated. Use fetchUser().", {
    type: "DeprecationWarning",
    code: "DEP001",
  });
  return fetchUser(id);
}

// In a web server:
app.get("/users/:id", (req, res) => {
  const user = getUser(req.params.id); // ⚠️ Warning fires EVERY request
});

// After 1000 requests: 1000 identical warnings in your logs

This is why depd exists.


depd: Fire Once Per Call Site

npm install depd  # 1.4kB, zero dependencies

depd's core value: it tracks the call site (file + line number) and only emits the warning once per unique location:

import depd from "depd";

// Create a deprecation function scoped to your package
const deprecate = depd("my-library");

function getUser(id: string) {
  deprecate("getUser() is deprecated. Use fetchUser() instead.");
  return fetchUser(id);
}

// First call from app.js:15 → warning printed
// Second call from app.js:15 → SILENCED (same call site)
// First call from routes.js:42 → warning printed (new call site)
// Second call from routes.js:42 → SILENCED

Output Format

my-library deprecated getUser() is deprecated. Use fetchUser() instead. at app.js:15:3

The output includes the package name, the message, and the exact call site — users can find and fix the deprecation immediately.

depd for Properties

import depd from "depd";

const deprecate = depd("my-library");

const config = {
  get oldProperty() {
    deprecate.property(this, "oldProperty", "Use newProperty instead");
    return this.newProperty;
  },
  newProperty: "value",
};

depd in Express Middleware

This is why depd has 45M+ weekly downloads — Express and its middleware ecosystem all use it:

import depd from "depd";

const deprecate = depd("my-middleware");

export function myMiddleware(options?: DeprecatedOptions) {
  if (options?.legacyMode) {
    deprecate("legacyMode option is deprecated. Use mode: 'compat' instead.");
  }

  return (req, res, next) => {
    // middleware logic
    next();
  };
}

depd Environment Behavior

NODE_ENVBehavior
developmentFull warning with stack trace
productionShort warning (no stack trace)
testFull warning
UnsetFull warning
# Production: minimal output
NODE_ENV=production node app.js
# my-library deprecated getUser()

# Development: full call site info
NODE_ENV=development node app.js
# my-library deprecated getUser() is deprecated. Use fetchUser() instead.
#     at Object.<anonymous> (app.js:15:3)

deprecation: Typed Warnings

npm install deprecation  # 0.5kB, zero dependencies

The deprecation package (used by Octokit and GitHub's npm packages) creates typed Deprecation error objects:

import { Deprecation } from "deprecation";

function getUser(id: string) {
  const warning = new Deprecation(
    "[@my-org/my-lib] getUser() is deprecated. Use fetchUser() instead. " +
    "See https://my-lib.dev/migration#getUser"
  );
  process.emitWarning(warning);
  return fetchUser(id);
}

Output

(node:12345) DeprecationWarning: [@my-org/my-lib] getUser() is deprecated. Use fetchUser() instead. See https://my-lib.dev/migration#getUser

Why Use deprecation Over Raw emitWarning?

The Deprecation class extends Error, so it captures the stack trace automatically:

const warning = new Deprecation("message");
warning.name;    // "Deprecation"
warning.message; // "message"
warning.stack;   // full stack trace (captured at construction)

This integrates well with --trace-warnings and error monitoring tools (Sentry, Datadog) that recognize Error-like objects.

deprecation with Once Pattern

deprecation doesn't deduplicate by default. Combine with once:

import { Deprecation } from "deprecation";
import once from "once"; // or lodash.once

const warnGetUser = once(() => {
  process.emitWarning(
    new Deprecation("getUser() is deprecated. Use fetchUser().")
  );
});

function getUser(id: string) {
  warnGetUser(); // fires once, ever (not per call site)
  return fetchUser(id);
}

Head-to-Head Comparison

Featureprocess.emitWarningdepddeprecation
Zero dependencies✅ Built-in✅ Zero deps✅ Zero deps
Bundle size0kB1.4kB0.5kB
Deduplication✅ Per call site❌ (DIY)
Stack trace--trace-warnings✅ Automatic✅ Error-based
Production silencing✅ NODE_ENV=production
Warning codescode: "DEP001"
Property deprecation.property()
Typed/Error-like❌ (string)❌ (string)✅ extends Error
Express ecosystem⚠️ Not standard✅ Standard
Weekly downloadsN/A45M+8M+

Patterns for Your Library

The Simple Pattern (process.emitWarning)

Best for: small libraries, internal packages, one-off deprecations.

// utils/deprecation.ts
const warned = new Set<string>();

export function deprecate(code: string, message: string) {
  if (warned.has(code)) return;
  warned.add(code);
  process.emitWarning(message, {
    type: "DeprecationWarning",
    code,
  });
}

// usage
deprecate("MY_LIB_001", "foo() is deprecated. Use bar().");

The Express Pattern (depd)

Best for: middleware, large libraries, when call-site tracking matters.

import depd from "depd";
const deprecate = depd("my-package");

export function legacyHandler(req, res) {
  deprecate("legacyHandler is deprecated, use modernHandler");
  return modernHandler(req, res);
}

The GitHub/Octokit Pattern (deprecation)

Best for: API clients, SDKs, when you want Error-compatible warnings.

import { Deprecation } from "deprecation";

const warned = new Set<string>();

export function deprecate(message: string) {
  if (warned.has(message)) return;
  warned.add(message);
  process.emitWarning(new Deprecation(`[my-sdk] ${message}`));
}

Testing Deprecation Warnings

import { describe, it, expect, vi } from "vitest";

describe("deprecation warnings", () => {
  it("emits deprecation warning on first call", () => {
    const warnSpy = vi.spyOn(process, "emitWarning");

    oldFunction();

    expect(warnSpy).toHaveBeenCalledWith(
      expect.stringContaining("deprecated"),
      expect.objectContaining({ type: "DeprecationWarning" })
    );

    warnSpy.mockRestore();
  });

  it("throws with --throw-deprecation behavior", () => {
    // Simulate --throw-deprecation
    process.on("warning", (warning) => {
      if (warning.name === "DeprecationWarning") throw warning;
    });

    expect(() => oldFunction()).toThrow(/deprecated/);
  });
});

Methodology

  • Analyzed npm download data for depd and deprecation packages (March 2026)
  • Reviewed Express, Connect, and Koa middleware source code for deprecation patterns
  • Tested deduplication behavior of depd across Node.js 20, 22, and 23
  • Compared output format and --trace-warnings integration across all three approaches
  • Reviewed Octokit/GitHub SDK source code for deprecation package usage patterns

Integrating Deprecation Warnings with Monitoring and Observability Tools

Production observability platforms treat process.emitWarning output differently from unhandled errors, and knowing those differences affects how you design your deprecation strategy.

Node.js emits deprecation warnings to stderr by default. Most log aggregation pipelines (Datadog, Loki, CloudWatch) ingest stderr alongside stdout, so raw process.emitWarning calls will appear in your logs — but without structured metadata they are difficult to query, alert on, or correlate with affected users. Adding a code field (e.g. MY_LIB_DEP001) gives you a stable search key. You can query code:MY_LIB_DEP001 in Datadog or build a CloudWatch Insights query to count unique affected call sites per day.

The deprecation package's approach of extending Error integrates cleanly with Sentry and similar error monitors. When you call process.emitWarning(new Deprecation("...")), Sentry's Node.js SDK captures the warning event and records it with the full stack trace from the Deprecation instance. This gives you a Sentry issue per deprecated API method, with user impact counts, affected releases, and affected URLs — the same observability you get for actual errors. For library authors shipping public SDKs, this level of visibility into which deprecated APIs are still in use across customer codebases is invaluable for planning removal timelines.

depd's approach does not integrate with error monitors by default because depd intercepts the warning before it reaches the standard process.warning event in some environments. If you want depd warnings to flow into Sentry, listen to process.on('warning', ...) and forward warnings with code matching your library prefix.


Deprecation Strategies When Removing APIs Across Major Versions

The mechanics of emitting a warning are straightforward; the harder problem is designing a deprecation lifecycle that gives users enough time to migrate without keeping dead code in your library indefinitely.

The standard Node.js ecosystem convention is a two-major-version deprecation window: mark an API as deprecated in v2.0, remove it in v4.0. This means the deprecated API survives at least one major release where it is still present but warned, giving users a predictable timeline. Express follows this convention throughout its middleware ecosystem.

For the deprecation message itself, the most actionable pattern is: [what is deprecated] → [what to use instead] → [migration link]. A message like "getUser() is deprecated. Use fetchUser() instead. See https://my-lib.dev/migration#v3-getUser" is unambiguous. Users should never have to consult a changelog to understand what to do with a deprecation warning — the warning itself should contain the complete migration instruction.

When the replacement API requires a different call signature, include the new signature in the deprecation message. depd does not support multi-line messages, so keep the guidance concise but complete. The deprecation package's approach of including a URL is particularly valuable here — a migration guide can provide code examples and explain context that would be too verbose for a single-line warning message.

Automated detection of deprecated API usage is a growing practice. ESLint rules and @typescript-eslint/no-deprecated can flag deprecated symbols at the editor level, complementing runtime warnings. For library authors, publishing an ESLint plugin alongside your deprecations gives users a way to catch deprecated usage before runtime — shifting from reactive (see the warning in production) to proactive (blocked at lint time in CI).


Compare Express vs Fastify and other Node.js packages on PkgPulse — real-time npm download trends.

In 2026, library authors should use depd or process.emitWarning() rather than the deprecation package for new projects. depd gives you the cleanest API and the most control over when warnings fire, and it is the established pattern in the Express/Connect ecosystem. process.emitWarning() is the modern Node.js built-in approach — no extra dependency, integrates with Node.js' native warning system, and works everywhere Node.js runs. The deprecation package is useful when you need to track whether a deprecated code path has already warned (the de-dupe logic), but the same effect is achievable with depd's built-in de-duplication.

The discipline of writing clear, actionable deprecation messages pays dividends that are easy to underestimate. A warning that tells users exactly which API changed, what to use instead, and where to find migration documentation reduces support burden proportionally to how many consuming projects need to migrate. Libraries with cryptic deprecation messages drive confusion that generates issue reports and delayed migrations. Treating deprecation messages as user-facing documentation — with the same care given to API documentation — is one of the clearest signals of a mature, user-focused library maintenance practice.

See also: Motia: #1 Backend in JS Rising Stars 2025 and Best npm Packages for API Testing and Mocking in 2026, Best CLI Frameworks for Node.js in 2026.

The 2026 JavaScript Stack Cheatsheet

One PDF: the best package for every category (ORMs, bundlers, auth, testing, state management). Used by 500+ devs. Free, updated monthly.