Skip to main content

Node.js SDK

What is the Node.js SDK?

The Node.js SDK provides TypeScript-first contract validation for Node.js applications. It includes HTTP client adapters for automatic validation, producer middleware for Express and Fastify, and a test kit for schema compliance testing.

SDK Architecture

For information about SDK design patterns, adapter architecture, and cross-language consistency, see SDK Architecture.

Installation

npm install @sahina/cvt-sdk

Quick Start

import { ContractValidator } from "@sahina/cvt-sdk";

const validator = new ContractValidator("localhost:9550");

// Register a schema from file
await validator.registerSchema("petstore", "./openapi.json");
// Or register from URL:
// await validator.registerSchema("petstore", "https://petstore3.swagger.io/api/v3/openapi.json");

// Validate an interaction
const result = await validator.validate(
{ method: "GET", path: "/pet/123" },
{ statusCode: 200, body: { id: 123, name: "doggie", status: "available" } },
);

console.log(result.valid); // true or false
if (!result.valid) {
console.error("Errors:", result.errors);
}

// Clean up
validator.close();

API Reference

ContractValidator

Constructor

// Simple usage (insecure connection)
new ContractValidator(address?: string)

// With options (TLS and API key)
new ContractValidator(options: ContractValidatorOptions)

Simple usage:

const validator = new ContractValidator("localhost:9550");

With options:

const validator = new ContractValidator({
address: "localhost:9550",
tls: {
enabled: true,
rootCertPath: "./certs/ca.crt",
},
apiKey: "your-api-key",
});
OptionTypeDescription
addressstringServer address (default: localhost:9550)
tls.enabledbooleanEnable TLS
tls.rootCertPathstringPath to CA certificate
tls.certPathstringPath to client certificate (for mTLS)
tls.keyPathstringPath to client private key (for mTLS)
apiKeystringAPI key for authentication

Methods

registerSchema

Registers an OpenAPI schema from a file path or URL.

registerSchema(schemaId: string, schemaPath: string): Promise<void>
// From local file
await validator.registerSchema("petstore", "./openapi.json");

// From URL
await validator.registerSchema(
"petstore",
"https://petstore3.swagger.io/api/v3/openapi.json",
);
registerSchemaWithVersion

Registers a schema with version information for comparison.

registerSchemaWithVersion(schemaId: string, schemaPath: string, version: string): Promise<void>
validate

Validates an HTTP request/response pair against the registered schema.

validate(request: ValidationRequest, response: ValidationResponse): Promise<ValidationResult>
compareSchemas

Compares two schema versions for breaking changes.

compareSchemas(schemaId: string, oldVersion?: string, newVersion?: string): Promise<CompareResult>
generateFixture

Generates test fixtures from the schema.

generateFixture(method: string, path: string, options?: GenerateOptions): Promise<GeneratedFixture>
generateResponse

Generates a response fixture only.

generateResponse(method: string, path: string, options?: GenerateOptions): Promise<GeneratedResponse>
generateRequestBody

Generates a request body fixture for an endpoint.

generateRequestBody(method: string, path: string, options?: GenerateOptions): Promise<any>
const body = await validator.generateRequestBody("POST", "/pet");
console.log(body); // { name: "string", status: "available" }
listEndpoints

Lists all endpoints in the registered schema.

listEndpoints(): Promise<EndpointInfo[]>
registerConsumer

Registers a consumer with expected interactions.

registerConsumer(options: RegisterConsumerOptions): Promise<ConsumerInfo>
listConsumers

Lists all consumers for a schema.

listConsumers(schemaId: string, environment?: string): Promise<ConsumerInfo[]>
deregisterConsumer

Removes a consumer registration.

deregisterConsumer(consumerId: string, schemaId: string, environment: string): Promise<void>
canIDeploy

Checks if a schema version can be safely deployed.

canIDeploy(schemaId: string, newVersion: string, environment: string): Promise<CanIDeployResult>
close

Closes the gRPC connection.

close(): void

HTTP Adapters

Axios Adapter

Automatically validate all Axios requests:

import axios from "axios";
import { ContractValidator } from "@sahina/cvt-sdk";
import { createAxiosAdapter } from "@sahina/cvt-sdk/adapters";

const validator = new ContractValidator("localhost:9550");
await validator.registerSchema("petstore", "./openapi.json");

const api = axios.create({ baseURL: "http://petstore-service" });

const adapter = createAxiosAdapter({
axios: api,
validator,
autoValidate: true,
onValidationFailure: (result) => {
throw new Error(`Contract violation: ${result.errors.join(", ")}`);
},
});

// All requests are now validated
const response = await api.get("/pet/123");

// Check captured interactions
const interactions = adapter.getInteractions();
console.log(interactions[0].validationResult?.valid);

// Clean up
adapter.detach();

Fetch Adapter

import { ContractValidator } from "@sahina/cvt-sdk";
import { createFetchAdapter } from "@sahina/cvt-sdk/adapters";

const validator = new ContractValidator("localhost:9550");
await validator.registerSchema("petstore", "./openapi.json");

const adapter = createFetchAdapter({
validator,
baseURL: "http://petstore-service",
});

