@girardmedia/bootspring 3.3.2 → 3.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (171) hide show
  1. package/assets/agents/accessibility-auditor.md +39 -0
  2. package/assets/agents/api-designer.md +40 -0
  3. package/assets/agents/auth-implementer.md +64 -0
  4. package/assets/agents/bug-hunter.md +42 -0
  5. package/assets/agents/bundle-analyzer.md +40 -0
  6. package/assets/agents/cache-optimizer.md +55 -0
  7. package/assets/agents/changelog-writer.md +55 -0
  8. package/assets/agents/ci-cd-builder.md +40 -0
  9. package/assets/agents/code-explainer.md +39 -0
  10. package/assets/agents/code-reviewer.md +39 -0
  11. package/assets/agents/cost-optimizer.md +57 -0
  12. package/assets/agents/cron-scheduler.md +51 -0
  13. package/assets/agents/data-seeder.md +56 -0
  14. package/assets/agents/database-architect.md +40 -0
  15. package/assets/agents/dependency-updater.md +40 -0
  16. package/assets/agents/deploy-checker.md +40 -0
  17. package/assets/agents/docker-optimizer.md +40 -0
  18. package/assets/agents/documentation-writer.md +40 -0
  19. package/assets/agents/email-builder.md +55 -0
  20. package/assets/agents/env-setup.md +40 -0
  21. package/assets/agents/error-handler.md +40 -0
  22. package/assets/agents/eslint-fixer.md +46 -0
  23. package/assets/agents/feature-flagger.md +69 -0
  24. package/assets/agents/git-detective.md +39 -0
  25. package/assets/agents/graphql-builder.md +60 -0
  26. package/assets/agents/incident-responder.md +59 -0
  27. package/assets/agents/log-analyzer.md +39 -0
  28. package/assets/agents/migration-planner.md +41 -0
  29. package/assets/agents/monorepo-navigator.md +39 -0
  30. package/assets/agents/nextjs-expert.md +57 -0
  31. package/assets/agents/notification-builder.md +56 -0
  32. package/assets/agents/onboarding-guide.md +39 -0
  33. package/assets/agents/performance-profiler.md +40 -0
  34. package/assets/agents/prisma-expert.md +57 -0
  35. package/assets/agents/rate-limiter.md +58 -0
  36. package/assets/agents/react-expert.md +58 -0
  37. package/assets/agents/refactorer.md +42 -0
  38. package/assets/agents/regex-builder.md +46 -0
  39. package/assets/agents/release-manager.md +40 -0
  40. package/assets/agents/s3-manager.md +58 -0
  41. package/assets/agents/schema-validator.md +40 -0
  42. package/assets/agents/search-builder.md +62 -0
  43. package/assets/agents/security-auditor.md +39 -0
  44. package/assets/agents/sitemap-generator.md +53 -0
  45. package/assets/agents/stripe-integrator.md +59 -0
  46. package/assets/agents/tailwind-expert.md +55 -0
  47. package/assets/agents/tech-debt-tracker.md +39 -0
  48. package/assets/agents/test-writer.md +42 -0
  49. package/assets/agents/type-fixer.md +45 -0
  50. package/assets/agents/webhook-builder.md +54 -0
  51. package/assets/rules/cpp.md +53 -0
  52. package/assets/rules/css.md +52 -0
  53. package/assets/rules/go.md +50 -0
  54. package/assets/rules/html.md +52 -0
  55. package/assets/rules/java.md +51 -0
  56. package/assets/rules/kotlin.md +50 -0
  57. package/assets/rules/php.md +51 -0
  58. package/assets/rules/python.md +51 -0
  59. package/assets/rules/ruby.md +51 -0
  60. package/assets/rules/rust.md +49 -0
  61. package/assets/rules/shell.md +52 -0
  62. package/assets/rules/sql.md +49 -0
  63. package/assets/rules/swift.md +50 -0
  64. package/assets/rules/typescript.md +52 -0
  65. package/assets/rules/yaml-json.md +51 -0
  66. package/assets/skills/accessibility.md +210 -0
  67. package/assets/skills/agent-patterns.md +387 -0
  68. package/assets/skills/ai-integration.md +263 -0
  69. package/assets/skills/animation-patterns.md +224 -0
  70. package/assets/skills/api-design.md +218 -0
  71. package/assets/skills/api-gateway.md +341 -0
  72. package/assets/skills/api-versioning.md +226 -0
  73. package/assets/skills/astro-patterns.md +233 -0
  74. package/assets/skills/auth-patterns.md +248 -0
  75. package/assets/skills/aws-patterns.md +171 -0
  76. package/assets/skills/background-jobs.md +162 -0
  77. package/assets/skills/browser-extensions.md +309 -0
  78. package/assets/skills/caching-patterns.md +253 -0
  79. package/assets/skills/ci-cd.md +251 -0
  80. package/assets/skills/cli-development.md +296 -0
  81. package/assets/skills/code-review.md +185 -0
  82. package/assets/skills/cron-patterns.md +327 -0
  83. package/assets/skills/data-fetching.md +231 -0
  84. package/assets/skills/database-migrations.md +346 -0
  85. package/assets/skills/database-patterns.md +219 -0
  86. package/assets/skills/debugging.md +281 -0
  87. package/assets/skills/design-system.md +289 -0
  88. package/assets/skills/django-patterns.md +182 -0
  89. package/assets/skills/docker-patterns.md +235 -0
  90. package/assets/skills/e2e-testing.md +287 -0
  91. package/assets/skills/edge-computing.md +268 -0
  92. package/assets/skills/electron-patterns.md +266 -0
  93. package/assets/skills/email-templates.md +206 -0
  94. package/assets/skills/error-handling.md +265 -0
  95. package/assets/skills/event-driven.md +232 -0
  96. package/assets/skills/express-patterns.md +239 -0
  97. package/assets/skills/fastapi-patterns.md +198 -0
  98. package/assets/skills/feature-flags.md +212 -0
  99. package/assets/skills/figma-to-code.md +298 -0
  100. package/assets/skills/file-upload.md +228 -0
  101. package/assets/skills/forms-patterns.md +264 -0
  102. package/assets/skills/gcp-patterns.md +189 -0
  103. package/assets/skills/git-workflow.md +187 -0
  104. package/assets/skills/golang-patterns.md +185 -0
  105. package/assets/skills/graphql-patterns.md +244 -0
  106. package/assets/skills/i18n-patterns.md +172 -0
  107. package/assets/skills/image-processing.md +350 -0
  108. package/assets/skills/java-springboot.md +226 -0
  109. package/assets/skills/kotlin-patterns.md +207 -0
  110. package/assets/skills/kubernetes-patterns.md +326 -0
  111. package/assets/skills/laravel-patterns.md +261 -0
  112. package/assets/skills/llm-fine-tuning.md +335 -0
  113. package/assets/skills/load-testing.md +303 -0
  114. package/assets/skills/logging-observability.md +228 -0
  115. package/assets/skills/markdown-processing.md +318 -0
  116. package/assets/skills/mcp-server-patterns.md +292 -0
  117. package/assets/skills/microservices.md +272 -0
  118. package/assets/skills/migration-patterns.md +239 -0
  119. package/assets/skills/mongodb-patterns.md +189 -0
  120. package/assets/skills/monorepo-patterns.md +287 -0
  121. package/assets/skills/nextjs-app-router.md +237 -0
  122. package/assets/skills/notification-patterns.md +348 -0
  123. package/assets/skills/oauth-patterns.md +246 -0
  124. package/assets/skills/payment-integration.md +222 -0
  125. package/assets/skills/pdf-generation.md +307 -0
  126. package/assets/skills/performance-optimization.md +277 -0
  127. package/assets/skills/php-patterns.md +210 -0
  128. package/assets/skills/prisma-patterns.md +241 -0
  129. package/assets/skills/prompt-engineering.md +193 -0
  130. package/assets/skills/pwa-patterns.md +247 -0
  131. package/assets/skills/python-patterns.md +158 -0
  132. package/assets/skills/python-testing.md +172 -0
  133. package/assets/skills/queue-patterns.md +295 -0
  134. package/assets/skills/rag-patterns.md +159 -0
  135. package/assets/skills/rate-limiting.md +319 -0
  136. package/assets/skills/react-components.md +201 -0
  137. package/assets/skills/react-native-patterns.md +299 -0
  138. package/assets/skills/real-time-patterns.md +181 -0
  139. package/assets/skills/redis-patterns.md +188 -0
  140. package/assets/skills/refactoring.md +218 -0
  141. package/assets/skills/regex-patterns.md +191 -0
  142. package/assets/skills/remix-patterns.md +262 -0
  143. package/assets/skills/responsive-design.md +199 -0
  144. package/assets/skills/ruby-rails-patterns.md +178 -0
  145. package/assets/skills/rust-patterns.md +211 -0
  146. package/assets/skills/search-patterns.md +227 -0
  147. package/assets/skills/security-hardening.md +237 -0
  148. package/assets/skills/seo-patterns.md +179 -0
  149. package/assets/skills/serverless-patterns.md +223 -0
  150. package/assets/skills/sql-optimization.md +154 -0
  151. package/assets/skills/state-management.md +254 -0
  152. package/assets/skills/storybook-patterns.md +330 -0
  153. package/assets/skills/svelte-patterns.md +258 -0
  154. package/assets/skills/swift-patterns.md +227 -0
  155. package/assets/skills/tailwind-patterns.md +272 -0
  156. package/assets/skills/tdd-workflow.md +199 -0
  157. package/assets/skills/terraform-patterns.md +270 -0
  158. package/assets/skills/testing-react.md +240 -0
  159. package/assets/skills/testing-vitest.md +232 -0
  160. package/assets/skills/typescript-strict.md +159 -0
  161. package/assets/skills/video-processing.md +340 -0
  162. package/assets/skills/vue-patterns.md +247 -0
  163. package/assets/skills/web-workers.md +327 -0
  164. package/assets/skills/webhooks-patterns.md +283 -0
  165. package/assets/skills/websocket-patterns.md +306 -0
  166. package/dist/cli/index.js +941 -958
  167. package/dist/core/index.d.ts +341 -11
  168. package/dist/core.js +58 -95
  169. package/dist/mcp/index.d.ts +33 -1
  170. package/dist/mcp-server.js +177 -255
  171. package/package.json +4 -1
