depd vs deprecation vs process.emitWarning: Deprecation Warnings in Node.js 2026
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-deprecationand--throw-deprecationflags 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_ENV | Behavior |
|---|---|
development | Full warning with stack trace |
production | Short warning (no stack trace) |
test | Full warning |
| Unset | Full 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
| Feature | process.emitWarning | depd | deprecation |
|---|---|---|---|
| Zero dependencies | ✅ Built-in | ✅ Zero deps | ✅ Zero deps |
| Bundle size | 0kB | 1.4kB | 0.5kB |
| Deduplication | ❌ | ✅ Per call site | ❌ (DIY) |
| Stack trace | ✅ --trace-warnings | ✅ Automatic | ✅ Error-based |
| Production silencing | ❌ | ✅ NODE_ENV=production | ❌ |
| Warning codes | ✅ code: "DEP001" | ❌ | ❌ |
| Property deprecation | ❌ | ✅ .property() | ❌ |
| Typed/Error-like | ❌ (string) | ❌ (string) | ✅ extends Error |
| Express ecosystem | ⚠️ Not standard | ✅ Standard | ❌ |
| Weekly downloads | N/A | 45M+ | 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.
See the live comparison
View express vs. fastify on PkgPulse →