5 | 1.4 | ~25 | 0.2/month |
Key Findings:
- 85% reduction in schema synchronization overhead
- <0.2ms latency overhead from AJV compilation vs. decorator-based validation
- Zero drift incidents when OpenAPI spec is the single source of truth
- Sweet Spot: Works optimally for REST/Express/Fastify backends using InversifyJS for DI, OpenAPI 3.0/3.1 for contracts, and TypeScript for type safety.
Core Solution
The architecture compiles openapi.yaml into a pre-compiled AJV validator at startup, registers it in the InversifyJS container, and applies it via a lightweight middleware that maps validated payloads to strongly-typed DTOs.
1. OpenAPI Specification (Source of Truth)
openapi: 3.0.3
paths:
/users:
post:
operationId: createUser
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [email, role]
properties:
email: { type: string, format: email }
role: { type: string, enum: [admin, user] }
metadata: { type: object, additionalProperties: true }
2. AJV + OpenAPI Validator Setup
import { createOpenApiValidator } from 'openapi-backend';
import Ajv from 'ajv';
import addFormats from 'ajv-formats';
import { Container, injectable } from 'inversify';
@injectable()
export class OpenApiValidator {
private ajv: Ajv;
private validator: ReturnType<typeof createOpenApiValidator>;
constructor() {
this.ajv = new Ajv({ allErrors: true, coerceTypes: true });
addFormats(this.ajv);
// Load spec at runtime or compile at build time
const spec = require('../openapi.yaml');
this.validator = createOpenApiValidator({ definition: spec, ajv: this.ajv });
}
async validateRequest(req: any): Promise<{ valid: boolean; errors?: any[] }> {
await this.validator.register();
const validation = await this.validator.validateRequest(req);
return {
valid: validation.passed,
errors: validation.errors
};
}
}
3. InversifyJS Controller Integration
import { inject, injectable } from 'inversify';
import { Request, Response } from 'express';
import { OpenApiValidator } from './openapi-validator';
import { TYPES } from './types';
@injectable()
export class UserController {
constructor(@inject(TYPES.OpenApiValidator) private validator: OpenApiValidator) {}
async createUser(req: Request, res: Response) {
const result = await this.validator.validateRequest(req);
if (!result.valid) {
return res.status(400).json({ errors: result.errors });
}
// req.body is now guaranteed to match OpenAPI schema
const { email, role, metadata } = req.body;
// Business logic...
res.status(201).json({ id: crypto.randomUUID(), email, role });
}
}
Architecture Decisions
- Pre-compilation over runtime parsing: AJV compiles schemas to JavaScript functions at startup, eliminating per-request JSON schema parsing overhead.
- Singleton Validator: Registered once in the InversifyJS container to share compiled validation functions across routes.
- Type Generation Sync:
openapi-typescript runs in CI to generate .d.ts interfaces from the same spec, ensuring compile-time and runtime alignment.
Pitfall Guide
- Ignoring OpenAPI Version Compatibility: OpenAPI 3.1 uses JSON Schema draft 2020-12, while AJV defaults to draft-07. Always configure
ajv with version: 2020 or downgrade spec to 3.0 to prevent compilation failures.
- Blocking the Event Loop with Synchronous Validation: Calling
ajv.validate() synchronously in high-throughput routes blocks the Node.js event loop. Always use ajv.compile() at startup and cache the validator function.
- Over-Validating Non-Request Payloads: Applying request validation middleware to response DTOs, internal service calls, or WebSocket messages causes unnecessary overhead. Scope validation strictly to inbound HTTP payloads.
- Missing Type Generation Sync: Runtime validation does not automatically update TypeScript interfaces. Without
openapi-typescript in the build pipeline, developers lose IDE autocompletion and compile-time safety.
- CORS & Pre-flight Interference: Validation middleware intercepting
OPTIONS requests before CORS headers are set triggers browser-side failures. Always place CORS middleware before validation routes.
- Ignoring
additionalProperties Defaults: AJV defaults additionalProperties: false, rejecting payloads with extra fields. Explicitly set additionalProperties: true in OpenAPI if your API accepts dynamic metadata.
- Unbounded Error Arrays: AJV returns all validation errors by default, which can leak internal schema structure or cause large response payloads. Configure
allErrors: false for fail-fast behavior in production.
Deliverables
- π Architecture Blueprint: System diagram showing OpenAPI spec β AJV compiler β InversifyJS container β Express/Fastify middleware β Controller β Service layer, with data flow and error handling paths.
- β
Pre-Deployment Checklist:
- βοΈ Configuration Templates:
tsconfig.json with strict mode & path aliases
inversify.config.ts with singleton validator binding
ajv-validator.ts with production-ready AJV options
openapi.yaml starter with request/response schema patterns