Skip to content

About

Contract-first API design using JSON Schema — modeling, validation, and real-world examples for scalable systems.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

api-first-approach

Contract-first API design using JSON Schema — modeling, validation, and real-world examples for scalable systems.

🧠 API-First Workflow

flowchart LR
    A[Examples] --> B[Schema Generator]
    B --> C[JSON Schema]
    C --> D[Validator]
    D --> E[Implementation]
Loading

🧠 How It Works

This project follows an API-first workflow:

Examples → Schema → Validation → Implementation

  • Examples define real payloads
  • Schemas define the contract
  • Validator enforces correctness

This ensures consistency across services and prevents breaking changes.


⚙️ Tools

1. Schema Generator

  • Generates JSON Schema from example payloads (bootstrap only).
  • Schemas define valid data only
  • Invalid examples are used for testing, not schema generation
python src/contract_toolkit/generator.py <input.json> <output.schema.json>

Example:

python src/contract_toolkit/generator.py examples/players/post/request_valid.json schemas/players/post_request.schema.json
  • Use valid examples only
  • Generated schema should be reviewed and refined manually
  • Do NOT generate schema from invalid examples

2. Validator

Validates JSON data against schema.

python src/contract_toolkit/validator.py <data.json> <schema.json>

Examples

Validate correct response
python src/contract_toolkit/validator.py examples/players/get/response_200_ok.json schemas/players/get_response.schema.json

Output:

{
  "is_valid": true,
  "errors": []
}

Validate correct request
python src/contract_toolkit/validator.py examples/players/post/request_valid.json schemas/players/post_request.schema.json

Output:

{
  "is_valid": true,
  "errors": []
}
Validate invalid request
python src/contract_toolkit/validator.py examples/players/post/request_invalid_missing_field.json schemas/players/post_request.schema.json

Output:

{
  "is_valid": false,
  "error_type": "ValidationError",
  "errors": [
    {
      "path": [],
      "message": "'lastName' is a required property",
      "validator": "required",
      "validator_value": [
        "firstName",
        "heightCm",
        "jerseyNumber",
        "lastName",
        "position",
        "team",
        "weightKg"
      ]
    }
  ]
}

🧠 Key Design Principles

  1. Schema is a Contract (not generated blindly) Shared schemas (e.g. error.schema.json) must be manually designed Domain schemas can be bootstrapped, then refined

  2. One Schema → Many Examples

post_request.schema.json

Used by:

request_valid.json
request_invalid_missing_field.json
request_invalid_type.json
  1. Error Model is Shared
{
  "error": {
    "code": "STRING_CODE",
    "message": "Human readable message",
    "details": [...]
  }
}
  • 404 -> no details
  • 422 -> with details
  1. Validation is Part of Development
  • validate requests before processing
  • validate responses before returning
  • validate in tests (unit + integration)

🚀 How to Use in Real Systems

Backend (FastAPI / Lambda)

  • validate incoming request against schema
  • validate response before sending

CI/CD

  • run validator on example payloads
  • catch breaking contract changes early

API Documentation

  • compose OpenAPI on top of JSON Schema
  • attach examples directly

🔥 Why This Matters (Real Impact)

Without contracts:

  • production bugs
  • integration failures
  • slow teams

With contracts:

  • faster development
  • safer refactoring
  • clear ownership between teams

🔌 JSON Schema vs OpenAPI

This project uses JSON Schema as the source of truth for data contracts.

When to use JSON Schema

Use JSON Schema when:

  • modeling data structures
  • validating payloads
  • working with event-driven systems (SQS, Kafka)
  • building internal services or libraries

👉 JSON Schema defines the data contract


When to use OpenAPI

Use OpenAPI only if your API is REST (HTTP-based).

OpenAPI defines:

  • endpoints (/players)
  • HTTP methods (GET, POST)
  • status codes (200, 404, 422)
  • request/response mappings

👉 OpenAPI defines how the API is exposed over HTTP


🧠 How to Compose OpenAPI

Do NOT generate OpenAPI from code.

Instead, compose it manually as a thin layer on top of JSON Schema.

Example (OpenAPI 3.1)

openapi: 3.1.0

info:
  title: Players API
  version: 1.0.0
  description: Contract-first Players API using JSON Schema

servers:
  - url: http://localhost:8000

paths:
  /players:
    post:
      summary: Create player
      operationId: createPlayer

      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: ../schemas/players/post_request.schema.json
            examples:
              valid:
                summary: Valid request
                value:
                  firstName: John
                  lastName: Doe
                  position: forward
                  team: Lakers
                  jerseyNumber: 23
                  heightCm: 198
                  weightKg: 95

      responses:
        '201':
          description: Player created
          content:
            application/json:
              schema:
                $ref: ../schemas/players/post_response.schema.json

        '422':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: ../schemas/common/error.schema.json
              examples:
                validation_error:
                  value:
                    error:
                      code: VALIDATION_ERROR
                      message: Invalid request payload
                      details:
                        - field: lastName
                          message: Field is required

  /players/{playerId}:
    get:
      summary: Get player by ID
      operationId: getPlayer

      parameters:
        - name: playerId
          in: path
          required: true
          schema:
            type: string
          description: Unique player identifier

      responses:
        '200':
          description: Player found
          content:
            application/json:
              schema:
                $ref: ../schemas/players/get_response.schema.json

        '404':
          description: Player not found
          content:
            application/json:
              schema:
                $ref: ../schemas/common/error.schema.json
              examples:
                not_found:
                  value:
                    error:
                      code: PLAYER_NOT_FOUND
                      message: Player with given ID not found

Important Notes

  • Use OpenAPI 3.1 (compatible with JSON Schema 2020-12)
  • Do NOT duplicate schemas inside OpenAPI
  • Always use $ref to reference existing JSON Schema files
  • Keep OpenAPI as a thin mapping layer, not the source of truth

🧠 Key Insight

  • JSON Schema -> defines data contracts
  • OpenAPI -> defines transport (HTTP) contracts

👉 If you are NOT building a REST API, you do not need OpenAPI


🧠 Final Thought

Code changes fast. Contracts must be stable.

This repository enforces: 👉 design first, implement second

About

Contract-first API design using JSON Schema — modeling, validation, and real-world examples for scalable systems.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages