API DESIGN VERSIONING MASTERY
In the modern software landscape, an API is a **contract**. Once a developer integrates with your API, you cannot simply change the shape of your data without breaking their business. API Design is the difference between a system that scales gracefully and one that collapses under the weight of its own technical debt.
Learn API Design & Versioning: From Zero to API Architect
Goal: Deeply understand the art and science of API Design and Versioningâmastering the ability to build robust, scalable, and evolvable interfaces. You will learn how to write perfect OpenAPI specifications, implement sophisticated versioning strategies (URI, Header, and Media Type), and apply design patterns to maintain backward compatibility without bloating your codebase.
Why API Design & Versioning Matters
In the modern software landscape, an API is a contract. Once a developer integrates with your API, you cannot simply change the shape of your data without breaking their business. API Design is the difference between a system that scales gracefully and one that collapses under the weight of its own technical debt.
The Business Case for Proper API Design
Breaking changes have real costs:
- According to Postmanâs 2024 report, 52% of teams cite breaking changes as their main migration issue
- 68% of enterprises cite versioning as a top challenge in API lifecycle management
- Companies with clear versioning mechanisms can reduce breaking changes by 50%
- APIs with proper versioning experience 40% fewer security issues during transitions
- 36% of companies spend more time troubleshooting APIs than developing new features
Real-World Impact
Poor API Design Well-Designed API
ââââââââââââââââââââ ââââââââââââââââââââ
â Breaking Change â â Versioned Update â
â Released â â Released â
ââââââââââŹââââââââââ ââââââââââŹââââââââââ
â â
v v
ââââââââââââââââââââ ââââââââââââââââââââ
â Mobile Apps â â Old Clients: â
â CRASH â â Still working â
â â â â
â Support Tickets â â New Clients: â
â Flood In â â New features â
â â â â
â Customer Churn â â Seamless DX â
ââââââââââââââââââââ ââââââââââââââââââââ
Why This Matters More Than Ever
- Developer Experience (DX): A well-designed API is intuitive, self-documenting, and predictable. Over 80% of organizations now use APIs as a primary integration method.
- Business Continuity: Breaking changes cost money, time, and trust. Unexpected API changes lead to customer churn and support overhead.
- The âStripeâ Standard: Companies like Stripe have set the bar high, supporting dozens of versions simultaneously for years using date-based versioning.
- Scalability: Proper design allows you to evolve the backend implementation independently of the public interface.
- Security: Poor versioning practices increase vulnerabilities during transitions.
Historical Context
REST APIs evolved from SOAP (2000s) and RPC patterns. Roy Fieldingâs 2000 dissertation defined REST principles, but it took until the 2010s for OpenAPI (formerly Swagger) to standardize API contracts. Today, OpenAPI 3.1 is the dominant standard, with GraphQL and AsyncAPI emerging as complementary approaches for specific use cases.
The versioning problem became acute around 2015 when mobile apps made it impossible to force-upgrade all clients simultaneously. Stripeâs 2017 blog post on API versioning revolutionized how the industry thinks about backward compatibility.
Prerequisites & Background Knowledge
Essential Prerequisites (Must Have)
Before starting these projects, you should have:
- HTTP Fundamentals
- Understanding of GET, POST, PUT, PATCH, DELETE methods
- Knowledge of status codes (200, 201, 400, 404, 500)
- Headers and request/response structure
- JSON Data Structures
- Object and array syntax
- Nested structures and references
- Backend Programming
- Proficiency in at least one language (JavaScript, Python, Go, Java)
- Understanding of web frameworks (Express, FastAPI, Flask, Spring)
- Basic Git & Command Line
- Creating branches and commits
- Running shell commands
Helpful But Not Required
These topics will be learned during the projects:
- OpenAPI/Swagger specification syntax
- Design patterns (Strategy, Adapter)
- Database migrations and schema evolution
- CI/CD pipelines and automation
- Distributed systems concepts
Self-Assessment Questions
Can you answer these? If not, study the referenced topics first:
- HTTP: Whatâs the difference between PUT and PATCH? When would each cause a 409 Conflict?
- REST: Why shouldnât you use verbs in URL paths like
/getUser? - JSON: How do you represent a one-to-many relationship in JSON?
- Programming: Can you write a middleware function that intercepts requests?
- Semantics: What does âidempotentâ mean in the context of API operations?
Development Environment Setup
Required Tools:
- Code Editor: VS Code with OpenAPI extensions (recommended: Swagger Viewer)
- API Testing: Postman or curl (both free)
- Language Runtime: Node.js 18+, Python 3.10+, or Go 1.21+ (depending on project choice)
Recommended Tools:
- Swagger Editor: editor.swagger.io for live spec validation
- Prism: For mock servers (
npm install -g @stoplight/prism-cli) - Spectral: For API linting (
npm install -g @stoplight/spectral-cli) - Database: PostgreSQL or SQLite for Projects 5, 8
- Docker: For Projects 5, 11, 12
Time Investment
Realistic estimates for complete mastery:
- Beginner projects (1-2): 2-3 weekends (16-24 hours total)
- Intermediate projects (3-6, 9-10): 4-8 weeks part-time (40-80 hours)
- Advanced projects (4, 7, 11): 6-12 weeks part-time (60-120 hours)
- Expert projects (8, 12): 3-6 months part-time (100-200 hours)
Total learning path: 6-12 months to internalize all concepts and complete all projects.
Important Reality Check
API design is deceptively difficult. The syntax is simple (YAML/JSON), but the decisions are hard:
- Should this field be optional or required?
- Is this change backward compatible?
- How do I evolve this without breaking clients?
Expect to:
- Redesign your specs multiple times before finding the ârightâ model
- Struggle with the tradeoff between âclean designâ and âbackward compatibilityâ
- Spend more time thinking than coding (this is good!)
- Question every decision (also good!)
Concept Summary Table
| Concept Cluster | What You Need to Internalize |
|---|---|
| Resource Mapping | APIs are about nouns and state, not verbs and actions. Mapping real-world entities to URLs. |
| Contract-First Design | The specification (OpenAPI) is the source of truth, not the code. Design before building. |
| Semantic Versioning | Major (breaking), Minor (feature), Patch (fix). Understanding what constitutes a âbreak.â |
| Idempotency | Ensuring that calling an operation multiple times has the same result as calling it once. |
| Versioning Strategies | URI vs. Header vs. Media Type. Trade-offs in caching, visibility, and complexity. |
| Strategy Pattern | Decoupling the API contract from the business logic to support multiple versions concurrently. |
Deep Dive Reading by Concept
This section maps each concept to specific book chapters. Read these before or alongside the projects to build strong mental models.
API Foundations & Design
| Concept | Book & Chapter |
|---|---|
| The API Lifecycle | âPrinciples of Web API Designâ by James Higginbotham â Ch. 1-2 |
| Resource-Oriented Design | âThe Design of Web APIsâ by Arnaud Lauret â Ch. 4: âDesigning Resourcesâ |
| OpenAPI Fundamentals | âThe Design of Web APIsâ by Arnaud Lauret â Ch. 12: âDocumenting the APIâ |
Versioning & Evolution
| Concept | Book & Chapter |
|---|---|
| Versioning Strategies | âAPI Design Patternsâ by JJ Geewax â Ch. 22: âVersioningâ |
| Breaking Changes | âDesigning Web APIsâ by Jin, Sahni, & Shevat â Ch. 9: âEvolving an APIâ |
| Backward Compatibility | âPrinciples of Web API Designâ by James Higginbotham â Ch. 11: âEvolving APIsâ |
Essential Reading Order
- Foundation (Week 1):
- Principles of Web API Design Ch. 1-3 (Aligning with goals)
- The Design of Web APIs Ch. 3-4 (Resource design)
- The Contract (Week 2):
- OpenAPI 3.1 Specification (Official Docs - read the structure)
- The Design of Web APIs Ch. 12
- Evolution (Week 3):
- API Design Patterns Ch. 22
- Designing Web APIs Ch. 9
Quick Start Guide
Feeling overwhelmed by 12 projects? Start here.
Your First 48 Hours
Day 1: Foundation (4 hours)
- Read Arnaud Lauretâs âThe Design of Web APIsâ Ch. 3-4 (Resource design)
- Explore 3 real-world API docs: Stripe, GitHub, Twilio
- Compare their versioning approaches
Day 2: Your First Spec (4 hours)
- Start Project 1 (API Spec Architect)
- Model a simple âBlog APIâ with posts and comments
- Validate it in Swagger Editor
- Generate docs with Redocly
First Week Goals
By end of week 1, you should:
- Have completed Project 1 (OpenAPI spec)
- Understand what makes a change âbreakingâ
- Know the difference between URI and Header versioning
- Be able to explain idempotency
First Month Milestones
- Week 1: Projects 1-2 (Spec + Mock Server)
- Week 2: Project 3 (URI Versioning)
- Week 3: Project 4 (Strategy Pattern)
- Week 4: Review and solidify concepts
Success metric: You can design a spec and explain your design decisions confidently.
Recommended Learning Paths
Path 1: The Complete Beginner
Starting from zero REST knowledge
Week 1-2: Project 1 (Spec Architect)
â
Week 3: Project 2 (Mock Server)
â
Week 4-5: Project 3 (URI Versioning)
â
Week 6: Pause - Read JJ Geewax Ch. 22
â
Week 7-9: Project 4 (Strategy Pattern)
â
Week 10: Project 6 (Deprecation)
Expected outcome: Solid foundation in API design and basic versioning.
Path 2: The Backend Developer
Youâve built APIs before, want to level up
Week 1: Project 1 (Quick review)
â
Week 2-3: Project 4 (Strategy Pattern) â Start here
â
Week 4-5: Project 7 (HATEOAS)
â
Week 6-8: Project 11 (Stripe-Style) â The masterpiece
â
Week 9: Project 12 (Gateway)
Expected outcome: Advanced architectural patterns, production-ready versioning.
Path 3: The DevOps/Platform Engineer
Focus on automation and infrastructure
Week 1: Project 2 (Linting/Mocking)
â
Week 2-3: Project 9 (Diffing Engine) â Your bread and butter
â
Week 4-5: Project 12 (Gateway Router)
â
Week 6-7: Project 5 (Legacy Adapter)
Expected outcome: CI/CD mastery, automated API governance.
Path 4: The Architect
System design and long-term evolution
Week 1-2: Projects 1-2 (Fast review)
â
Week 3-4: Project 8 (Schema Evolution) â Critical
â
Week 5-7: Project 11 (Stripe-Style)
â
Week 8-10: Final Project (Immortal API)
Expected outcome: Ability to design APIs that last 10+ years.
Project 1: The API Spec Architect (Contract-First Mastery)
- File: API_DESIGN_VERSIONING_MASTERY.md
- Main Programming Language: YAML (OpenAPI 3.1)
- Alternative Programming Languages: JSON, TypeScript (TypeSpec)
- Coolness Level: Level 2: Practical but Forgettable
- Business Potential: 1. The âResume Goldâ
- Difficulty: Level 1: Beginner
- Knowledge Area: API Specification / Domain Modeling
- Software or Tool: Swagger Editor, Stoplight Elements
- Main Book: âThe Design of Web APIsâ by Arnaud Lauret
What youâll build: A comprehensive OpenAPI 3.1 specification for a complex âDigital Walletâ system, including accounts, transactions, currency conversions, and user profiles.
Why it teaches API Design: You will learn to model complex domain relationships using only a specification. This forces you to think about data structures, security schemes, and error codes before a single line of application code is written.
Core challenges youâll face:
- Modeling Recursive Relationships â maps to understanding how accounts link to transactions
- Implementing Polymorphism with
oneOf/anyOfâ maps to different transaction types (Credit, Debit, Transfer) - Designing Reusable Components â maps to minimizing duplication in the specification
Key Concepts:
- OpenAPI Components: Swagger.io Documentation
- API Status Codes: MDN Web Docs - HTTP Status Codes
- Resource Hierarchy: âThe Design of Web APIsâ Ch. 4
Difficulty: Beginner Time estimate: Weekend Prerequisites: Basic understanding of YAML and HTTP.
Real World Outcome
You will have a single openapi.yaml file that, when dropped into a viewer like Swagger UI, renders a professional, interactive documentation portal.
Example Output:
# Rendering your spec with Redocly
$ npx @redocly/cli build-docs openapi.yaml
[info] Successfully generated docs into redoc-static.html
The Core Question Youâre Answering
âCan I describe my entire systemâs behavior perfectly without writing a single line of executable code?â
Before you write any code, sit with this question. A spec is a contract. If the spec is vague, the implementation will be buggy. Developers often rush to code and âdocument later,â but later never comes.
Concepts You Must Understand First
Stop and research these before coding:
- REST Resource Modeling
- What is the difference between a resource and an endpoint?
- Why shouldnât you use verbs in URLs?
- Book Reference: âThe Design of Web APIsâ Ch. 4 - Arnaud Lauret
- OpenAPI 3.x Structure
- What is the purpose of the
componentssection? - How does
$refwork for reusability? - Book Reference: âThe Design of Web APIsâ Ch. 12
- What is the purpose of the
Questions to Guide Your Design
Before implementing, think through these:
- Entity Relationships
- How should a âTransactionâ link to a âUserâ? By ID or by full object?
- Should
GET /accountsreturn the full transaction history or just a link?
- Security
- How will you describe Bearer Token authentication in the spec?
- Can you define different scopes for reading vs writing?
Thinking Exercise
The âNew Fieldâ Impact
Imagine you have a User object. You need to add a middleName field.
# Current
User:
type: object
properties:
firstName: { type: string }
lastName: { type: string }
Questions while analyzing:
- Is adding
middleNamea breaking change? - What if you make it
required: [firstName, middleName, lastName]? - How would a mobile app built 6 months ago react to this change?
The Interview Questions Theyâll Ask
Prepare to answer these:
- âExplain the difference between PUT and PATCH and when to use each.â
- âWhat makes a change âbreakingâ in an API?â
- âHow do you handle pagination in a RESTful way?â
- âWhy is the OpenAPI spec valuable for frontend teams?â
- âDescribe the importance of idempotency in API design.â
Hints in Layers
Hint 1: Start with the paths List all the actions a user can take (create account, transfer money) and map them to HTTP methods.
Hint 2: Identify common objects
Create a schemas section in components for User, Account, and Transaction so you can reuse them.
Hint 3: Use meaningful status codes Donât just return 200. Use 201 for creation, 400 for validation errors, and 404 for missing resources.
Hint 4: Validate your YAML Use an editor like Swagger Editor (editor.swagger.io) to catch syntax errors immediately.
Books That Will Help
| Topic | Book | Chapter |
|---|---|---|
| Resource Modeling | âThe Design of Web APIsâ by Arnaud Lauret | Ch. 4 |
| Documentation | âThe Design of Web APIsâ by Arnaud Lauret | Ch. 12 |
Common Pitfalls & Debugging
Problem 1: âSwagger Editor shows âResolver error at pathsâŚââ
- Why: You likely have a typo in a
$refpath or the referenced component doesnât exist - Fix: Check that
$ref: '#/components/schemas/User'matches exactly the name in yourcomponents.schemassection (case-sensitive!) - Quick test: Copy the reference path and manually navigate to it in your YAML to verify it exists
Problem 2: âAll my endpoints return the same schema but I have tons of duplicationâ
- Why: Youâre not using
$reffor reusability - Fix: Define common responses in
components.responsesand reference them:$ref: '#/components/responses/NotFound' - Quick test: If youâre copy-pasting YAML, you need more
$refusage
Problem 3: âShould âcreated_atâ be required or optional?â
- Why: This is a design decision, not a syntax error
- Fix: Server-generated fields (timestamps, IDs) should be marked
readOnly: trueand excluded from POST requests but required in GET responses - Quick test: Ask âDoes the client provide this, or does the server generate it?â
Problem 4: âSwagger UI shows my example, but it doesnât match my schemaâ
- Why: Examples are not validated by default
- Fix: Use Spectral linter with
oas-example-schema-matchesrule to catch mismatches - Quick test:
spectral lint openapi.yaml --ruleset spectral:oas
Problem 5: âI changed my spec but the mock server still returns old dataâ
- Why: Prism caches examples or the process didnât restart
- Fix: Kill and restart Prism:
pkill prism && prism mock openapi.yaml - Quick test: Add
?__cacheBust=123to your request URL
Project 2: The Contract Validator & Mock Server
- File: API_DESIGN_VERSIONING_MASTERY.md
- Main Programming Language: Node.js
- Alternative Programming Languages: Python, Go
- Coolness Level: Level 3: Genuinely Clever
- Business Potential: 2. The âMicro-SaaS / Pro Toolâ
- Difficulty: Level 2: Intermediate
- Knowledge Area: Tooling / Contract Testing
- Software or Tool: Prism, Spectral
- Main Book: âAPI Design Patternsâ by JJ Geewax
What youâll build: A toolchain that takes your Project 1 spec, runs a âlinterâ to ensure best practices (naming, status codes), and spins up a dynamic mock server that validates incoming requests against the spec.
Why it teaches API Design: This project makes the âContractâ alive. Youâll see how a spec can actually enforce behavior and provide a working sandbox for frontend teams before the backend is even built.
Core challenges youâll face:
- Setting up Automated Linting â maps to enforcing consistent naming conventions
- Dynamic Response Generation â maps to using examples from the spec to drive the mock
- Request Validation â maps to intercepting calls and checking them against OAS schemas
Difficulty: Intermediate Time estimate: 1 week Prerequisites: Project 1 completed, basic CLI tool knowledge.
Real World Outcome
A CI/CD pipeline step that fails if the API design is âuglyâ and a local command that developers run to start a âfakeâ backend.
Example Output:
$ spectral lint openapi.yaml
â Path must follow kebab-case paths./user_profile
â Operation must have a 400 response paths./transfer.post
$ prism mock openapi.yaml
[PRISM] [14:00:00] Mock server listening on http://127.0.0.1:4010
[PRISM] [14:00:05] GET /accounts/123 -> 200 OK (Validated)
[PRISM] [14:00:12] POST /transfer {"amount": 100} -> 201 Created
# Frontend developers can now work in parallel!
The Core Question Youâre Answering
âCan I catch API design mistakes before they reach production?â
Before you code, consider this: Every badly-named endpoint, every missing 400 response, every inconsistent casing becomes technical debt. Linters automate what code reviews miss. Mock servers let frontend teams work independently, doubling team productivity.
Concepts You Must Understand First
Stop and research these before coding:
- API Linting and Style Guides
- What makes an API âRESTfulâ? (Hint: More than just using HTTP)
- Why does consistent naming matter across 100+ endpoints?
- Book Reference: âThe Design of Web APIsâ Ch. 10 - Arnaud Lauret
- Contract-Driven Development
- How does a mock server validate requests against a schema?
- What happens if you send invalid JSON to a mock?
- Book Reference: âAPI Design Patternsâ Ch. 1 - JJ Geewax
Questions to Guide Your Design
Before implementing, think through these:
- Linting Rules
- Should you enforce kebab-case or snake_case in paths?
- Do all POST endpoints need to return 201? What about idempotent creates?
- Mock Server Behavior
- Should the mock server return static examples or generate random data?
- How should it handle path parameters like
/users/{id}?
Thinking Exercise
The âValidation Paradoxâ
You have this spec:
/users/{id}:
get:
responses:
'200':
schema:
type: object
required: [id, name]
'404':
description: User not found
Someone requests GET /users/abc (non-numeric ID).
Questions while analyzing:
- Should the mock return 404 (semantically correct) or 400 (validation error)?
- Does your spec even define a 400 response?
- How would Prism handle this vs. a real server?
The Interview Questions Theyâll Ask
Prepare to answer these:
- âWhat are the benefits of contract-first API development?â
- âHow would you enforce API design standards across a team of 20 developers?â
- âExplain how a mock server can speed up frontend development.â
- âWhatâs the difference between validation at the gateway vs. validation in the service?â
- âHow do you handle API versioning in your OpenAPI specs?â
Hints in Layers
Hint 1: Start with Spectral
Create a .spectral.yaml file with rules like path-case, operation-tag-defined, operation-success-response.
Hint 2: Use Prismâs validation mode
Run prism mock -d openapi.yaml to enable request/response validation. It will return errors if clients send invalid data.
Hint 3: Integrate into CI
Add a GitHub Action or GitLab CI step: spectral lint openapi.yaml || exit 1 to block merges with bad specs.
Hint 4: Generate examples automatically
Use a tool like openapi-examples-validator to ensure your example fields match your schema definitions.
Books That Will Help
| Topic | Book | Chapter |
|---|---|---|
| Contract Testing | âAPI Design Patternsâ by JJ Geewax | Ch. 1 |
| API Style Guides | âThe Design of Web APIsâ by Arnaud Lauret | Ch. 10 |
Common Pitfalls & Debugging
Problem 1: âSpectral says my paths are wrong but they look fineâ
- Why: Youâre using inconsistent casing (e.g.,
/userProfilevs/user-profile) - Fix: Choose a convention (kebab-case recommended) and apply it everywhere
- Quick test: Run
spectral lintwith thepath-caserule
Problem 2: âPrism returns 500 errors for everythingâ
- Why: Your spec has invalid references or malformed examples
- Fix: Validate spec first with
swagger-cli validate openapi.yaml - Quick test: Load spec in Swagger Editor and fix all red errors
Problem 3: âFrontend team says mock returns wrong data typesâ
- Why: Your examples donât match your schema (string vs number, etc.)
- Fix: Use Spectralâs
oas-example-schema-matchesrule - Quick test: Compare your
exampleagainst yourtypedefinition
Project 3: The Multi-Version Router (URI Versioning)
- File: API_DESIGN_VERSIONING_MASTERY.md
- Main Programming Language: TypeScript (Node/Express or Hono)
- Alternative Programming Languages: Go (Chi), Python (FastAPI)
- Coolness Level: Level 3: Genuinely Clever
- Business Potential: 1. The âResume Goldâ
- Difficulty: Level 2: Intermediate
- Knowledge Area: Implementation / Routing
- Software or Tool: Express.js, Postman
- Main Book: âAPI Versioning for The Real Worldâ by Dan Patrascu
What youâll build: A backend service that serves two versions of the same resource simultaneously using URI versioning (/v1/... and /v2/...). You will implement a change where a field is renamed (e.g., user_name to fullName) and both versions must still work.
Why it teaches Versioning: Youâll grapple with code duplication vs. abstraction. Should you have two controllers? Or one controller with an if statement? This project teaches you the cost of branching.
Core challenges youâll face:
- Namespace Routing â maps to organizing code so v1 and v2 donât collide
- Shared Data Layer â maps to making one database model serve two different API shapes
- Testing for Regressions â maps to ensuring changes in v2 donât accidentally break v1
Difficulty: Intermediate Time estimate: 1-2 weeks Prerequisites: Project 1 & 2.
Real World Outcome
Two distinct endpoints that return the same underlying data but in different formats.
Example Output:
$ curl http://api.local/v1/users/1
{ "id": 1, "user_name": "Alice" }
$ curl http://api.local/v2/users/1
{ "id": 1, "fullName": "Alice Smith" }
# Same database record, two different API shapes!
The Core Question Youâre Answering
âHow do I support old mobile apps while shipping new features?â
Before coding, grasp this reality: Mobile apps donât auto-update. Users on iOS 14 from 2020 might still use your app in 2025. URI versioning (/v1/, /v2/) is the most visible, cacheable approachâbut it creates code duplication. This project forces you to confront that tradeoff.
Concepts You Must Understand First
Stop and research these before coding:
- URI Versioning vs. Other Strategies
- Why is
/v1/usersbetter than/users?version=1? - How does this affect HTTP caching (CDN, browser cache)?
- Book Reference: âAPI Design Patternsâ Ch. 22 - JJ Geewax
- Why is
- Code Organization Patterns
- Should v1 and v2 share the same controller logic?
- How do you avoid duplicating database queries?
- Book Reference: âClean Architectureâ Ch. 22 - Robert C. Martin
Questions to Guide Your Design
Before implementing, think through these:
- Routing Strategy
- Do you create separate Express routers for
/v1/and/v2/? - Should version detection happen in middleware or per-route?
- Do you create separate Express routers for
- Data Transformation
- Do you transform at the database layer or presentation layer?
- If
user_namein DB becomesfullNamein v2, where does the mapping happen?
Thinking Exercise
The âShared Logic Dilemmaâ
You have business logic that validates user input (e.g., âemail must be valid formatâ).
// Option A: Duplicate the validation in v1 and v2
// v1/users.controller.ts
if (!isValidEmail(email)) throw new Error();
// v2/users.controller.ts
if (!isValidEmail(email)) throw new Error();
// Option B: Shared service layer
// shared/users.service.ts
validateEmail(email) { ... }
Questions while analyzing:
- If you fix a bug in validation logic, do you want it applied to both versions automatically?
- What if v2 has stricter validation (e.g., email + phone required)?
- Where do you draw the line between âsharedâ and âversion-specificâ?
The Interview Questions Theyâll Ask
Prepare to answer these:
- âWhat are the pros and cons of URI versioning compared to header-based versioning?â
- âHow would you handle a breaking database schema change in a versioned API?â
- âExplain how you would deprecate v1 while keeping it functional for legacy clients.â
- âHow does URI versioning affect HTTP caching?â
- âCan you describe a scenario where URI versioning is the wrong choice?â
Hints in Layers
Hint 1: Start with routing
Create two routers: v1Router and v2Router. Mount them at /api/v1 and /api/v2 in your main app.
Hint 2: Share the database layer Both versions should call the same repository/DAO. Version-specific transformations happen in controllers or response mappers.
Hint 3: Use DTOs (Data Transfer Objects)
Create UserV1DTO and UserV2DTO classes. Map from your database model to the appropriate DTO based on the route.
Hint 4: Write version-aware tests
Donât just test âdoes /users work?â Test âdoes /v1/users return user_name and /v2/users return fullName?â
Books That Will Help
| Topic | Book | Chapter |
|---|---|---|
| Versioning Strategies | âAPI Design Patternsâ by JJ Geewax | Ch. 22 |
| Clean Architecture | âClean Architectureâ by Robert C. Martin | Ch. 22 |
Common Pitfalls & Debugging
Problem 1: âChanges to v2 accidentally break v1â
- Why: Youâre sharing too much code without proper abstraction
- Fix: Create separate controllers or use transformation layers that are tested independently
- Quick test: Make a change to v2, then run v1 tests. If they fail, you have coupling.
Problem 2: âURL /v1/v1/users appears in responsesâ
- Why: Your base URL configuration is duplicating the version prefix
- Fix: Check your route mounting. Use
app.use('/v1', v1Router), notapp.use('/api/v1', v1Router)if your router already includes/v1/ - Quick test:
console.log(req.baseUrl)to see what Express thinks the base is
Problem 3: âDatabase query runs twice for the same requestâ
- Why: Both your v1 and v2 controllers are fetching independently
- Fix: Extract data fetching to a shared service, let version-specific logic only handle transformation
- Quick test: Add logging to your database queries and check if the same query runs multiple times
Problem 4: âNot sure whether to version /health or /metrics endpointsâ
- Why: Operational endpoints are different from business endpoints
- Fix: Keep operational endpoints unversioned (
/health) since theyâre for infrastructure, not clients - Quick test: Ask âWould a monitoring system care about API versions?â If no, donât version it.
Project 4: The Strategy Pattern Implementer (Header Versioning)
- File: API_DESIGN_VERSIONING_MASTERY.md
- Main Programming Language: TypeScript/JavaScript
- Alternative Programming Languages: C#, Java, Go
- Coolness Level: Level 4: Hardcore Tech Flex
- Business Potential: 4. The âOpen Coreâ Infrastructure
- Difficulty: Level 3: Advanced
- Knowledge Area: Software Design Patterns
- Software or Tool: Any modern Web Framework
- Main Book: âAPI Design Patternsâ by JJ Geewax
What youâll build: A version-aware API where the version is NOT in the URL, but in the Accept-Version custom header. You will use the Strategy Pattern to select the appropriate âTransformerâ class that shapes the response based on the requested version.
Why it teaches Versioning: This project forces you to decouple your routing logic from your business logic. Youâll learn how to keep your controllers âversion-agnosticâ while using specialized transformers to handle the API contract variations.
Core challenges youâll face:
- Header Parsing & Defaults â maps to deciding what version to serve if no header is present
- The Strategy Registry â maps to dynamically loading the right transformer for v1, v2, etc.
- Avoiding Code Duplication â maps to using inheritance or composition in your transformers
Key Concepts:
- Strategy Pattern: Refactoring.Guru - Strategy Pattern
- Content Negotiation: MDN - Content Negotiation
- Custom Headers: IETF RFC 6648 (Deprecating X- prefixes)
Difficulty: Advanced Time estimate: 1-2 weeks Prerequisites: Understanding of Object-Oriented Design Patterns.
Real World Outcome
A single endpoint that behaves differently based on the clientâs headers.
Example Output:
# Requesting v1
$ curl -H "Accept-Version: 1.0" http://api.local/profile
{ "name": "John Doe", "address": "123 Main St" }
# Requesting v2
$ curl -H "Accept-Version: 2.0" http://api.local/profile
{ "firstName": "John", "lastName": "Doe", "location": { "street": "123 Main St" } }
The Core Question Youâre Answering
âHow can I support multiple API versions without my controllers becoming a mess of if/else statements?â
Before coding, imagine supporting 10 versions. If you have 10 if statements in every function, your code is unmaintainable. The Strategy Pattern allows you to swap âtransformersâ based on the request metadata.
Concepts You Must Understand First
Stop and research these before coding:
- The Strategy Design Pattern
- How does a Context object use a Strategy interface?
- How can you implement this without a heavy OOP language? (e.g., using a map of functions)
- Book Reference: âDesign Patternsâ - Gamma et al. (The Gang of Four)
- HTTP Content Negotiation
- What are the
AcceptandContent-Typeheaders? - How does a server decide which version to return when multiple are supported?
- Book Reference: âAPI Design Patternsâ Ch. 22
- What are the
Questions to Guide Your Design
Before implementing, think through these:
- Version Resolution
- Where should the version selection happen? In a middleware, or inside the controller?
- What happens if the client requests a version that doesnât exist? (Return 406 Not Acceptable or 400 Bad Request?)
- Data Transformation
- Should the âv1 strategyâ fetch from a different database table, or just hide fields from the main model?
- How do you handle common logic that both v1 and v2 share?
Thinking Exercise
The âBranchingâ Nightmare
Compare these two pseudo-code snippets:
Approach A (Conditional logic):
function getProfile(req, res) {
const data = db.getUser();
if (req.header('v') === '1') {
return res.json({ name: data.name });
} else {
return res.json({ first: data.first, last: data.last });
}
}
Approach B (Strategy Pattern):
function getProfile(req, res) {
const data = db.getUser();
const transformer = TransformerFactory.get(req.header('v'));
return res.json(transformer.transform(data));
}
Questions while analyzing:
- Which one is easier to test?
- Which one is easier to delete when v1 is deprecated?
- How many lines of code would you have to change in Approach A if you added v3?
The Interview Questions Theyâll Ask
Prepare to answer these:
- âWhat are the pros and cons of URI versioning vs Header versioning?â
- âHow would you use the Strategy Pattern to manage API versioning?â
- âWhat HTTP status code should you return for an unsupported version?â
- âHow do you handle default versions for existing clients?â
- âCan you explain how Stripe handles versioning without breaking old clients?â
Hints in Layers
Hint 1: Create a Version Middleware
Extract the version from the header and attach it to the request object (req.apiVersion).
Hint 2: Define a Transformer Interface
Create a base class or interface with a transform(data) method. Every version (v1, v2) will implement this.
Hint 3: Use a Factory
Create a TransformerFactory that takes a version string and returns the correct instance of a transformer.
Hint 4: Centralize your defaults If no version is provided, always default to the oldest stable version to avoid breaking unknown clients, or the latest version for new ones (choose a policy).
Books That Will Help
| Topic | Book | Chapter |
|---|---|---|
| Strategy Pattern | âDesign Patternsâ by Gamma et al. | Ch. 5 (Behavioral) |
| Versioning Patterns | âAPI Design Patternsâ by JJ Geewax | Ch. 22 |
Common Pitfalls & Debugging
Problem 1: âVersion header is ignored; always getting default versionâ
- Why: Middleware might not be extracting the header correctly
- Fix: Check exact header name (case-sensitive!):
Accept-Versionvsaccept-version - Quick test:
console.log(req.headers)to see all incoming headers
Problem 2: âStrategy Factory returns undefined transformerâ
- Why: Version string doesnât match registered keys (â1.0â vs â1â vs âv1â)
- Fix: Normalize version strings in middleware before lookup
- Quick test: Add logging in factory:
console.log('Requested:', version, 'Available:', Object.keys(strategies))
Problem 3: âBoth v1 and v2 transformers run on same requestâ
- Why: Youâre not returning early after transformation
- Fix: Ensure strategy selection is mutually exclusive with
if/elseorswitch - Quick test: Add console.log in each transformer; you should see only one fire
Problem 4: âTransformer code has tons of if/else for field differencesâ
- Why: Youâre trying to do too much in one transformer class
- Fix: Use composition: small, focused transformers that chain together
- Quick test: If a transformer has >5 if statements, itâs doing too much
Project 5: The Legacy Adapter Layer (Backward Compatibility)
- File: API_DESIGN_VERSIONING_MASTERY.md
- Main Programming Language: Go or Python
- Alternative Programming Languages: Node.js, Ruby
- Coolness Level: Level 3: Genuinely Clever
- Business Potential: 3. The âService & Supportâ Model
- Difficulty: Level 3: Advanced
- Knowledge Area: Architectural Patterns / Refactoring
- Software or Tool: Docker (to simulate old/new services)
- Main Book: âMonolith to Microservicesâ by Sam Newman
What youâll build: You have a âLegacyâ service and a âModernâ service. You will build an API Gateway/Adapter Layer that accepts requests for the old v1 API but translates and forwards them to the new Modern v2 service.
Why it teaches Versioning: This is the âStrangle Patternâ in action. Youâll learn how to migrate your backend entirely while keeping the public interface stable for old clients who refuse to upgrade.
Core challenges youâll face:
- Payload Translation â maps to mapping old JSON keys to new ones
- Response Synthesis â maps to combining multiple new service calls to satisfy one old request
- Error Mapping â maps to ensuring new error codes are translated back to v1 error codes
Real World Outcome
You can shut down your legacy database and code, yet old mobile apps still function perfectly by talking to your Adapter.
Example Output:
# Request to the ADAPTER (acting as the old API)
$ curl http://adapter.local/v1/legacy-endpoint
# The adapter calls Modern Service -> Transforms Result -> Returns to User
{ "status": "success", "old_field": "new_data" }
The Core Question Youâre Answering
âHow can I replace my backend entirely without breaking old clients?â
Before coding, understand this pattern: The Adapter is the âtranslatorâ between old contracts and new reality. Itâs how companies migrate from monoliths to microservices without a âbig bangâ rewrite. This is the Strangler Fig pattern in action.
Concepts You Must Understand First
Stop and research these before coding:
- The Adapter Design Pattern
- How does an adapter translate between incompatible interfaces?
- Why is this better than modifying the new service to support old formats?
- Book Reference: âDesign Patternsâ Ch. 4 - Gamma et al.
- The Strangler Fig Migration Pattern
- How do you incrementally migrate without downtime?
- Whatâs the difference between âstranglerâ and âbig bangâ migration?
- Book Reference: âMonolith to Microservicesâ Ch. 3 - Sam Newman
Questions to Guide Your Design
Before implementing, think through these:
- Translation Logic
- Does the adapter call one modern endpoint or multiple?
- How do you handle fields that exist in v1 but not in the modern service?
- Error Handling
- If the modern service returns 422 (Unprocessable Entity), should the adapter translate to 400 (Bad Request) for v1 clients?
Thinking Exercise
The âMulti-Call Synthesisâ
Old API: GET /v1/user/123 returns { id, name, email, address, orderHistory }
New Architecture:
- User Service:
/users/123â{ id, name, email } - Address Service:
/addresses?userId=123â{ street, city } - Order Service:
/orders?userId=123â[{ orderId, date }]
Questions while analyzing:
- Does the adapter make 3 separate calls and combine the results?
- What if one service is down? Return partial data or error?
- How do you handle latency (now 3x slower than before)?
The Interview Questions Theyâll Ask
Prepare to answer these:
- âExplain the Strangler Fig pattern and when youâd use it.â
- âHow would you migrate from a monolith to microservices without API downtime?â
- âWhat are the performance implications of an adapter layer?â
- âHow do you handle backwards-incompatible changes during migration?â
- âWhen would you retire the adapter layer entirely?â
Hints in Layers
Hint 1: Adapter as a proxy
The adapter should act like a reverse proxy, listening on the old /v1/ paths but forwarding (transformed) to modern services.
Hint 2: Use HTTP client libraries
In Go: net/http, in Python: requests or httpx, in Node: axios. Make the adapter a HTTP client to modern services.
Hint 3: Cache aggressively If the modern service is slower, add caching in the adapter to maintain old SLAs.
Hint 4: Versioned error codes
Create an error mapping table: { 422: 400, 429: 503 } so modern errors become v1-compatible errors.
Books That Will Help
| Topic | Book | Chapter |
|---|---|---|
| Adapter Pattern | âDesign Patternsâ by Gamma et al. | Ch. 4 |
| Strangler Fig | âMonolith to Microservicesâ by Sam Newman | Ch. 3 |
Common Pitfalls & Debugging
Problem 1: âAdapter is too slow; timeout errors everywhereâ
- Why: Youâre making synchronous calls to multiple services sequentially
- Fix: Use async/parallel requests (
Promise.all()in Node,asyncio.gather()in Python) - Quick test: Time a single adapter call vs direct modern service call
Problem 2: âOne microservice is down and adapter crashesâ
- Why: No error handling; propagating exceptions
- Fix: Implement circuit breaker pattern or return degraded responses
- Quick test: Shut down one microservice; adapter should handle gracefully
Problem 3: âOld clients see new field names leaking throughâ
- Why: Adapter is passing through modern responses without transformation
- Fix: Explicit field mapping, never
return modernResponse;directly - Quick test: Compare adapter response to old v1 spec character-by-character
Project 6: The Deprecation & Sunset Manager
- File: API_DESIGN_VERSIONING_MASTERY.md
- Main Programming Language: Any
- Alternative Programming Languages: N/A (Standard based)
- Coolness Level: Level 2: Practical but Forgettable
- Business Potential: 1. The âResume Goldâ
- Difficulty: Level 2: Intermediate
- Knowledge Area: HTTP Standards / Communication
- Software or Tool: Postman, Browser DevTools
- Main Book: âDesigning Web APIsâ by Jin, Sahni, & Shevat
What youâll build: A system that automatically injects Deprecation and Sunset headers into API responses for endpoints that are scheduled for retirement. Youâll also build a small dashboard that parses these headers to alert developers.
Why it teaches API Lifecycle: Designing an API is easy; retiring one is hard. This project teaches you how to communicate âThe Endâ to your users programmatically, following IETF standards.
Core challenges youâll face:
- Implementing RFC 8594 â maps to understanding the Sunset header format
- Middleware Integration â maps to injecting headers globally based on a deprecation config
- Client-Side Awareness â maps to writing a fetch wrapper that logs warnings if it sees these headers
Key Concepts:
- Sunset Header: RFC 8594
- Deprecation Header: draft-ietf-httpapi-deprecation-header
- Graceful Degradation: MDN Web Docs
Real World Outcome
Every response from a âdyingâ endpoint warns the developer in their console or logs.
Example Output:
$ curl -i http://api.local/v1/legacy-users
HTTP/1.1 200 OK
Deprecation: true
Sunset: Sat, 31 Dec 2025 23:59:59 GMT
Link: <https://docs.api.local/migration>; rel="deprecation"
[{"id": 1, "name": "Alice"}]
# Clients see headers warning them of upcoming retirement
The Core Question Youâre Answering
âHow do I communicate âThe Endâ to developers programmatically?â
Before coding, understand this: Developers donât read your changelogs. They need machine-readable signals. RFC 8594 (Sunset header) and the Deprecation header spec provide standard ways to say âThis endpoint will die on DATE. Migrate now.â
Concepts You Must Understand First
Stop and research these before coding:
- HTTP Sunset Header (RFC 8594)
- Whatâs the exact date format for Sunset header? (HTTP-date)
- Can you sunset individual fields, or only entire endpoints?
- Book Reference: IETF RFC 8594 (available online)
- Graceful Degradation vs. Immediate Shutdown
- Should deprecated endpoints still work fully, or return warnings?
- At what point do you return 410 Gone instead of 200 OK?
- Book Reference: âDesigning Web APIsâ Ch. 9 - Jin, Sahni, & Shevat
Questions to Guide Your Design
Before implementing, think through these:
- Deprecation Policy
- How far in advance do you warn? (30 days? 6 months?)
- Do different endpoint types get different deprecation windows?
- Client Detection
- Should you log which clients are still hitting deprecated endpoints?
- Do you need analytics to know when usage drops to zero?
Thinking Exercise
The âTimeline Paradoxâ
You announce deprecation on Jan 1, 2025. Sunset date is July 1, 2025.
On June 30, you still see 10,000 requests/day to the deprecated endpoint.
Questions while analyzing:
- Do you extend the deadline (rewarding procrastinators)?
- Do you shut down anyway (potentially breaking apps)?
- How do you identify which clients are still using it?
- Should you have different SLAs (slower responses, rate limits)?
The Interview Questions Theyâll Ask
Prepare to answer these:
- âExplain the difference between Deprecation and Sunset headers.â
- âHow long should a deprecation period be for a public API?â
- âWhat HTTP status code should you return after the sunset date?â
- âHow would you handle clients that ignore deprecation warnings?â
- âCan you describe a real-world API deprecation that went badly?â
Hints in Layers
Hint 1: Middleware for headers Create middleware that checks if the requested path matches a deprecation config, then injects headers.
Hint 2: Config-driven Store deprecation metadata in a JSON/YAML file:
deprecated_endpoints:
- path: /v1/users
sunset: "2025-12-31T23:59:59Z"
link: https://docs.api.local/v1-to-v2
Hint 3: Client dashboard Build a simple page that parses Sunset headers from responses and displays a migration checklist.
Hint 4: Gradual performance degradation After sunset date passes, add intentional delays (e.g., 5-second sleep) to deprecated endpoints as a âsoft shutdown.â
Books That Will Help
| Topic | Book | Chapter |
|---|---|---|
| API Evolution | âDesigning Web APIsâ by Jin, Sahni, & Shevat | Ch. 9 |
| Deprecation Policy | âPrinciples of Web API Designâ by James Higginbotham | Ch. 11 |
Common Pitfalls & Debugging
Problem 1: âSunset header shows wrong date formatâ
- Why: Youâre using ISO 8601 instead of HTTP-date (RFC 7231)
- Fix: Use format like
Sat, 31 Dec 2025 23:59:59 GMT, not2025-12-31T23:59:59Z - Quick test: Validate against HTTP-date regex or use a library
Problem 2: âClients donât see deprecation warnings in their logsâ
- Why: Most HTTP clients donât log headers by default
- Fix: Provide a SDK/wrapper that explicitly checks for and warns on these headers
- Quick test: curl -i shows headers; does your client library expose them?
Problem 3: âAfter sunset date, endpoint still returns 200 OKâ
- Why: No logic to check if current date > sunset date
- Fix: Middleware should check
if (now > sunsetDate) return 410 Gone - Quick test: Manually set system clock forward and test
Project 7: The HATEOAS Navigator (Hypermedia Evolution)
- File: API_DESIGN_VERSIONING_MASTERY.md
- Main Programming Language: Node.js or Java (Spring HATEOAS)
- Alternative Programming Languages: Ruby (Grape), Python (Flask-RESTful)
- Coolness Level: Level 5: Pure Magic (Super Cool)
- Business Potential: 1. The âResume Goldâ
- Difficulty: Level 3: Advanced
- Knowledge Area: REST Architecture / Discovery
- Software or Tool: HAL Browser, Postman
- Main Book: âREST API Design Rulebookâ by Mark Masse
What youâll build: An API where the client doesnât need to hardcode URLs. Each response contains links (Hypermedia) to available actions. You will evolve the API by changing the URLs while proving that a âsmart clientâ doesnât break.
Why it teaches Versioning: This is the âHoly Grailâ of REST. Youâll understand how Hypermedia allows for out-of-band versioning, where the client follows links instead of constructing URLs. This is the ultimate defense against breaking changes.
Core challenges youâll face:
- Designing the Link Structure â maps to choosing between HAL, JSON-LD, or Siren formats
- Dynamic Action Discovery â maps to showing/hiding links based on state (e.g., âcancelâ link only if order is âpendingâ)
- Client Implementation â maps to writing a generic navigator that âcrawlsâ the API
Key Concepts:
- HATEOAS: Wikipedia - HATEOAS
- HAL (Hypertext Application Language): stateless.group - HAL
- Richardson Maturity Model: Martin Fowler - Richardson Maturity Model
Real World Outcome
You can move your /users endpoint to /v2/accounts and your mobile app continues to work without an update because it just âfollowed the link.â
Example Output:
$ curl http://api.local/orders/123
{
"id": 123,
"status": "shipped",
"_links": {
"self": { "href": "/orders/123" },
"track": { "href": "/shipping/v2/track/123" },
"returns": { "href": "/returns/v1/new?orderId=123" }
}
}
The Core Question Youâre Answering
âWhat if the client doesnât need to know the URL structure at all?â
Before coding, grasp this: HATEOAS (Hypermedia as the Engine of Application State) is the highest level of REST maturity. Instead of hardcoding /users/123/posts, the client follows _links.posts.href. This means you can change URLs without breaking clients.
Concepts You Must Understand First
Stop and research these before coding:
- Richardson Maturity Model
- What are Levels 0-3 of REST maturity?
- Why is HATEOAS considered âLevel 3â?
- Book Reference: Martin Fowlerâs blog post on Richardson Maturity Model
- HAL (Hypertext Application Language)
- How does HAL structure
_linksand_embedded? - Whatâs the difference between HAL and JSON-LD?
- Book Reference: âREST API Design Rulebookâ Ch. 6 - Mark Masse
- How does HAL structure
Questions to Guide Your Design
Before implementing, think through these:
- Link Representation
- Should links be absolute URLs or relative paths?
- Do you include templated URLs (e.g.,
/orders/{id})?
- State-Dependent Links
- Should a âshippedâ order show a âcancelâ link? (No!)
- How do you conditionally include links based on resource state?
Thinking Exercise
The âDumb Client, Smart Serverâ
Traditional client:
// Hardcoded knowledge
if (order.status === 'pending') {
cancelUrl = `/orders/${order.id}/cancel`;
}
HATEOAS client:
// Follows what server provides
if (order._links.cancel) {
cancelUrl = order._links.cancel.href;
}
Questions while analyzing:
- Which approach lets you change the cancel URL path without updating clients?
- Which approach lets you add new actions (like ârefundâ) without client changes?
- Whatâs the tradeoff in response payload size?
The Interview Questions Theyâll Ask
Prepare to answer these:
- âExplain HATEOAS and why itâs rarely implemented in practice.â
- âWhatâs the difference between HAL, JSON-LD, and Siren formats?â
- âHow does HATEOAS enable true API versioning without version numbers?â
- âWhat are the performance implications of including links in every response?â
- âWhy might a mobile app avoid HATEOAS and hardcode URLs instead?â
Hints in Layers
Hint 1: Choose a hypermedia format
HAL is simplest: { "data": {...}, "_links": { "self": {...}, "next": {...} } }
Hint 2: Link generation helpers
Create a function addLinks(resource, baseUrl) that inspects resource state and builds appropriate links.
Hint 3: Conditional link inclusion
if (order.status === 'pending') {
links.cancel = { href: `/orders/${order.id}/cancel`, method: 'DELETE' };
}
Hint 4: Client navigator Write a generic client that starts at a root URL and recursively follows links, never constructing URLs itself.
Books That Will Help
| Topic | Book | Chapter |
|---|---|---|
| HATEOAS | âREST API Design Rulebookâ by Mark Masse | Ch. 6 |
| Hypermedia | âRESTful Web APIsâ by Leonard Richardson | Ch. 6-7 |
Common Pitfalls & Debugging
Problem 1: âClients still hardcode URLs despite providing HAL linksâ
- Why: Habit; developers find it âeasierâ to construct URLs
- Fix: Make URL structure intentionally unpredictable (random IDs, hashed paths) to force link-following
- Quick test: Change a URL path; hardcoded clients break, HATEOAS clients donât
Problem 2: âResponse payload bloated with redundant linksâ
- Why: Including every possible action even when not applicable
- Fix: Conditional link generation based on resource state and user permissions
- Quick test: A âshippedâ order shouldnât have 10 links; maybe only âtrackâ and âreturnâ
Problem 3: âClient gets stuck in infinite loop following linksâ
- Why: Circular references (A links to B, B links back to A)
- Fix: Track visited URLs in client, use
relattributes to identify link purpose - Quick test: Crawl your API from root; does it terminate or loop forever?
Project 8: The Schema Evolution Tester (DB vs API)
- File: API_DESIGN_VERSIONING_MASTERY.md
- Main Programming Language: SQL + Backend (any)
- Alternative Programming Languages: Python (Alembic), Node (Knex)
- Coolness Level: Level 3: Genuinely Clever
- Business Potential: 3. The âService & Supportâ Model
- Difficulty: Level 4: Expert
- Knowledge Area: Database Design / Data Integrity
- Software or Tool: PostgreSQL, Liquibase
- Main Book: âMonolith to Microservicesâ by Sam Newman
What youâll build: A system where you refactor a database column (e.g., splitting name into first_name and last_name) while keeping an API v1 alive that still expects a single name field. You will implement the migration using the Expand/Contract Pattern.
Why it teaches Versioning: Most API breaks happen because of database changes. This project teaches you how to decouple your database schema from your API schema. Youâll learn to use database views or application-level mapping to maintain backward compatibility.
Core challenges youâll face:
- Dual-Writing â maps to writing to both old and new columns during migration
- Backward Compatible Views â maps to using SQL views to fake the old table structure
- Performance Impact â maps to measuring the overhead of the mapping layer
Difficulty: Expert Time estimate: 2 weeks Prerequisites: Understanding of Database Normalization and SQL.
The Core Question Youâre Answering
âHow do I change my database schema without breaking the API contract?â
Before coding, understand this truth: Your database is an implementation detail. Your API is a public contract. They must evolve independently. The Expand/Contract pattern (also called âParallel Changeâ) lets you make breaking database changes without API downtime.
Concepts You Must Understand First
Stop and research these before coding:
- The Expand/Contract Pattern
- Whatâs the three-phase migration? (Expand â Migrate â Contract)
- How long do you maintain dual-write state?
- Book Reference: âRefactoring Databasesâ by Ambler & Sadalage
- Database Views for Compatibility
- How can a SQL view fake the old schema?
- Whatâs the performance impact of view-based abstractions?
- Book Reference: âMonolith to Microservicesâ Ch. 4 - Sam Newman
Questions to Guide Your Design
Before implementing, think through these:
- Migration Strategy
- Do you dual-write (to both old and new columns) during transition?
- How do you backfill old data into the new schema?
- Rollback Plan
- If something breaks, can you roll back the migration without data loss?
- Do you keep old columns until 100% of traffic migrates?
Thinking Exercise
The âName Split Migrationâ
Current DB: users table with name VARCHAR(100)
Target: Split into first_name and last_name
Phase 1 (Expand): Add first_name, last_name columns
Phase 2 (Migrate): Application writes to both name and first_name/last_name
Phase 3 (Contract): Drop name column
Questions while analyzing:
- What if a user has only one name (like âMadonnaâ)?
- During Phase 2, do you trust
nameorfirst_name + last_nameas source of truth? - How do you test that v1 API still works during all 3 phases?
The Interview Questions Theyâll Ask
Prepare to answer these:
- âExplain the Expand/Contract pattern for database migrations.â
- âHow would you split a column without API downtime?â
- âWhatâs the difference between a migration and a refactoring?â
- âHow do you handle data inconsistencies during dual-write phases?â
- âCan you describe a database migration that went wrong and how youâd fix it?â
Hints in Layers
Hint 1: Add new columns first (Expand)
Use an ALTER TABLE to add first_name and last_name as nullable columns initially.
Hint 2: Dual-write application logic (Migrate)
Modify your API to write to both schemas: name for v1 clients, first_name/last_name for new logic.
Hint 3: Use database triggers for backfilling
Create a trigger that auto-populates first_name/last_name when name is updated (or vice versa).
Hint 4: Views for backward compatibility (Contract)
Create a view that synthesizes the old name column: CREATE VIEW users_v1 AS SELECT id, CONCAT(first_name, ' ', last_name) AS name FROM users.
Books That Will Help
| Topic | Book | Chapter |
|---|---|---|
| Database Refactoring | âRefactoring Databasesâ by Ambler & Sadalage | Ch. 3 |
| Schema Evolution | âMonolith to Microservicesâ by Sam Newman | Ch. 4 |
Common Pitfalls & Debugging
Problem 1: âData in old column doesnât match new columnsâ
- Why: Dual-write logic isnât symmetric; one path writes correctly, other doesnât
- Fix: Write comprehensive tests that verify data consistency across both schemas
- Quick test: Insert via v1 API, read via direct DB query of new columns; should match
Problem 2: âPerformance degraded after adding viewâ
- Why: Views add query overhead, especially with complex CONCAT or JOIN operations
- Fix: Add indexes on new columns, or consider materialized views
- Quick test: EXPLAIN your view query and check for full table scans
Problem 3: âMigration script fails halfway; database in inconsistent stateâ
- Why: Migration wasnât wrapped in a transaction or migration tool
- Fix: Use migration frameworks (Alembic, Flyway, Liquibase) with automatic rollback
- Quick test: Intentionally break migration mid-way; can you recover?
Project 9: The API Diffing Engine (CI/CD Safety)
- File: API_DESIGN_VERSIONING_MASTERY.md
- Main Programming Language: Python or Go
- Alternative Programming Languages: Rust, Bash
- Coolness Level: Level 4: Hardcore Tech Flex
- Business Potential: 5. The âIndustry Disruptorâ
- Difficulty: Level 3: Advanced
- Knowledge Area: DevOps / Automation
- Software or Tool:
openapi-diff, GitHub Actions - Main Book: âDesigning Web APIsâ by Jin, Sahni, & Shevat
What youâll build: A tool that compares two OpenAPI specifications and identifies Breaking Changes (e.g., removed field, changed type, new required parameter). You will integrate this into a Git workflow that blocks Pull Requests if they break the contract without a version bump.
Why it teaches API Design: You will learn exactly what constitutes a breaking change by building the logic that detects them. Youâll categorize changes into âSafeâ (Additions) and âBreakingâ (Removals/Changes).
Core challenges youâll face:
- Semantic Diffing â maps to ignoring formatting changes and focusing on structural changes
- Detecting Optional vs Required â maps to understanding that making an optional field required is a break
- CLI Design â maps to providing clear output for developers to fix their specs
Real World Outcome
A GitHub Action that comments on PRs: âđ¨ WARNING: This PR introduces a breaking change to the /v1/users endpoint. Please bump the version or fix the schema.â
Example Output:
$ git diff main feature/api-v2 -- openapi.yaml | ./api-diff
đ¨ BREAKING CHANGES DETECTED:
1. /users endpoint
- REMOVED field: "username" (was required)
- CHANGED type: "age" from integer to string
2. /orders endpoint
- ADDED required field: "shipping_address"
â
SAFE CHANGES:
1. /products endpoint
- ADDED optional field: "tags"
RECOMMENDATION: Bump major version (v2 â v3)
The Core Question Youâre Answering
âHow can I prevent accidental breaking changes from reaching production?â
Before coding, understand this: Humans make mistakes. Code reviews miss subtle breaks. Automated diffing catches what humans miss. This project builds the âsafety netâ for your API evolution.
Concepts You Must Understand First
Stop and research these before coding:
- Semantic Versioning for APIs
- What constitutes a MAJOR vs MINOR vs PATCH change?
- Is adding a required field breaking? (Yes!)
- Book Reference: âAPI Design Patternsâ Ch. 22 - JJ Geewax
- AST (Abstract Syntax Tree) Diffing
- How do you compare two JSON/YAML structures semantically, not textually?
- Whatâs the difference between
git diffand semantic diffing? - Book Reference: âDesigning Web APIsâ Ch. 9 - Jin, Sahni, & Shevat
Questions to Guide Your Design
Before implementing, think through these:
- Breaking Change Detection
- Is renaming a field breaking? (Yes, even if the data type is the same)
- Is changing from
type: stringtotype: [string, null]breaking? (No, itâs more permissive)
- CI Integration
- Should the diff tool block the PR automatically, or just warn?
- How do you allow intentional breaking changes (with manual override)?
Thinking Exercise
The âSubtle Breakâ
v1 spec:
email:
type: string
pattern: "^[a-z@.]+$"
v2 spec:
email:
type: string
pattern: "^[A-Za-z0-9@.]+$"
Questions while analyzing:
- Is this breaking? (Itâs more permissive, so⌠no!)
- But waitâold clients might not handle uppercase. Is it still safe?
- How do you detect pattern/regex changes in your diff tool?
The Interview Questions Theyâll Ask
Prepare to answer these:
- âHow would you automatically detect breaking changes in an OpenAPI spec?â
- âWhatâs the difference between backward-compatible and forward-compatible changes?â
- âExplain how semantic versioning applies to APIs.â
- âHow would you handle a team that wants to make a breaking change without bumping the major version?â
- âCan you describe a change that seems safe but is actually breaking?â
Hints in Layers
Hint 1: Use existing diff libraries
Donât parse YAML manually. Use libraries like openapi-diff, oasdiff, or swagger-diff.
Hint 2: Categorize changes
Create an enum: BREAKING | POTENTIALLY_BREAKING | SAFE. Map each detected change to a category.
Hint 3: Exit codes for CI Return exit code 1 if breaking changes detected, 0 otherwise. CI can fail the build on non-zero exit.
Hint 4: Allow manual override
Support a flag like --allow-breaking or a commit message footer [breaking-change-approved] to bypass the check.
Books That Will Help
| Topic | Book | Chapter |
|---|---|---|
| API Evolution | âDesigning Web APIsâ by Jin, Sahni, & Shevat | Ch. 9 |
| Semantic Versioning | âAPI Design Patternsâ by JJ Geewax | Ch. 22 |
Common Pitfalls & Debugging
Problem 1: âDiff tool says everything is breaking, even spacing changesâ
- Why: Youâre doing textual diff, not semantic diff
- Fix: Parse both specs into objects, compare the AST, ignore formatting
- Quick test: Reformat a spec with different indentation; tool should report no changes
Problem 2: âTool misses breaking changes like removing an endpointâ
- Why: Youâre only checking schema changes, not path changes
- Fix: Also diff the
pathssection, not justcomponents.schemas - Quick test: Delete an endpoint; tool should scream âBREAKINGâ
Problem 3: âCI always fails even on safe PRsâ
- Why: Tool is too strict, flagging optional field additions as breaking
- Fix: Implement proper breaking change logic: removing required field = breaking, adding optional = safe
- Quick test: Add optional field; should pass. Remove required field; should fail.
Project 10: The Idempotency Key Manager (Safety First)
- File: API_DESIGN_VERSIONING_MASTERY.md
- Main Programming Language: Node.js or Python
- Alternative Programming Languages: Go, Java
- Coolness Level: Level 4: Hardcore Tech Flex
- Business Potential: 4. The âOpen Coreâ Infrastructure
- Difficulty: Level 3: Advanced
- Knowledge Area: Distributed Systems / Transactional Integrity
- Software or Tool: Redis, PostgreSQL
- Main Book: âAPI Design Patternsâ by JJ Geewax
What youâll build: A middleware that handles Idempotency-Key headers. If a client retries a POST request with the same key, your API must return the cached response of the first successful request instead of performing the action again.
Why it teaches API Design: One of the biggest challenges in API design is âat-least-onceâ delivery. Youâll learn how to make your API âsafeâ for unreliable networks. This is a core requirement for payments and financial APIs.
Core challenges youâll face:
- Atomic Storage â maps to ensuring you store the key and the result atomically
- Handling Race Conditions â maps to what happens if two identical requests arrive at the exact same millisecond?
- Response Caching â maps to storing headers and status codes, not just the body
Key Concepts:
- Idempotency: Stripe Engineering Blog - Idempotency
- Distributed Locking: Redis Redlock
- Transactional Outbox: Microservices.io - Transactional Outbox
Real World Outcome
You can click âPayâ 10 times on a slow connection, but the user is only charged once.
Example Output:
# First request
$ curl -X POST -H "Idempotency-Key: 123" http://api.local/charge -d '{"amount": 10}'
HTTP/1.1 201 Created
{ "transaction_id": "abc", "status": "success" }
# Immediate retry
$ curl -X POST -H "Idempotency-Key: 123" http://api.local/charge -d '{"amount": 10}'
HTTP/1.1 200 OK (From Cache)
{ "transaction_id": "abc", "status": "success" }
The Core Question Youâre Answering
âHow do I make my API safe for unreliable networks and impatient users?â
Before coding, understand this: The internet is unreliable. Clients retry. Users double-click. Without idempotency, you charge their credit card twice. This project teaches you to make write operations safe to retry.
Concepts You Must Understand First
Stop and research these before coding:
- Idempotency in Distributed Systems
- Whatâs the difference between âat-most-onceâ and âexactly-onceâ delivery?
- Why are GET/DELETE naturally idempotent but POST is not?
- Book Reference: âDesigning Data-Intensive Applicationsâ Ch. 11 - Martin Kleppmann
- Distributed Locking
- How do you prevent two identical requests from executing simultaneously?
- Whatâs the Redlock algorithm?
- Book Reference: âAPI Design Patternsâ Ch. 13 - JJ Geewax
Questions to Guide Your Design
Before implementing, think through these:
- Key Storage
- How long do you cache idempotency keys? (24 hours? Forever?)
- Do you store just the response body or also headers and status code?
- Race Conditions
- What happens if two requests with the same key arrive in the same millisecond?
- Do you use pessimistic locking (Redis lock) or optimistic (unique constraint)?
Thinking Exercise
The âDouble-Submit Problemâ
User clicks âPay $100â button twice within 10ms.
Without idempotency:
- Request 1: Charge $100 â Success
- Request 2: Charge $100 â Success (OOPS! $200 charged)
With idempotency keys:
- Request 1: Store key âabc123â â Charge $100 â Cache response
- Request 2: Key âabc123â exists â Return cached response (no second charge!)
Questions while analyzing:
- What if Request 1 is still processing when Request 2 arrives?
- Should Request 2 wait (blocking) or return 409 Conflict?
- How do you handle the case where Request 1 fails but Request 2 succeeds?
The Interview Questions Theyâll Ask
Prepare to answer these:
- âExplain idempotency and why itâs critical for payment APIs.â
- âHow would you implement idempotency keys in a distributed system?â
- âWhatâs the difference between idempotency and deduplication?â
- âHow long should you cache idempotency keys?â
- âWhat happens if a client retries with the same key but different payload?â
Hints in Layers
Hint 1: Use Redis for key storage
Store idempotency_key -> { status_code, headers, body, timestamp } with a TTL (e.g., 24 hours).
Hint 2: Atomic check-and-set
Use Redis SETNX (set if not exists) to atomically claim the key before processing.
Hint 3: Handle in-flight requests
If the key exists but the response isnât cached yet, return 409 Conflict or wait with a timeout.
Hint 4: Validate payload consistency
If the same key is used with a different request body, return 422 Unprocessable Entity.
Books That Will Help
| Topic | Book | Chapter |
|---|---|---|
| Idempotency | âAPI Design Patternsâ by JJ Geewax | Ch. 13 |
| Distributed Systems | âDesigning Data-Intensive Applicationsâ by Martin Kleppmann | Ch. 11 |
Common Pitfalls & Debugging
Problem 1: âSame request processed twice despite idempotency keyâ
- Why: Race condition; both requests see key doesnât exist simultaneously
- Fix: Use atomic operations like Redis
SETNXor database unique constraints - Quick test: Send 100 concurrent requests with same key; should process exactly once
Problem 2: âCached response has stale dataâ
- Why: TTL is too long; underlying data changed after caching
- Fix: Shorter TTL (24 hours max) or invalidate cache when resource changes
- Quick test: Make request, modify resource directly, retry with same key; should see old data (this is correct!)
Problem 3: âClient sends same key but different payload; should that be allowed?â
- Why: Design decisionâStripe treats this as an error
- Fix: Hash the request payload and store it with the key; if payload doesnât match, return 422
- Quick test: Send key âabcâ with
{"amount": 100}, retry with{"amount": 200}; should reject second
Project 11: The Stripe-Style Date Versioner
- File: API_DESIGN_VERSIONING_MASTERY.md
- Main Programming Language: TypeScript
- Alternative Programming Languages: Ruby, Python
- Coolness Level: Level 5: Pure Magic (Super Cool)
- Business Potential: 5. The âIndustry Disruptorâ
- Difficulty: Level 5: Master
- Knowledge Area: Software Architecture / Metaprogramming
- Software or Tool: Any highly dynamic language
- Main Book: âStripeâs API Versioningâ (Blog/Articles)
What youâll build: A versioning system based on dates (e.g., 2024-12-28). Instead of v1/v2, the backend always runs the âCurrentâ code, and a series of âVersion Transformersâ (Middleware) intercept the request/response to transform data back to the format requested by the clientâs version date.
Why it teaches Versioning: This is the most sophisticated versioning model in existence. Youâll learn how to avoid âbranchingâ entirely by using a transformation pipeline. This project represents the pinnacle of backward compatibility.
Core challenges youâll face:
- The Transformation Pipeline â maps to chaining multiple âundoâ operations to reach an old version
- Schema Snapshots â maps to knowing what the API looked like on a specific date
- Maintainability â maps to how to add a transformation without breaking the pipeline
Real World Outcome
You can release daily updates to your API, and a client using a 2-year-old version date still sees exactly what they expect.
Example Output:
# Client pinned to an old version
$ curl -H "API-Version: 2022-01-01" http://api.local/users/1
{ "name": "Alice" }
# Current version
$ curl -H "API-Version: 2024-12-28" http://api.local/users/1
{ "firstName": "Alice", "lastName": "Smith" }
The Core Question Youâre Answering
âCan I evolve my API daily without ever breaking old clients?â
Before coding, understand this is the pinnacle: Stripeâs date-based versioning. Instead of v1/v2/v3, you have 2024-01-15, 2024-03-20, etc. The backend always runs the latest code, and transformers âundoâ changes to make responses match old dates. This eliminates branching entirely.
Concepts You Must Understand First
Stop and research these before coding:
- The Transformation Pipeline Pattern
- How do you chain reversible transformations?
- Whatâs the difference between âundoâ and âredoâ transformations?
- Book Reference: Stripe Engineering Blog - API Versioning
- Migration Scripts as Code
- How do you represent schema changes as executable functions?
- Can you âreverseâ a transformation mathematically?
- Book Reference: âRefactoring Databasesâ - Ambler & Sadalage
Questions to Guide Your Design
Before implementing, think through these:
- Transformation Order
- If you have 100 versions, do transformations run sequentially (slow) or can they be optimized?
- How do you handle non-reversible changes (e.g., data deleted)?
- Version Metadata
- How do you know what your API looked like on 2022-05-15?
- Do you snapshot the spec on each version date?
Thinking Exercise
The âTime Travel Transformationâ
Current version (2024-12-28): { "user": { "firstName": "Alice", "lastName": "Smith" } }
Client requests API-Version: 2022-01-01.
Transformations to apply (in reverse chronological order):
- 2024-01-15: Split
nameintofirstName/lastNameâ REVERSE: Combine intoname - 2023-06-20: Nested user data under
userkey â REVERSE: Flatten to root - 2022-06-01: Renamed
usernametonameâ REVERSE: Rename back tousername
Result: { "username": "Alice Smith" }
Questions while analyzing:
- How do you store these transformations (as code? as config?)?
- What if a transformation canât be reversed (data deleted)?
- How do you test that 100 transformations donât break?
The Interview Questions Theyâll Ask
Prepare to answer these:
- âExplain how Stripeâs date-based API versioning works.â
- âWhat are the advantages and disadvantages compared to URI versioning?â
- âHow would you implement a reversible transformation pipeline?â
- âWhat happens if a client requests a version from before your API existed?â
- âHow do you handle non-reversible changes in this model?â
Hints in Layers
Hint 1: Store transformations as functions
Create a registry: { "2024-01-15": { forward: splitName, backward: combineName } }
Hint 2: Apply transformations conditionally If client version < transformation date, apply the backward transformation.
Hint 3: Use middleware for version resolution
Extract the API-Version header, determine which transformations to apply, attach to request context.
Hint 4: Cache transformed responses If many clients use the same old version, cache the transformed response to avoid recomputing.
Books That Will Help
| Topic | Book | Chapter |
|---|---|---|
| Stripeâs Approach | Stripe Engineering Blog | API Versioning article |
| Reversible Migrations | âRefactoring Databasesâ by Ambler & Sadalage | Ch. 5 |
Common Pitfalls & Debugging
Problem 1: âTransformation pipeline is too slow for productionâ
- Why: Running 50+ transformations sequentially on every request
- Fix: Combine/optimize transformations, or pre-generate responses for popular old versions
- Quick test: Benchmark request with version=2020-01-01 vs current; should be <10ms difference
Problem 2: âA transformation broke; canât serve old versionsâ
- Why: One backward transformation has a bug
- Fix: Comprehensive tests for each transformation with real data
- Quick test: Request every historical version; all should return 200
Problem 3: âClient requests version from before API existedâ
- Why: No validation on version header
- Fix: Return 400 Bad Request with
Earliest-Supported-Version: 2022-01-01header - Quick test: Request version=1999-01-01; should get clear error
Project 12: The Multi-Tenant API Gateway Router
- File: API_DESIGN_VERSIONING_MASTERY.md
- Main Programming Language: Go or Node.js
- Alternative Programming Languages: Rust (Axum/Tower), Nginx (Lua)
- Coolness Level: Level 4: Hardcore Tech Flex
- Business Potential: 4. The âOpen Coreâ Infrastructure
- Difficulty: Level 4: Expert
- Knowledge Area: Proxy Servers / Infrastructure
- Software or Tool: Envoy, Nginx, or a custom Proxy
- Main Book: âAPI Gatewaysâ by Madhusudhan Konda
What youâll build: A gateway that sits in front of your microservices. It looks up a clientâs API Key in a database, finds their âPinned Version,â and automatically routes their request to the correct backend service version.
Why it teaches Infrastructure: This project shows how versioning is managed at the network layer. Youâll learn how to separate the âWhoâ (the client) from the âWhatâ (the version) and âWhereâ (the service).
Core challenges youâll face:
- High-Performance Lookups â maps to using a cache (Redis) for API keys so you donât hit the DB for every request
- Dynamic Routing â maps to changing the target URL on the fly based on client metadata
- Observability â maps to logging which versions are being used by which clients
Real World Outcome
You can âforce upgradeâ a specific client to v2 by simply changing a value in your admin database, without them changing a single line of code.
Example Output:
# Client A (Pinned to v1)
$ curl -H "Authorization: Bearer KEY_A" http://gateway.local/data
-> Proxying to http://internal-service:8001/v1/data
# Client B (Pinned to v2)
$ curl -H "Authorization: Bearer KEY_B" http://gateway.local/data
-> Proxying to http://internal-service:8002/v2/data
# You control versions centrally, without client changes!
The Core Question Youâre Answering
âHow can I manage versions centrally without forcing every client to change headers?â
Before coding, understand this pattern: Instead of clients specifying versions, the gateway knows which version each client should use (based on API key, tenant ID, or account metadata). This gives you centralized control over migrations.
Concepts You Must Understand First
Stop and research these before coding:
- API Gateway Patterns
- Whatâs the difference between a reverse proxy and an API gateway?
- How do you implement dynamic routing based on metadata?
- Book Reference: âBuilding Microservicesâ Ch. 11 - Sam Newman
- Multi-Tenancy
- Whatâs tenant isolation and why does it matter?
- How do you store per-tenant configuration (versions, rate limits)?
- Book Reference: âAPI Gatewaysâ - Madhusudhan Konda
Questions to Guide Your Design
Before implementing, think through these:
- Client Identification
- Do you use API keys, JWTs, or OAuth tokens?
- Where do you store the version mapping (database, Redis, config file)?
- Performance
- How do you avoid a database lookup on every request?
- Should you cache client-to-version mappings in memory?
Thinking Exercise
The âVersion Override Matrixâ
You have:
- 1000 clients
- 3 API versions (v1, v2, v3)
- 95% should use v2 (stable)
- 5 beta clients on v3 (testing)
- 1 legacy client stuck on v1
Questions while analyzing:
- Is it better to store âdefault: v2, exceptions: [âŚ]â or individual mappings per client?
- How do you migrate a client from v1 â v2 without them changing code?
- What if you want to A/B test v3 with 10% of traffic?
The Interview Questions Theyâll Ask
Prepare to answer these:
- âWhatâs the difference between an API gateway and a load balancer?â
- âHow would you implement per-client versioning in a gateway?â
- âWhat are the performance implications of looking up client metadata on every request?â
- âHow would you handle a gradual rollout (e.g., 10% on v2, 90% on v1)?â
- âCan you describe the tradeoffs between client-driven and server-driven versioning?â
Hints in Layers
Hint 1: Extract client identifier
Parse the Authorization: Bearer <token> header and extract client ID (from JWT or API key lookup).
Hint 2: Version lookup with caching
Query a database/Redis for client_id -> version, but cache aggressively (in-memory LRU cache).
Hint 3: Dynamic proxy target
Based on version, modify the upstream URL: http://service-v1:8001 vs http://service-v2:8002.
Hint 4: Observability
Log every request with { client_id, version, upstream, latency } for analytics on version usage.
Books That Will Help
| Topic | Book | Chapter |
|---|---|---|
| API Gateways | âBuilding Microservicesâ by Sam Newman | Ch. 11 |
| Gateway Patterns | âAPI Gatewaysâ by Madhusudhan Konda | Ch. 3-4 |
Common Pitfalls & Debugging
Problem 1: âGateway is the bottleneck; everything is slowâ
- Why: Doing expensive lookups (database query) on every request
- Fix: Aggressive caching (in-memory with TTL) of client-to-version mappings
- Quick test: Benchmark gateway latency; should add <5ms overhead
Problem 2: âChanged client version in database but still routing to old versionâ
- Why: Cached mapping is stale
- Fix: Implement cache invalidation (pub/sub on version changes) or shorter TTL
- Quick test: Update version, wait for TTL expiry, check routing
Problem 3: âSome clients authenticated but version routing failsâ
- Why: New clients donât have version mapping yet
- Fix: Default fallback version (e.g., always use latest if no mapping exists)
- Quick test: Create new API key, make request without version mapping; should work
Project Comparison Table
| Project | Difficulty | Time | Depth of Understanding | Fun Factor |
|---|---|---|---|---|
| 1. Spec Architect | Level 1 | Weekend | High (Design) | âââ |
| 4. Strategy Patterns | Level 3 | 1-2 Weeks | High (Code structure) | ââââ |
| 8. Schema Evolution | Level 4 | 2 Weeks | Very High (Systems) | âââ |
| 9. CI/CD Diffing | Level 3 | 1 Week | High (DevOps) | ââââ |
| 11. Stripe-Style | Level 5 | 1 Month | Master (Evolution) | âââââ |
Recommendation
For beginners: Start with Project 1 and Project 2. You must learn to âspeakâ API via OpenAPI before you can build them. Understanding the contract is 50% of the battle.
For senior engineers: Jump straight to Project 4 and Project 11. These projects address the âspaghetti codeâ that inevitably happens when an API lives for more than a year.
Final Overall Project: The Immortal API
- File: API_DESIGN_VERSIONING_MASTERY.md
- Main Programming Language: Node.js/TypeScript or Go
- Business Potential: 5. The âIndustry Disruptorâ
- Difficulty: Level 5: Master
- Knowledge Area: Full Lifecycle API Management
What youâll build: A complete, multi-versioned âBanking as a Serviceâ API that implements every concept learned:
- Contract-First: A 1000+ line OpenAPI spec.
- Versioned Gateway: Routing by API-Key/Pinned Version.
- Strategy Pattern: Clean, transformer-based versioning logic.
- Idempotency: Safe payment processing.
- Sunset Logic: Automated deprecation headers.
- CI/CD: Automatic breaking-change detection.
Why it teaches API Design: This project forces you to integrate all the isolated skills into a single, production-ready system. Youâll see how design decisions in the spec (Project 1) impact your implementation (Project 4) and your deployment (Project 9).
Real World Outcome
A system that you can evolve for years without ever sending a âSorry, we broke your appâ email to your users.
Example Output:
$ git push origin main
[CI/CD] Comparing specs... No breaking changes detected for v2.
[CI/CD] Deploying v2.3...
[Gateway] v1.0 clients still routing to Transformer V1.
[Gateway] v2.0 clients now seeing new 'rewards' field.
The Core Question Youâre Answering
âCan I build a system that never has to stop, never has to break, and can grow forever?â
Thinking Exercise
The âUniversal Transformationâ
Imagine a request comes in for a version from 3 years ago (2021-01-01). Your database now has 5 fields that didnât exist then, and 2 fields that were renamed twice.
Questions while analyzing:
- Does your Strategy pattern handle the request (incoming data) or the response (outgoing data)? Or both?
- If you have 50 versions, do you write 50 separate transformers, or a pipeline of 50 small changes?
- How do you handle a change where the data type changed from a String to an Object?
Hints in Layers
Hint 1: The Layered Architecture Build the gateway first. It should identify the user and their version. Then build the core service which only speaks the âCurrentâ version.
Hint 2: The Middleware Pipeline
For versioning, use a pipeline. If the client is on v1 and the current is v3, the request goes through v1_to_v2 then v2_to_v3. The response goes back through v3_to_v2 then v2_to_v1.
Hint 3: Use a Document Store or Flexible Schemas For the backend, using a flexible schema (like JSONB in Postgres) can make it easier to store new metadata without breaking old transformers that donât know it exists.
Hint 4: Automated Testing is Mandatory You cannot build this without a test suite that runs the SAME tests against every supported version. Use a tool like Dredd or custom Jest tests to ensure v1, v2, and v3 all pass their respective contracts.
The Interview Questions Theyâll Ask
- âWalk me through your process for implementing a breaking change in a high-traffic API.â
- âHow do you ensure data consistency between different API versions sharing one database?â
- âWhat are the trade-offs between âContract-Firstâ and âCode-Firstâ development?â
- âHow do you detect and prevent accidental breaking changes in a large team?â
- âIf a client is stuck on v1 but you need to delete that database table, what do you do?â
Summary
This learning path covers API Design & Versioning through 12 hands-on projects. Hereâs the complete list:
| # | Project Name | Main Language | Difficulty | Time Estimate |
|---|---|---|---|---|
| 1 | The API Spec Architect | YAML (OAS) | Beginner | Weekend |
| 2 | The Contract Validator | Node.js | Intermediate | 1 week |
| 3 | Multi-Version Router (URI) | TypeScript | Intermediate | 1-2 weeks |
| 4 | Strategy Pattern Implementer | TypeScript | Advanced | 1-2 weeks |
| 5 | Legacy Adapter Layer | Go | Advanced | 2 weeks |
| 6 | Deprecation Manager | Any | Intermediate | Weekend |
| 7 | HATEOAS Navigator | Node.js | Advanced | 2 weeks |
| 8 | Schema Evolution Tester | SQL/Any | Expert | 2 weeks |
| 9 | API Diffing Engine | Python/Go | Advanced | 1 week |
| 10 | Idempotency Key Manager | Node.js | Advanced | 1-2 weeks |
| 11 | Stripe-Style Date Versioner | TypeScript | Master | 1 month |
| 12 | Multi-Tenant Gateway | Go | Expert | 2 weeks |
Expected Outcomes
After completing these projects, you will:
- Write industrial-grade OpenAPI 3.1 specifications.
- Understand the technical and social cost of breaking changes.
- Implement advanced architectural patterns (Strategy, Adapter) for versioning.
- Manage the entire API lifecycle from design to deprecation.
- Build CI/CD pipelines that protect your API contract.
Youâll have built 12 working projects that demonstrate deep understanding of API Design and Versioning from first principles.
Core Concept Analysis
The API Lifecycle: From Spec to Sunset
[ Design ] --> [ Specification ] --> [ Implementation ] --> [ Evolution ]
^ (OpenAPI) |
|
[ Feedback ] <----------------------- [ Monitoring ] <--------+
1. Resource-Oriented Design
Instead of thinking in âfunctionsâ (getUser()), think in âresourcesâ (GET /users/{id}).
- Nouns, not Verbs:
/orders, not/getOrders. - Hierarchies:
/users/123/posts/456. - Idempotency: Ensuring
PUTandDELETEcan be called multiple times safely.
2. The OpenAPI Specification (OAS)
The source of truth. It defines:
- Paths: The endpoints.
- Components: Reusable schemas (models).
- Security: How clients authenticate.
- Examples: What the data actually looks like.
3. Versioning Strategies (The âWhyâ)
URI Versioning Header Versioning Media Type Versioning
(Standard) (Clean URLs) (Granular)
/v1/users Accept-Version: 1 Accept: application/vnd.app.v1+json
4. The Strategy Pattern for Versioning
When a request comes in, a âVersion Selectorâ (The Strategy) identifies the version and routes it to the correct âHandlerâ or âTransformerâ. This keeps your controller logic clean.
[ Request ]
|
[ Version Resolver ]
/ | \
[v1 Logic] [v2 Logic] [v3 Logic]
\ | /
[ Database ]
5. Backward Compatibility & âTombstoningâ
How to remove fields without breaking old clients.
- Expansion: Adding fields (Safe).
- Contraction: Removing/Renaming fields (Breaking).
- Transformation: Using Adapters to map new DB fields back to old API fields.