// Use the adapter's fetch method
const response = await adapter.fetch("/pet/123");
const data = await response.json();

// Check captured interactions
const interactions = adapter.getInteractions();

Producer Middleware

Express

import express from "express";
import { ContractValidator } from "@sahina/cvt-sdk";
import { createExpressMiddleware } from "@sahina/cvt-sdk/producer";

const app = express();
app.use(express.json());

const validator = new ContractValidator("localhost:9550");
await validator.registerSchema("petstore", "./openapi.json");

app.use(
createExpressMiddleware({
schemaId: "petstore",
validator,
mode: "strict", // "strict" | "warn" | "shadow"
excludePaths: ["/health", "/metrics"],
}),
);

app.get("/pet/:petId", (req, res) => {
res.json({
id: parseInt(req.params.petId),
name: "doggie",
status: "available",
});
});

Fastify

import Fastify from "fastify";
import { ContractValidator } from "@sahina/cvt-sdk";
import { fastifyProducerPlugin } from "@sahina/cvt-sdk/producer";

const fastify = Fastify();
const validator = new ContractValidator("localhost:9550");
await validator.registerSchema("petstore", "./openapi.json");

fastify.register(fastifyProducerPlugin, {
schemaId: "petstore",
validator,
mode: "strict",
});

Producer Test Kit

Test your API responses against your schema without real consumers:

import { ProducerTestKit } from "@sahina/cvt-sdk/producer";

describe("Pet API", () => {
let testKit: ProducerTestKit;

beforeAll(async () => {
testKit = new ProducerTestKit({
schemaId: "petstore",
serverAddress: "localhost:9550",
});
});

afterAll(() => testKit.close());

it("returns valid response for GET /pet/{petId}", async () => {
const result = await testKit.validateResponse({
method: "GET",
path: "/pet/123",
response: {
statusCode: 200,
body: { id: 123, name: "doggie", status: "available" },
},
});

expect(result.valid).toBe(true);
});

it("detects invalid response", async () => {
const result = await testKit.validateResponse({
method: "GET",
path: "/pet/123",
response: {
statusCode: 200,
body: { id: "not-a-number" }, // Missing required fields
},
});

expect(result.valid).toBe(false);
expect(result.errors.length).toBeGreaterThan(0);
});
});

Auto-Registration

Build consumer registrations from captured mock interactions:

import { createMockAdapter } from "@sahina/cvt-sdk/adapters";

// Capture interactions during tests
const mock = createMockAdapter({ validator, cache: true });
await mock.fetch("http://mock.petstore/pet/123");
await mock.fetch("http://mock.petstore/pet", { method: "POST", body: "{}" });

// Build registration options (preview)
const opts = validator.buildConsumerFromInteractions(mock.getInteractions(), {
consumerId: "order-service",
consumerVersion: "2.1.0",
environment: "dev",
schemaVersion: "1.0.0",
});
console.log(`Would register ${opts.usedEndpoints?.length} endpoints`);

// Or register directly
const info = await validator.registerConsumerFromInteractions(
mock.getInteractions(),
{
consumerId: "order-service",
consumerVersion: "2.1.0",
environment: "dev",
schemaVersion: "1.0.0",
},
);

buildConsumerFromInteractions

Builds consumer registration options from captured interactions without registering.

buildConsumerFromInteractions(
interactions: CapturedInteraction[],
config: AutoRegisterConfig
): RegisterConsumerOptions

registerConsumerFromInteractions

Registers a consumer from captured interactions (combines buildConsumerFromInteractions + registerConsumer).

registerConsumerFromInteractions(
interactions: CapturedInteraction[],
config: AutoRegisterConfig
): Promise<ConsumerInfo>

TLS Configuration

const validator = new ContractValidator({
address: "localhost:9550",
tls: {
enabled: true,
rootCertPath: "./certs/ca.crt",
},
});

Mutual TLS (mTLS)

const validator = new ContractValidator({
address: "localhost:9550",
tls: {
enabled: true,
rootCertPath: "./certs/ca.crt",
certPath: "./certs/client.crt",
keyPath: "./certs/client.key",
},
});

API Key Authentication

const validator = new ContractValidator({
address: "localhost:9550",
apiKey: "your-api-key",
});

TypeScript Types

The SDK exports all TypeScript types:

import {
ContractValidator,
ContractValidatorOptions,
TLSOptions,
ValidationRequest,
ValidationResponse,
ValidationResult,
BreakingChange,
CompareResult,
GenerateOptions,
GeneratedFixture,
GeneratedRequest,
GeneratedResponse,
EndpointInfo,
RegisterConsumerOptions,
ConsumerInfo,
ConsumerImpact,
EndpointUsage,
CanIDeployResult,
} from "@sahina/cvt-sdk";

Error Handling

try {
await validator.registerSchema("petstore", "./openapi.json");
} catch (error) {
if (error.code === "UNAVAILABLE") {
console.error("CVT server is not reachable");
} else {
console.error("Registration failed:", error.message);
}
}

// Validation errors are returned in the result, not thrown
const result = await validator.validate(request, response);
if (!result.valid) {
console.error("Validation errors:", result.errors);
}