Skip to main content

depd vs deprecation vs process.emitWarning: Deprecation Warnings in Node.js 2026

·PkgPulse Team

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

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

Comments

Stay Updated

Get the latest package insights, npm trends, and tooling tips delivered to your inbox.