res the spec remains authoritative, enforceable, and continuously verified.
Core Solution
SDD implementation follows a three-layer architecture: Contract Definition β Runtime Enforcement β Continuous Verification.
Step 1: Writing the Specification (The Contract)
Define the API contract in openapi.yaml. This file dictates endpoints, payload structures, validation rules, and response schemas. It serves as the single source of truth for all consuming teams.
# openapi.yaml
openapi: 3.0.0
info:
title: "Task Management API"
version: 1.0.0
description: "A simple API to manage tasks, built with Spec-Driven Development."
paths:
/tasks:
get:
summary: Get all tasks
responses:
'200':
description: "A list of tasks"
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/Task'
post:
summary: Create a new task
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/NewTask'
responses:
'201':
description: "Task created successfully"
components:
schemas:
Task:
type: object
required: [id, title, completed]
properties:
id:
type: string
format: uuid
title: ""
type: string
completed:
type: boolean
NewTask:
type: object
required: [title]
properties:
title: ""
type: string
minLength: 3
Step 2: Implementation (The Code)
Backend logic is implemented with runtime contract enforcement. The express-openapi-validator middleware automatically reads the spec and intercepts requests/responses, rejecting payloads that violate defined constraints without requiring manual validation code.
// index.js
const express = require('express');
const path = require('path');
const OpenApiValidator = require('express-openapi-validator');
const app = express();
app.use(express.json());
// 1. Install the OpenAPI Validator middleware
// This automatically reads our openapi.yaml and validates all incoming requests
app.use(
OpenApiValidator.middleware({
apiSpec: path.join(__dirname, 'openapi.yaml'),
validateRequests: true, // Fail if request doesn't match spec
validateResponses: true // Fail if our server sends a bad response
})
);
// 2. Dummy Database
let tasks = [
{ id: "123e4567-e89b-12d3-a456-426614174000", title: "Learn SDD", completed: false }
];
// 3. Define Routes
app.get('/tasks', (req, res) => {
res.status(200).json(tasks);
});
app.post('/tasks', (req, res) => {
// We don't need to write validation logic like `if(!req.body.title)`!
// The OpenAPI validator already checked it against the YAML spec.
const newTask = {
id: "987e6543-e21b-34d3-b123-426614174111", // In reality, generate a UUID
title: req.body.title,
completed: false
};
tasks.push(newTask);
res.status(201).json(newTask);
});
// 4. Error Handler for Validation Errors
app.use((err, req, res, next) => {
res.status(err.status || 500).json({
message: err.message,
errors: err.errors,
});
});
app.listen(3000, () => {
console.log('Server running on port 3000. Protected by OpenAPI!');
});
Step 3: GitHub Version Control & Automation (CI/CD)
Repository structure isolates the spec, application code, and automation pipelines. GitHub Actions enforces spec quality gates before merging.
my-sdd-project/
βββ .github/
β βββ workflows/
β βββ lint-spec.yml <-- CI/CD Automation
βββ package.json
βββ index.js <-- Application Code
βββ openapi.yaml <-- The Specification
name: Lint OpenAPI Spec
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
jobs:
validate-spec:
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
- name: Install Spectral
run: npm install -g @stoplight/spectral-cli
- name: Lint OpenAPI Document
run: spectral lint openapi.yaml --ruleset spectral:oas
Architecture Decision: Using spectral:oas ruleset ensures compliance with OpenAPI best practices (naming conventions, required descriptions, consistent formatting). Failed linting blocks PR merges, guaranteeing spec integrity across the team.
Pitfall Guide
- Neglecting Response Validation: Only enabling
validateRequests leaves clients vulnerable to malformed or schema-violating responses. Always set validateResponses: true to catch backend serialization bugs before they reach consumers.
- Treating the Spec as Static Documentation: OpenAPI files drift when updated manually without process enforcement. Treat the spec as executable infrastructure; tie every code change to a spec update and gate merges with CI/CD.
- Hardcoding IDs and Mock Data in Production Logic: The example uses static UUIDs for demonstration. In production, integrate proper generation libraries (
uuid, nanoid) and separate mock servers (swagger-ui, prism) for frontend development to avoid coupling test data with runtime logic.
- Over-Complicating Early-Stage Schemas: Deep nesting, excessive
allOf/oneOf usage, and custom format types break tooling compatibility and slow down mock server generation. Stick to flat JSON Schema structures and standard formats until domain complexity demands otherwise.
- Skipping CI/CD Linting Gates: Manual spec reviews fail at scale. Without automated linting (
spectral, oas-validator), teams will merge broken contracts, reintroducing the exact integration failures SDD aims to eliminate.
- Ignoring Error Schema Standardization: Validation errors must conform to a predictable structure (e.g., RFC 7807 Problem Details). Custom error responses bypass client-side error handling and break contract consistency.
- Versioning Without Deprecation Strategy: Bumping
info.version without maintaining backward-compatible routes or sunset headers causes breaking changes for downstream consumers. Implement route versioning (/v1/tasks) and explicit deprecation headers alongside spec updates.
Deliverables
- π SDD Architecture Blueprint: Visual workflow mapping the contract lifecycle from spec authoring β mock server generation β runtime enforcement β CI/CD validation β production deployment. Includes dependency graph for
express-openapi-validator and Spectral integration points.
- β
Pre-Merge & Runtime Validation Checklist: 12-point verification matrix covering schema completeness, required field enforcement, response shape alignment, error payload standardization, CI linting gates, and mock server sync status.
- βοΈ Configuration Templates: Production-ready starter files including
openapi.yaml baseline, Express middleware bootstrap with validation hooks, GitHub Actions workflow with Spectral/oas-validator integration, and Dockerized mock server configuration for frontend parallel development.