$npx -y skills add TerminalSkills/skills --skill api-versioningVersion REST and GraphQL APIs. Use when a user asks to version an API, handle breaking changes, implement API deprecation, manage multiple API versions, or design an API evolution strategy.
| 1 | # API Versioning |
| 2 | |
| 3 | ## Overview |
| 4 | |
| 5 | APIs evolve, but breaking changes break clients. This skill covers versioning strategies (URL path, headers, query params), deprecation workflows, backwards-compatible changes, and migration patterns for REST and GraphQL APIs. |
| 6 | |
| 7 | ## Instructions |
| 8 | |
| 9 | ### Step 1: URL Path Versioning (Recommended) |
| 10 | |
| 11 | ```typescript |
| 12 | // routes/v1/projects.ts — Version 1 routes |
| 13 | import { Router } from 'express' |
| 14 | |
| 15 | const v1Router = Router() |
| 16 | |
| 17 | v1Router.get('/projects', async (req, res) => { |
| 18 | const projects = await db.project.findMany() |
| 19 | // V1 returns flat array |
| 20 | res.json(projects) |
| 21 | }) |
| 22 | |
| 23 | // routes/v2/projects.ts — Version 2 with pagination |
| 24 | const v2Router = Router() |
| 25 | |
| 26 | v2Router.get('/projects', async (req, res) => { |
| 27 | const { cursor, limit = 20 } = req.query |
| 28 | const projects = await db.project.findMany({ |
| 29 | take: Number(limit) + 1, |
| 30 | cursor: cursor ? { id: String(cursor) } : undefined, |
| 31 | }) |
| 32 | |
| 33 | const hasMore = projects.length > Number(limit) |
| 34 | if (hasMore) projects.pop() |
| 35 | |
| 36 | // V2 returns paginated envelope |
| 37 | res.json({ |
| 38 | data: projects, |
| 39 | pagination: { |
| 40 | nextCursor: hasMore ? projects[projects.length - 1].id : null, |
| 41 | hasMore, |
| 42 | }, |
| 43 | }) |
| 44 | }) |
| 45 | |
| 46 | // app.ts — Mount versions |
| 47 | app.use('/v1', v1Router) |
| 48 | app.use('/v2', v2Router) |
| 49 | ``` |
| 50 | |
| 51 | ### Step 2: Backwards-Compatible Changes |
| 52 | |
| 53 | These changes are SAFE (no version bump needed): |
| 54 | - Adding new optional fields to responses |
| 55 | - Adding new endpoints |
| 56 | - Adding new optional query parameters |
| 57 | - Adding new enum values (if clients handle unknown values) |
| 58 | |
| 59 | These changes REQUIRE a new version: |
| 60 | - Removing or renaming fields |
| 61 | - Changing field types |
| 62 | - Making optional fields required |
| 63 | - Changing response structure (array → object) |
| 64 | - Changing authentication scheme |
| 65 | |
| 66 | ```typescript |
| 67 | // Adding a field is backwards-compatible |
| 68 | // V1 response: { id, name, status } |
| 69 | // V1.1 response: { id, name, status, taskCount } ← safe, old clients ignore new field |
| 70 | |
| 71 | // Changing structure is BREAKING |
| 72 | // V1 response: [{ id, name }] |
| 73 | // V2 response: { data: [{ id, name }], pagination: {} } ← new version required |
| 74 | ``` |
| 75 | |
| 76 | ### Step 3: Deprecation Headers |
| 77 | |
| 78 | ```typescript |
| 79 | // middleware/deprecation.ts — Warn clients about deprecated versions |
| 80 | export function deprecationMiddleware(version: string, sunsetDate: string) { |
| 81 | return (req, res, next) => { |
| 82 | res.set('Deprecation', 'true') |
| 83 | res.set('Sunset', sunsetDate) // RFC 8594 |
| 84 | res.set('Link', `</v2${req.path}>; rel="successor-version"`) |
| 85 | console.log(`[DEPRECATION] ${req.method} /v${version}${req.path} from ${req.ip}`) |
| 86 | next() |
| 87 | } |
| 88 | } |
| 89 | |
| 90 | // Usage |
| 91 | app.use('/v1', deprecationMiddleware('1', 'Sat, 01 Jun 2026 00:00:00 GMT'), v1Router) |
| 92 | ``` |
| 93 | |
| 94 | ### Step 4: API Changelog |
| 95 | |
| 96 | ```markdown |
| 97 | # API Changelog |
| 98 | |
| 99 | ## v2.0.0 (2025-03-01) |
| 100 | ### Breaking Changes |
| 101 | - `GET /projects` now returns paginated response `{ data: [], pagination: {} }` |
| 102 | - Removed `GET /projects/all` (use pagination instead) |
| 103 | |
| 104 | ### Migration Guide |
| 105 | - Update response parsing to read `response.data` instead of `response` directly |
| 106 | - Implement cursor-based pagination for large datasets |
| 107 | - v1 sunset date: June 1, 2026 |
| 108 | |
| 109 | ## v1.3.0 (2025-02-15) |
| 110 | ### Added |
| 111 | - `taskCount` field in project responses |
| 112 | - `GET /projects/{id}/activity` endpoint |
| 113 | ``` |
| 114 | |
| 115 | ## Guidelines |
| 116 | |
| 117 | - URL path versioning (`/v1/`, `/v2/`) is simplest and most widely adopted. |
| 118 | - Only create new major versions for breaking changes — everything else is additive. |
| 119 | - Keep old versions running for 6-12 months minimum after deprecation. |
| 120 | - Monitor old version usage — don't sunset until traffic is near zero. |
| 121 | - Document every breaking change with a migration guide. |
| 122 | - Consider API gateways (Kong, AWS API Gateway) for routing versions independently. |