Ratgeber · JSON Schema 2020-12
JSON-Validation in CI/CD: Schema-Checks vor jedem Deploy
Pre-Commit: husky + lint-staged + ajv validate. Build-Step: ajv-cli compile. Vitest: Schema-Tests pro Endpoint, fail-fast bei Drift.
Von Mateusz Viola
Betreiber & redaktionelle Verantwortung json-validator.de
Veröffentlicht
Aktualisiert:
Drei Validation-Stufen im Build-Prozess
Schema-Validation in CI/CD hat drei sinnvolle Stufen:
| Stufe | Was validiert wird | Tool |
|---|---|---|
| 1. Schema-Selbst-Validation | Sind die *.schema.json Files gültiges JSON Schema? | ajv-cli compile |
| 2. Fixture-Validation | Passen Test-Fixtures zu den Schemas? | Vitest + Ajv |
| 3. Contract-Tests | Liefern Endpoints Schema-konforme Responses? | Supertest + Ajv |
Stufe 1: Schemas selbst sind valides JSON Schema
Klassischer Lookup-Fehler: "required": "name" statt "required": ["name"]. Wird beim Build sofort gefangen mit ajv-cli:
# package.json
"scripts": {
"validate:schemas": "ajv compile --strict=true --spec=draft2020 -s 'src/schemas/**/*.schema.json'"
}
Mit --strict=true werden auch typische Anti-Patterns gemeldet: unbekannte Keywords (Tippfehler), kombinierte type+enum (redundant), etc.
Stufe 2: Test-Fixtures gegen Schemas
Wenn dein Code Test-Fixtures hat (JSON-Dateien mit Sample-Payloads), sollten die zum Schema passen:
// vitest test
import { describe, it, expect } from 'vitest';
import Ajv from 'ajv';
import addFormats from 'ajv-formats';
import userSchema from '../schemas/user.schema.json';
import userFixture from '../fixtures/users/valid-user.json';
const ajv = new Ajv();
addFormats(ajv);
const validate = ajv.compile(userSchema);
describe('user fixture', () => {
it('matches the schema', () => {
const ok = validate(userFixture);
expect(ok, JSON.stringify(validate.errors)).toBe(true);
});
});
Bei Schema-Änderungen: Tests werden rot, du siehst sofort welche Fixtures angepasst werden müssen.
Stufe 3: Response-Validation als Contract-Test
Endpoint-Tests, die das Response-Body gegen das Response-Schema prüfen:
import { describe, it, expect } from 'vitest';
import request from 'supertest';
import { app } from '../app';
import responseSchema from '../schemas/users-get.response.schema.json';
const validate = ajv.compile(responseSchema);
describe('GET /users/:id', () => {
it('returns a user matching the schema', async () => {
const res = await request(app).get('/users/123');
expect(res.status).toBe(200);
expect(validate(res.body), JSON.stringify(validate.errors)).toBe(true);
});
});
GitLab-CI-Beispiel
# .gitlab-ci.yml
stages:
- validate
- test
- build
validate:schemas:
stage: validate
image: node:22-alpine
script:
- npm ci
- npm run validate:schemas
rules:
- changes:
- 'src/schemas/**/*.schema.json'
test:contracts:
stage: test
image: node:22-alpine
script:
- npm ci
- npm run test
needs: [validate:schemas]
Pre-Commit-Hook für lokale Validation
Mit husky + lint-staged:
# .lintstagedrc.json
{
"src/schemas/**/*.schema.json": [
"ajv compile --strict=true -s"
],
"src/fixtures/**/*.json": [
"node scripts/validate-fixture.mjs"
]
}
Vorteil: Schema-Fehler werden sofort beim Commit gefangen, nicht erst im CI.
OpenAPI-Linting parallel
Wenn du eine OpenAPI-Spec hast: Redocly CLI im selben Build-Step:
npx @redocly/cli lint openapi.yaml
npx @redocly/cli stats openapi.yaml
Typische CI-Fehler und Fix
| Fehler | Fix |
|---|---|
| "strict mode: unknown keyword 'example'" | example ist OpenAPI-spezifisch - in pure JSON Schema raus oder ignorieren via strict: false |
| "can't resolve reference $ref" | loadSchema-Callback in Ajv konfigurieren, oder relative Pfade fix |
| "format 'date-time' validation failed" | addFormats(ajv) vergessen - vor ajv.compile() aufrufen |
| "strict mode: required is not an array" | required ist Array von Strings, nicht Object |
Performance-Tipp: pre-compile zur Deploy-Zeit
Ajv kann Schemas vor-kompilieren und als JS-Modul exportieren:
npx ajv-cli compile -s schemas/user.schema.json -o dist/user-validate.js
Das fertige Modul lädst du dann zur Laufzeit - keine Compile-Zeit mehr beim App-Start, deutlich kleinere Bundle, kein ajv-Runtime nötig.