express-deprecation
v1.0.0
Published
Comprehensive API deprecation middleware for Express.js with warnings, sunset dates, and analytics
Maintainers
Readme
express-deprecation
Comprehensive API deprecation middleware for Express.js with warnings, sunset dates, analytics, and gradual enforcement.
Features
- 🎯 Multiple enforcement levels: Silent logging, warnings, blocking, or redirects
- 📅 Sunset date support: Schedule API removal with RFC 8594 Sunset headers
- 📊 Built-in analytics: Track deprecated API usage and generate reports
- 🔄 Version deprecation: Deprecate entire API versions at once
- 🚦 Method-specific: Deprecate only certain HTTP methods (e.g., deprecate PUT but allow GET)
- 📝 Standard headers: Follows RFC standards (Deprecation, Sunset, Warning, Link)
- 🎨 Customizable: Flexible configuration with custom handlers
- 📈 Usage tracking: Monitor which users/endpoints need migration
- 🔍 TypeScript support: Full TypeScript definitions included
Installation
Option 1: Install from Local Package (Development)
# From your project directory
npm install ../express-deprecationOption 2: Install from npm (Once Published)
npm install express-deprecationOption 3: Install from Git Repository
npm install git+https://github.com/your-org/express-deprecation.gitQuick Start
import express from 'express';
import { deprecateApi, DeprecationLevel } from 'express-deprecation';
const app = express();
// Simple deprecation warning
app.get('/old-endpoint',
deprecateApi({
message: 'This endpoint is deprecated. Use /new-endpoint instead.',
alternativeUrl: '/new-endpoint'
}),
(req, res) => {
res.json({ data: 'old response' });
}
);
// Block access to deprecated endpoint
app.post('/legacy-action',
deprecateApi({
level: DeprecationLevel.BLOCK,
message: 'This endpoint has been removed',
sunsetDate: '2024-12-31',
docsUrl: 'https://docs.example.com/migration'
})
);
app.listen(3000);Usage Examples
1. Warning with Headers (Default)
Adds deprecation headers but allows the request to proceed:
import { deprecateApi } from 'express-deprecation';
app.get('/api/users',
deprecateApi({
message: 'This endpoint will be removed on December 31, 2025',
sunsetDate: '2025-12-31',
alternativeUrl: '/api/v2/users',
docsUrl: 'https://docs.example.com/api/v2/migration'
}),
usersController.list
);Response Headers:
Deprecation: true
Sunset: Wed, 31 Dec 2025 23:59:59 GMT
Link: </api/v2/users>; rel="alternate"
X-API-Deprecation-Message: This endpoint will be removed on December 31, 2025
X-API-Deprecation-Docs: https://docs.example.com/api/v2/migration
Warning: 299 - "Deprecated API" "This endpoint will be removed on December 31, 2025"2. Warning in Response Body
Includes deprecation warning in the JSON response:
import { DeprecationLevel, deprecateApi } from 'express-deprecation';
app.get('/api/products',
deprecateApi({
level: DeprecationLevel.WARN_BODY,
message: 'Migrate to /api/v2/products',
alternativeUrl: '/api/v2/products'
}),
productsController.list
);Response:
{
"products": [...],
"_deprecation": {
"deprecated": true,
"message": "Migrate to /api/v2/products",
"alternativeUrl": "/api/v2/products",
"sunsetDate": null,
"docsUrl": null
}
}3. Block Deprecated Endpoint
Returns 410 Gone and prevents access:
import { DeprecationLevel, deprecateApi, deprecationErrorHandler } from 'express-deprecation';
app.delete('/api/dangerous-action',
deprecateApi({
level: DeprecationLevel.BLOCK,
message: 'This endpoint has been permanently disabled for security reasons',
docsUrl: 'https://docs.example.com/security-update'
})
);
// Add error handler
app.use(deprecationErrorHandler);Response (410 Gone):
{
"error": "DeprecatedApiError",
"message": "This endpoint has been permanently disabled for security reasons",
"deprecation": {
"endpoint": "DELETE /api/dangerous-action",
"sunsetDate": null,
"alternative": null,
"documentation": "https://docs.example.com/security-update"
}
}4. Redirect to New Endpoint
Automatically redirects with 301 Moved Permanently:
import { DeprecationLevel, deprecateApi } from 'express-deprecation';
app.get('/api/old-path',
deprecateApi({
level: DeprecationLevel.REDIRECT,
alternativeUrl: '/api/v2/new-path',
message: 'Resource has moved permanently'
})
);5. Deprecate Entire API Version
import { deprecateVersion, DeprecationLevel } from 'express-deprecation';
// Apply to all v1 routes
app.use('/api/v1/*',
deprecateVersion('v1', {
level: DeprecationLevel.WARN,
message: 'API v1 is deprecated. Please migrate to v2 by December 31, 2025.',
sunsetDate: '2025-12-31',
alternativeUrl: '/api/v2',
docsUrl: 'https://docs.example.com/v2-migration'
})
);
// Your v1 routes
app.use('/api/v1/users', v1UsersRouter);
app.use('/api/v1/products', v1ProductsRouter);6. Deprecate Specific HTTP Methods
import { deprecateMethod, DeprecationLevel } from 'express-deprecation';
// Deprecate PUT but allow GET, POST, PATCH
app.use('/api/users/:id',
deprecateMethod(['PUT'], {
level: DeprecationLevel.WARN,
message: 'PUT is deprecated. Use PATCH for partial updates.',
alternativeUrl: 'PATCH /api/users/:id'
})
);
app.route('/api/users/:id')
.get(usersController.show)
.put(usersController.replace) // Deprecated
.patch(usersController.update); // Not deprecated7. Silent Logging (Monitoring)
Track usage without user-facing changes:
import { DeprecationLevel, deprecateApi } from 'express-deprecation';
app.get('/api/experimental',
deprecateApi({
level: DeprecationLevel.SILENT,
message: 'Monitoring usage before deprecation'
}),
experimentalController.handler
);8. Custom Handler
Add custom logic before deprecation handling:
import { deprecateApi, DeprecationLevel } from 'express-deprecation';
app.get('/api/premium-feature',
deprecateApi({
level: DeprecationLevel.WARN,
message: 'This feature is deprecated for free tier users',
customHandler: async (req, res, info) => {
// Allow premium users to bypass deprecation
if (req.user?.isPremium) {
return false; // Don't show deprecation warning
}
return true; // Continue with deprecation
}
}),
premiumController.handler
);9. Bypass Deprecation for Internal Services
import { bypassDeprecationIf, deprecateApi, DeprecationLevel } from 'express-deprecation';
app.use('/api/internal',
bypassDeprecationIf((req) => req.headers['x-internal-service'] === 'true'),
deprecateApi({ level: DeprecationLevel.BLOCK }),
internalController.handler
);Analytics & Reporting
Access the Registry
import { registry } from 'express-deprecation';
// Get statistics for a specific endpoint
const stats = registry.getEndpointStats('GET /api/v1/users');
console.log(stats);
// {
// endpoint: 'GET /api/v1/users',
// totalCalls: 1523,
// uniqueUsers: 42,
// firstSeen: '2024-01-15T10:30:00.000Z',
// lastSeen: '2024-12-03T14:22:00.000Z',
// level: 'warn',
// sunsetDate: '2025-12-31T00:00:00.000Z'
// }
// Get all deprecated endpoints
const allStats = registry.getAllStats({ limit: 50, sortBy: 'count' });
// Get endpoints nearing sunset
const nearing = registry.getEndpointsNearingSunset(30); // 30 days
console.log(nearing);
// [
// {
// endpoint: 'POST /api/v1/orders',
// daysRemaining: 15,
// totalCalls: 892,
// uniqueUsers: 23
// }
// ]
// Get user-specific deprecation usage
const userStats = registry.getUserStats('user-123');
console.log(userStats);
// {
// userId: 'user-123',
// totalDeprecatedCalls: 45,
// uniqueEndpoints: 3,
// endpoints: [...]
// }Generate Deprecation Report
import { registry } from 'express-deprecation';
// Generate comprehensive report
const report = registry.generateReport();
console.log(report);
// {
// summary: {
// totalDeprecatedEndpoints: 12,
// totalCalls: 5432,
// totalUniqueUsers: 156,
// byLevel: { warn: 8, block: 2, silent: 2 }
// },
// topEndpoints: [...],
// endpointsNearingSunset: [...],
// recentActivity: [...]
// }Create a Dashboard Endpoint
import express from 'express';
import { registry } from 'express-deprecation';
const app = express();
// Admin dashboard
app.get('/admin/deprecations', (req, res) => {
const report = registry.generateReport();
res.json(report);
});
// Check specific user's usage
app.get('/admin/deprecations/users/:userId', (req, res) => {
const stats = registry.getUserStats(req.params.userId);
res.json(stats);
});
// Export data for analysis
app.get('/admin/deprecations/export', (req, res) => {
const data = registry.export();
res.json(data);
});Custom Logger Integration
import { registry } from 'express-deprecation';
import winston from 'winston';
const logger = winston.createLogger({
level: 'info',
transports: [new winston.transports.Console()]
});
// Integrate with your logging system
registry.setLogger((level, message, meta) => {
logger.log(level, message, meta);
});Configuration Options
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| level | DeprecationLevel | WARN | Enforcement level (SILENT, WARN, WARN_BODY, BLOCK, REDIRECT) |
| message | string | Default message | Custom deprecation message |
| sunsetDate | Date\|string | null | ISO 8601 date when API will be removed |
| alternativeUrl | string | null | Recommended replacement endpoint |
| docsUrl | string | null | Documentation URL for migration guide |
| logDeprecations | boolean | true | Whether to log deprecation events |
| useRegistry | boolean | true | Whether to record in analytics registry |
| version | string | null | API version being deprecated |
| customHandler | Function | null | Custom handler (req, res, info) => boolean |
| getUserId | Function | req => req.user_id | Extract user ID from request |
| getRequestId | Function | req => req.uuid | Extract request ID from request |
Deprecation Levels
| Level | Behavior | Use Case |
|-------|----------|----------|
| SILENT | Only logs, no user-facing changes | Monitor usage before deprecation |
| WARN | Adds deprecation headers | Soft deprecation, educate users |
| WARN_BODY | Headers + warning in response body | More visible warnings |
| BLOCK | Returns 410 Gone | Hard deprecation, prevent access |
| REDIRECT | 301 redirect to alternative | Resource moved permanently |
Error Handling
Add the deprecation error handler to your Express error chain:
import express from 'express';
import { deprecationErrorHandler, DeprecatedApiError } from 'express-deprecation';
const app = express();
// Your routes
app.use('/api', apiRoutes);
// Deprecation error handler (add before generic error handler)
app.use(deprecationErrorHandler);
// Generic error handler
app.use((err, req, res, next) => {
res.status(500).json({ error: 'Internal Server Error' });
});Integration with Existing Error Maps
If you have an existing error map pattern:
import { DeprecatedApiError } from 'express-deprecation';
const errorMap = {
NotAuthenticatedError: { message: 'Not authenticated', title: 'Authentication Error' },
NotAuthorizedError: { message: 'Not authorized', title: 'Authorization Error' },
DeprecatedApiError: { message: 'API no longer available', title: 'API Deprecated' }
};
function exceptionHandler(err, req, res, next) {
const errorClassName = err.constructor.name;
const errorInfo = errorMap[errorClassName];
// Special handling for DeprecatedApiError
if (err instanceof DeprecatedApiError) {
return res.status(410).json({
message: err.message,
deprecation: err.deprecationInfo
});
}
// Handle other errors...
}
app.use(exceptionHandler);Best Practices
- Start with WARN: Begin with warning level to educate users before blocking
- Set sunset dates: Always provide a sunset date so users can plan migrations
- Provide alternatives: Always specify
alternativeUrlanddocsUrl - Monitor before blocking: Use SILENT level first to understand usage patterns
- Gradual enforcement: SILENT → WARN → WARN_BODY → BLOCK over time
- Generate reports: Regularly check analytics to identify migration progress
- Communicate early: Give users ample time (3-6 months recommended)
Migration Timeline Example
import { deprecateApi, DeprecationLevel } from 'express-deprecation';
// Month 1-2: Silent monitoring
app.get('/api/old', deprecateApi({ level: DeprecationLevel.SILENT }), handler);
// Month 3-4: Soft warnings
app.get('/api/old', deprecateApi({
level: DeprecationLevel.WARN,
sunsetDate: '2025-06-30'
}), handler);
// Month 5: More visible warnings
app.get('/api/old', deprecateApi({
level: DeprecationLevel.WARN_BODY,
sunsetDate: '2025-06-30'
}), handler);
// Month 6+: Block or redirect
app.get('/api/old', deprecateApi({
level: DeprecationLevel.REDIRECT,
alternativeUrl: '/api/v2/new'
}));TypeScript Support
Full TypeScript definitions included:
import express from 'express';
import { deprecateApi, DeprecationLevel, DeprecationOptions } from 'express-deprecation';
const app = express();
const options: DeprecationOptions = {
level: DeprecationLevel.WARN,
message: 'This endpoint is deprecated',
sunsetDate: '2025-12-31',
alternativeUrl: '/api/v2/endpoint'
};
app.get('/api/old', deprecateApi(options), (req, res) => {
// req.deprecationInfo is typed
console.log(req.deprecationInfo?.endpoint);
res.json({ data: 'response' });
});License
MIT
Contributing
Contributions are welcome! Please open an issue or submit a pull request.
Support
For issues and questions, please use the GitHub issue tracker.
