Spec-First API Development: Make Your OpenAPI File the Source of Truth
Source: Dev.to
Spec-First API Development: Make Your OpenAPI File the Source of Truth
Most teams write the API first and the spec later — if ever. The code ships, the OpenAPI document drifts, and six months on the docs describe an API that no longer exists. Spec-first flips the order: you design the contract in OpenAPI before writing a single route, then generate mocks, validation, docs, and clients from that one file. The spec stops being documentation and becomes the source of truth. Here’s how to actually do it. Start with a minimal but precise OpenAPI 3.1 document. Describe the shape of requests and responses, not the implementation.
openapi.yaml
openapi: 3.1.0 info: title: Orders API version: 1.0.0 paths: /orders/{id}: get: operationId: getOrder parameters: - name: id in: path required: true schema: { type: string } responses: “200”: description: The order content: application/json: schema: { $ref: ”#/components/schemas/Order” } “404”: description: Not found components: schemas: Order: type: object required: [id, status, total] properties: id: { type: string } status: { type: string, enum: [pending, paid, shipped] } total: { type: number }
Because this is written before the code, frontend and backend can agree on the contract on day one instead of arguing about field names in code review. Your frontend team shouldn’t wait for the backend. Point a mock server at the spec and they can build against realistic responses immediately. Prism does this with zero code: npx @stoplight/prism-cli mock openapi.yaml
GET http://127.0.0.1:4010/orders/123
→ { “id”: “string”, “status”: “pending”, “total”: 0 }
Prism validates incoming requests against the spec and returns spec-compliant responses. If the frontend sends a malformed request, it fails against the mock — long before it ever hits real infrastructure. The biggest risk with spec-first is drift: the code quietly diverges from the document. Stop that by validating live traffic against the spec. With Express: const express = require(“express”); const OpenApiValidator = require(“express-openapi-validator”);
const app = express(); app.use(express.json());
app.use( OpenApiValidator.middleware({ apiSpec: ”./openapi.yaml”, validateRequests: true, validateResponses: true, // catches drift in YOUR responses too }) );
app.get(“/orders/:id”, (req, res) => { res.json({ id: req.params.id, status: “paid”, total: 42.0 }); });
// Spec violations become clean 400/500s instead of silent bugs app.use((err, req, res, next) => { res.status(err.status || 500).json({ message: err.message }); });
app.listen(3000);
validateResponses: true is the part most people skip — and it’s the part that matters. It fails loudly in tests when your handler returns a field the spec doesn’t allow, so the code can never silently outrun the contract. Once the spec is authoritative, typed clients are a build step, not a chore: npx openapi-typescript openapi.yaml -o src/api-types.ts
Now your frontend gets compile-time errors the moment a response shape changes in the spec. No more guessing whether total is a string or a number. Spec-first works because every artifact flows from one file: mocks for parallel work, runtime validation to prevent drift, generated types for safety, and always-accurate docs. Change the spec, regenerate everything, and your whole stack stays in sync. The friction is usually tooling sprawl — a mock server here, a validator there, a docs generator somewhere else, all pointed at copies of a spec that slowly diverge. APIKumo keeps the spec, live versioned docs, mock environments, and generated client code in one workspace, so the contract you design is the contract your team builds against. Define it once, and let everything else follow.