@@ -0,0 +1,341 @@
1
+ ---
2
+ name: api-gateway
3
+ description: API gateway patterns for routing, rate limiting, authentication, request transformation, load balancing, and health checks.
4
+ ---
5
+
6
+ # API Gateway Patterns
7
+
8
+ ## When to Use
9
+ Implement an API gateway when you have multiple backend services and need a unified entry point for clients. Gateways handle cross-cutting concerns: authentication, rate limiting, request routing, response transformation, and load balancing. Use these patterns in microservice architectures, BFF (Backend for Frontend) setups, or any system where you need to decouple client-facing APIs from internal service topology.
10
+
11
+ ## How It Works
12
+
13
+ ### Express-Based API Gateway
14
+
15
+ ```typescript
16
+ // src/gateway.ts
17
+ import express from 'express';
18
+ import { createProxyMiddleware } from 'http-proxy-middleware';
19
+ import rateLimit from 'express-rate-limit';
20
+ import helmet from 'helmet';
21
+ import cors from 'cors';
22
+
23
+ const app = express();
24
+
25
+ // Security headers
26
+ app.use(helmet());
27
+ app.use(cors({ origin: process.env.ALLOWED_ORIGINS?.split(','), credentials: true }));
28
+ app.use(express.json({ limit: '1mb' }));
29
+
30
+ // Service registry
31
+ const services: Record<string, { target: string; healthPath: string }> = {
32
+ users: { target: 'http://user-service:3001', healthPath: '/health' },
33
+ posts: { target: 'http://post-service:3002', healthPath: '/health' },
34
+ billing: { target: 'http://billing-service:3003', healthPath: '/health' },
35
+ search: { target: 'http://search-service:3004', healthPath: '/health' },
36
+ };
37
+
38
+ // Global rate limiter
39
+ app.use(rateLimit({
40
+ windowMs: 60_000,
41
+ max: 100,
42
+ standardHeaders: true,
43
+ legacyHeaders: false,
44
+ keyGenerator: (req) => req.headers['x-forwarded-for'] as string || req.ip,
45
+ message: { error: 'Too many requests', retryAfter: 60 },
46
+ }));
47
+
48
+ // Route to services
49
+ for (const [name, config] of Object.entries(services)) {
50
+ app.use(`/api/${name}`, createProxyMiddleware({
51
+ target: config.target,
52
+ changeOrigin: true,
53
+ pathRewrite: { [`^/api/${name}`]: '' },
54
+ timeout: 30_000,
55
+ proxyTimeout: 30_000,
56
+ on: {
57
+ proxyReq: (proxyReq, req) => {
58
+ // Forward auth context
59
+ if ((req as any).userId) {
60
+ proxyReq.setHeader('X-User-Id', (req as any).userId);
61
+ }
62
+ proxyReq.setHeader('X-Request-Id', req.headers['x-request-id'] || crypto.randomUUID());
63
+ },
64
+ error: (err, req, res) => {
65
+ console.error(`Proxy error [${name}]:`, err.message);
66
+ (res as express.Response).status(502).json({
67
+ error: 'Service unavailable',
68
+ service: name,
69
+ });
70
+ },
71
+ },
72
+ }));
73
+ }
74
+
75
+ app.listen(3000, () => console.log('Gateway listening on :3000'));
76
+ ```
77
+
78
+ ### Authentication Middleware
79
+
80
+ ```typescript
81
+ // src/middleware/auth.ts
82
+ import jwt from 'jsonwebtoken';
83
+ import type { Request, Response, NextFunction } from 'express';
84
+
85
+ interface AuthConfig {
86
+ publicPaths: string[];
87
+ jwtSecret: string;
88
+ }
89
+
90
+ export function authMiddleware(config: AuthConfig) {
91
+ return (req: Request, res: Response, next: NextFunction) => {
92
+ // Skip auth for public paths
93
+ if (config.publicPaths.some((p) => req.path.startsWith(p))) {
94
+ return next();
95
+ }
96
+
97
+ const authHeader = req.headers.authorization;
98
+ if (!authHeader?.startsWith('Bearer ')) {
99
+ return res.status(401).json({ error: 'Missing authorization header' });
100
+ }
101
+
102
+ const token = authHeader.slice(7);
103
+ try {
104
+ const payload = jwt.verify(token, config.jwtSecret) as { userId: string; role: string };
105
+ (req as any).userId = payload.userId;
106
+ (req as any).userRole = payload.role;
107
+ next();
108
+ } catch (err) {
109
+ if ((err as jwt.JsonWebTokenError).name === 'TokenExpiredError') {
110
+ return res.status(401).json({ error: 'Token expired', code: 'TOKEN_EXPIRED' });
111
+ }
112
+ return res.status(401).json({ error: 'Invalid token' });
113
+ }
114
+ };
115
+ }
116
+
117
+ // Register before proxy routes
118
+ app.use(authMiddleware({
119
+ publicPaths: ['/api/auth', '/health', '/api/docs'],
120
+ jwtSecret: process.env.JWT_SECRET!,
121
+ }));
122
+ ```
123
+
124
+ ### Per-Route Rate Limiting
125
+
126
+ ```typescript
127
+ // src/middleware/rate-limit.ts
128
+ import rateLimit from 'express-rate-limit';
129
+ import RedisStore from 'rate-limit-redis';
130
+ import { createClient } from 'redis';
131
+
132
+ const redis = createClient({ url: process.env.REDIS_URL });
133
+ await redis.connect();
134
+
135
+ export function createRateLimiter(windowMs: number, max: number) {
136
+ return rateLimit({
137
+ windowMs,
138
+ max,
139
+ standardHeaders: true,
140
+ store: new RedisStore({
141
+ sendCommand: (...args: string[]) => redis.sendCommand(args),
142
+ }),
143
+ keyGenerator: (req) => {
144
+ const userId = (req as any).userId;
145
+ return userId ? `user:${userId}` : req.ip;
146
+ },
147
+ });
148
+ }
149
+
150
+ // Different limits per route
151
+ app.use('/api/auth/login', createRateLimiter(15 * 60_000, 5)); // 5 per 15 min
152
+ app.use('/api/search', createRateLimiter(60_000, 30)); // 30 per min
153
+ app.use('/api/billing', createRateLimiter(60_000, 10)); // 10 per min
154
+ ```
155
+
156
+ ### Request/Response Transformation
157
+
158
+ ```typescript
159
+ // src/middleware/transform.ts
160
+ import type { Request, Response, NextFunction } from 'express';
161
+
162
+ // Version-based response transformation
163
+ export function apiVersionTransform(req: Request, res: Response, next: NextFunction) {
164
+ const version = req.headers['api-version'] || 'v2';
165
+
166
+ // Intercept response for transformation
167
+ const originalJson = res.json.bind(res);
168
+ res.json = function (body: any) {
169
+ if (version === 'v1' && body?.data) {
170
+ // v1 clients expect flat response, v2 uses envelope
171
+ return originalJson(body.data);
172
+ }
173
+ return originalJson(body);
174
+ };
175
+
176
+ next();
177
+ }
178
+
179
+ // Request enrichment
180
+ export function enrichRequest(req: Request, _res: Response, next: NextFunction) {
181
+ const requestId = req.headers['x-request-id'] || crypto.randomUUID();
182
+ req.headers['x-request-id'] = requestId;
183
+ req.headers['x-gateway-timestamp'] = new Date().toISOString();
184
+ req.headers['x-client-ip'] = req.headers['x-forwarded-for'] as string || req.ip;
185
+ next();
186
+ }
187
+ ```
188
+
189
+ ### Health Check Aggregation
190
+
191
+ ```typescript
192
+ // src/health.ts
193
+ import type { Request, Response } from 'express';
194
+
195
+ interface ServiceHealth {
196
+ name: string;
197
+ status: 'healthy' | 'degraded' | 'down';
198
+ latencyMs: number;
199
+ lastChecked: string;
200
+ }
201
+
202
+ async function checkServiceHealth(name: string, url: string): Promise<ServiceHealth> {
203
+ const start = Date.now();
204
+ try {
205
+ const controller = new AbortController();
206
+ const timeout = setTimeout(() => controller.abort(), 5000);
207
+ const res = await fetch(url, { signal: controller.signal });
208
+ clearTimeout(timeout);
209
+
210
+ return {
211
+ name,
212
+ status: res.ok ? 'healthy' : 'degraded',
213
+ latencyMs: Date.now() - start,
214
+ lastChecked: new Date().toISOString(),
215
+ };
216
+ } catch {
217
+ return {
218
+ name,
219
+ status: 'down',
220
+ latencyMs: Date.now() - start,
221
+ lastChecked: new Date().toISOString(),
222
+ };
223
+ }
224
+ }
225
+
226
+ app.get('/health', async (_req: Request, res: Response) => {
227
+ const checks = await Promise.all(
228
+ Object.entries(services).map(([name, config]) =>
229
+ checkServiceHealth(name, `${config.target}${config.healthPath}`)
230
+ )
231
+ );
232
+
233
+ const allHealthy = checks.every((c) => c.status === 'healthy');
234
+ const anyDown = checks.some((c) => c.status === 'down');
235
+
236
+ res.status(anyDown ? 503 : 200).json({
237
+ status: anyDown ? 'degraded' : allHealthy ? 'healthy' : 'degraded',
238
+ services: checks,
239
+ uptime: process.uptime(),
240
+ timestamp: new Date().toISOString(),
241
+ });
242
+ });
243
+
244
+ // Liveness probe (lightweight)
245
+ app.get('/health/live', (_req, res) => res.status(200).send('ok'));
246
+
247
+ // Readiness probe (checks dependencies)
248
+ app.get('/health/ready', async (_req, res) => {
249
+ const critical = ['users', 'billing'];
250
+ const checks = await Promise.all(
251
+ critical.map((name) =>
252
+ checkServiceHealth(name, `${services[name].target}${services[name].healthPath}`)
253
+ )
254
+ );
255
+ const ready = checks.every((c) => c.status !== 'down');
256
+ res.status(ready ? 200 : 503).json({ ready, services: checks });
257
+ });
258
+ ```
259
+
260
+ ### Circuit Breaker
261
+
262
+ ```typescript
263
+ // src/circuit-breaker.ts
264
+ type State = 'closed' | 'open' | 'half-open';
265
+
266
+ export class CircuitBreaker {
267
+ private state: State = 'closed';
268
+ private failures = 0;
269
+ private lastFailure = 0;
270
+ private successCount = 0;
271
+
272
+ constructor(
273
+ private readonly threshold: number = 5,
274
+ private readonly resetTimeoutMs: number = 30_000,
275
+ private readonly halfOpenMax: number = 3,
276
+ ) {}
277
+
278
+ async execute<T>(fn: () => Promise<T>): Promise<T> {
279
+ if (this.state === 'open') {
280
+ if (Date.now() - this.lastFailure > this.resetTimeoutMs) {
281
+ this.state = 'half-open';
282
+ this.successCount = 0;
283
+ } else {
284
+ throw new Error('Circuit breaker is open');
285
+ }
286
+ }
287
+
288
+ try {
289
+ const result = await fn();
290
+ this.onSuccess();
291
+ return result;
292
+ } catch (err) {
293
+ this.onFailure();
294
+ throw err;
295
+ }
296
+ }
297
+
298
+ private onSuccess() {
299
+ if (this.state === 'half-open') {
300
+ this.successCount++;
301
+ if (this.successCount >= this.halfOpenMax) {
302
+ this.state = 'closed';
303
+ this.failures = 0;
304
+ }
305
+ } else {
306
+ this.failures = 0;
307
+ }
308
+ }
309
+
310
+ private onFailure() {
311
+ this.failures++;
312
+ this.lastFailure = Date.now();
313
+ if (this.failures >= this.threshold) {
314
+ this.state = 'open';
315
+ }
316
+ }
317
+
318
+ getState(): State { return this.state; }
319
+ }
320
+ ```
321
+
322
+ ## Examples
323
+
324
+ | Concern | Implementation | Where |
325
+ |---------|---------------|-------|
326
+ | Auth | JWT verification middleware | Before proxy |
327
+ | Rate limiting | Redis-backed sliding window | Per-route |
328
+ | Routing | `http-proxy-middleware` | Path prefix mapping |
329
+ | Transformation | Response interceptor | API version compat |
330
+ | Health | Aggregated service checks | `/health` endpoint |
331
+ | Circuit breaking | Failure counter + timeout | Per-service proxy |
332
+
333
+ ## Checklist
334
+ - [ ] All requests authenticated before reaching backend services
335
+ - [ ] Rate limiting applied globally and per-route with Redis backing
336
+ - [ ] Proxy forwards `X-Request-Id` for distributed tracing
337
+ - [ ] Service errors return 502 with service name, not raw proxy errors
338
+ - [ ] Health endpoint checks all downstream services with timeout
339
+ - [ ] Liveness and readiness probes separated for Kubernetes
340
+ - [ ] Circuit breaker prevents cascading failures between services
341
+ - [ ] Request/response logged with request ID for debugging
@@ -0,0 +1,226 @@
1
+ ---
2
+ name: api-versioning
3
+ description: API versioning patterns with URL, header, and query param versioning, deprecation strategy, and changelog.
4
+ ---
5
+
6
+ # API Versioning
7
+
8
+ ## When to Use
9
+ Apply when your API has external consumers and you need to make breaking changes without disrupting them. Version your API from day one. Use URL-based versioning for simplicity, header-based for cleanliness, and maintain a deprecation strategy that gives consumers time to migrate.
10
+
11
+ ## How It Works
12
+
13
+ ### URL-Based Versioning (Recommended Default)
14
+
15
+ The most common and discoverable approach:
16
+
17
+ ```typescript
18
+ import { Router } from "express";
19
+
20
+ const v1Router = Router();
21
+ const v2Router = Router();
22
+
23
+ v1Router.get("/users/:id", async (req, res) => {
24
+ const user = await userService.getById(req.params.id);
25
+ res.json({ id: user.id, name: user.fullName, email: user.email });
26
+ });
27
+
28
+ v2Router.get("/users/:id", async (req, res) => {
29
+ const user = await userService.getById(req.params.id);
30
+ res.json({
31
+ id: user.id,
32
+ name: { first: user.firstName, last: user.lastName },
33
+ email: user.email,
34
+ createdAt: user.createdAt.toISOString(),
35
+ links: { self: `/api/v2/users/${user.id}` },
36
+ });
37
+ });
38
+
39
+ app.use("/api/v1", v1Router);
40
+ app.use("/api/v2", v2Router);
41
+ ```
42
+
43
+ ### Header-Based Versioning
44
+
45
+ Keeps URLs clean; version is in the Accept header:
46
+
47
+ ```typescript
48
+ function versionMiddleware(req: Request, _res: Response, next: NextFunction) {
49
+ const accept = req.headers.accept ?? "";
50
+ const match = accept.match(/application\/vnd\.myapp\.v(\d+)\+json/);
51
+ req.apiVersion = match ? parseInt(match[1]) : 1;
52
+ next();
53
+ }
54
+
55
+ app.use(versionMiddleware);
56
+
57
+ app.get("/api/users/:id", (req, res) => {
58
+ switch (req.apiVersion) {
59
+ case 2: return getUserV2(req, res);
60
+ default: return getUserV1(req, res);
61
+ }
62
+ });
63
+ ```
64
+
65
+ Client sends: `Accept: application/vnd.myapp.v2+json`
66
+
67
+ ### Deprecation Strategy
68
+
69
+ Give consumers clear warnings and a migration timeline:
70
+
71
+ ```typescript
72
+ function deprecationMiddleware(
73
+ deprecatedVersion: number,
74
+ sunsetDate: string,
75
+ migrationGuide: string
76
+ ) {
77
+ return (req: Request, res: Response, next: NextFunction) => {
78
+ if (req.apiVersion <= deprecatedVersion) {
79
+ res.setHeader("Deprecation", "true");
80
+ res.setHeader("Sunset", new Date(sunsetDate).toUTCString());
81
+ res.setHeader("Link", `<${migrationGuide}>; rel="deprecation"`);
82
+ logger.warn({
83
+ apiVersion: req.apiVersion,
84
+ path: req.path,
85
+ clientId: req.headers["x-client-id"],
86
+ }, "Deprecated API version called");
87
+ }
88
+ next();
89
+ };
90
+ }
91
+
92
+ app.use(deprecationMiddleware(
93
+ 1,
94
+ "2027-06-01",
95
+ "https://docs.example.com/api/migration-v2"
96
+ ));
97
+ ```
98
+
99
+ ### Breaking vs. Non-Breaking Changes
100
+
101
+ ```
102
+ NON-BREAKING (no new version needed):
103
+ - Adding a new field to the response
104
+ - Adding a new optional query parameter
105
+ - Adding a new endpoint
106
+ - Making a required field optional
107
+
108
+ BREAKING (needs new version):
109
+ - Removing or renaming a field
110
+ - Changing a field's type (string -> number)
111
+ - Changing the response structure (flat -> nested)
112
+ - Making an optional field required
113
+ - Changing error response format
114
+ - Changing authentication method
115
+ ```
116
+
117
+ ### Version Negotiation Middleware
118
+
119
+ ```typescript
120
+ interface VersionConfig {
121
+ supported: number[];
122
+ current: number;
123
+ deprecated: number[];
124
+ sunset: Record<number, string>;
125
+ }
126
+
127
+ const versionConfig: VersionConfig = {
128
+ supported: [1, 2, 3],
129
+ current: 3,
130
+ deprecated: [1],
131
+ sunset: { 1: "2027-03-01T00:00:00Z" },
132
+ };
133
+
134
+ function versionNegotiation(config: VersionConfig) {
135
+ return (req: Request, res: Response, next: NextFunction) => {
136
+ const requested = parseInt(req.headers["x-api-version"] as string) || config.current;
137
+
138
+ if (!config.supported.includes(requested)) {
139
+ return res.status(400).json({
140
+ error: `API version ${requested} not supported`,
141
+ supported: config.supported,
142
+ });
143
+ }
144
+
145
+ if (config.deprecated.includes(requested)) {
146
+ res.setHeader("Deprecation", "true");
147
+ if (config.sunset[requested]) {
148
+ res.setHeader("Sunset", config.sunset[requested]);
149
+ }
150
+ }
151
+
152
+ req.apiVersion = requested;
153
+ res.setHeader("X-API-Version", String(requested));
154
+ next();
155
+ };
156
+ }
157
+ ```
158
+
159
+ ### API Changelog
160
+
161
+ ```markdown
162
+ ## v2.3.0 -- 2026-06-15
163
+ ### Added
164
+ - `GET /users/:id` now includes `createdAt` field
165
+ - New endpoint: `POST /users/:id/avatar`
166
+ ### Deprecated
167
+ - `items` field in `POST /orders` -- use `lineItems`. Sunset: 2027-01-01.
168
+ - `GET /api/v1/*` -- migrate to v2. Sunset: 2027-03-01.
169
+
170
+ ## v2.2.0 -- 2026-04-20
171
+ ### Fixed
172
+ - Pagination correctly returns `hasNextPage: false` on last page
173
+ ```
174
+
175
+ ### OpenAPI Spec Per Version
176
+
177
+ ```yaml
178
+ openapi: 3.0.3
179
+ info:
180
+ title: My API
181
+ version: "2.0.0"
182
+ servers:
183
+ - url: https://api.example.com/api/v2
184
+ paths:
185
+ /users:
186
+ get:
187
+ summary: List users
188
+ parameters:
189
+ - name: cursor
190
+ in: query
191
+ schema: { type: string }
192
+ - name: limit
193
+ in: query
194
+ schema: { type: integer, default: 20, maximum: 100 }
195
+ responses:
196
+ "200":
197
+ content:
198
+ application/json:
199
+ schema:
200
+ type: object
201
+ properties:
202
+ data:
203
+ type: array
204
+ items: { $ref: "#/components/schemas/UserV2" }
205
+ pagination:
206
+ $ref: "#/components/schemas/CursorPagination"
207
+ ```
208
+
209
+ ## Examples
210
+
211
+ | Strategy | Pros | Cons |
212
+ |----------|------|------|
213
+ | URL (`/api/v2/`) | Discoverable, cacheable | URL pollution |
214
+ | Header (`Accept: vnd.app.v2`) | Clean URLs | Less discoverable |
215
+ | Query param (`?version=2`) | Easy to test | Pollutes caching |
216
+ | GraphQL `@deprecated` | No versioning needed | Requires schema discipline |
217
+
218
+ ## Checklist
219
+ - [ ] API versioned from v1 on day one
220
+ - [ ] Versioning strategy is consistent (URL or header -- pick one)
221
+ - [ ] Breaking changes only in new version numbers
222
+ - [ ] Deprecated versions send `Deprecation` and `Sunset` headers
223
+ - [ ] Migration guide published for every breaking change
224
+ - [ ] Changelog maintained with every release
225
+ - [ ] OpenAPI spec exists per active version
226
+ - [ ] Old versions have sunset date and caller monitoring