validatePlugin API
Complete API reference for the validatePlugin.
Overview
The validatePlugin is a built-in plugin that adds validation methods to each route for runtime schema validation.
Import
import { validatePlugin } from '@ts-contract/plugins';Plugin Object
const validatePlugin: ContractPlugin<'validate'>Properties
- name:
'validate' - route: Function that adds validation methods to routes
Usage
import { initContract } from '@ts-contract/core';
import { validatePlugin } from '@ts-contract/plugins';
import { contract } from './contract';
const api = initContract(contract)
.use(validatePlugin)
.build();Added Methods
validatePathParams()
Validates path parameters against the route's pathParams schema.
Signature
validatePathParams(params: unknown): InferPathParams<Route>Parameters
- params - Unknown data to validate
Returns
- Validated and typed path parameters
Throws
- Error if validation fails or if route has no
pathParamsschema
Example
const contract = createContract({
getUser: {
method: 'GET',
path: '/users/:id',
pathParams: z.object({ id: z.string().uuid() }),
responses: {
200: z.object({ id: z.string() }),
},
},
});
const api = initContract(contract).use(validatePlugin).build();
// ✓ Valid
const params = api.getUser.validatePathParams({
id: '550e8400-e29b-41d4-a716-446655440000',
});
// => { id: '550e8400-e29b-41d4-a716-446655440000' }
// ✗ Throws validation error
api.getUser.validatePathParams({ id: 'not-a-uuid' });validateQuery()
Validates query parameters against the route's query schema.
Signature
validateQuery(query: unknown): InferQuery<Route>Parameters
- query - Unknown data to validate
Returns
- Validated and typed query parameters
Throws
- Error if validation fails or if route has no
queryschema
Example
const contract = createContract({
listUsers: {
method: 'GET',
path: '/users',
query: z.object({
page: z.string().transform(Number),
limit: z.string().transform(Number).optional(),
}),
responses: {
200: z.array(z.object({ id: z.string() })),
},
},
});
const api = initContract(contract).use(validatePlugin).build();
// ✓ Valid - transforms strings to numbers
const query = api.listUsers.validateQuery({ page: '2', limit: '10' });
// => { page: 2, limit: 10 }
// ✗ Throws validation error
api.listUsers.validateQuery({ page: 'invalid' });validateBody()
Validates request body against the route's body schema.
Signature
validateBody(body: unknown): InferBody<Route>Parameters
- body - Unknown data to validate
Returns
- Validated and typed request body
Throws
- Error if validation fails or if route has no
bodyschema
Example
const contract = createContract({
createUser: {
method: 'POST',
path: '/users',
body: z.object({
name: z.string().min(1),
email: z.string().email(),
}),
responses: {
201: z.object({ id: z.string() }),
},
},
});
const api = initContract(contract).use(validatePlugin).build();
// ✓ Valid
const body = api.createUser.validateBody({
name: 'Alice',
email: '[email protected]',
});
// ✗ Throws validation error
api.createUser.validateBody({
name: '',
email: 'not-an-email',
});validateResponse()
Validates response data against the route's response schema for a specific status code.
Signature
validateResponse<S extends keyof Route['responses'] & HttpStatusCodes>(
status: S,
data: unknown
): InferResponseBody<Route, S>Type Parameters
- S - HTTP status code (must be defined in route's
responses)
Parameters
- status - HTTP status code
- data - Unknown data to validate
Returns
- Validated and typed response body for the specified status code
Throws
- Error if validation fails or if route has no response schema for the status code
Example
const contract = createContract({
getUser: {
method: 'GET',
path: '/users/:id',
pathParams: z.object({ id: z.string() }),
responses: {
200: z.object({
id: z.string(),
name: z.string(),
email: z.string().email(),
}),
404: z.object({
message: z.string(),
}),
},
},
});
const api = initContract(contract).use(validatePlugin).build();
// ✓ Valid - 200 response
const user = api.getUser.validateResponse(200, {
id: '123',
name: 'Alice',
email: '[email protected]',
});
// ✓ Valid - 404 response
const error = api.getUser.validateResponse(404, {
message: 'User not found',
});
// ✗ Throws validation error - wrong schema for status
api.getUser.validateResponse(200, {
message: 'User not found',
});validateHeaders()
Validates request headers against the route's headers schema.
Signature
validateHeaders(headers: Record<string, unknown>): InferHeaders<Route>Parameters
- headers - Object with header names and values
Returns
- Validated and typed headers
Throws
- Error if validation fails or if route has no
headersschema
Example
const contract = createContract({
getProtected: {
method: 'GET',
path: '/protected',
headers: {
'authorization': z.string().startsWith('Bearer '),
'x-api-key': z.string(),
},
responses: {
200: z.object({ data: z.string() }),
},
},
});
const api = initContract(contract).use(validatePlugin).build();
// ✓ Valid
const headers = api.getProtected.validateHeaders({
'authorization': 'Bearer token123',
'x-api-key': 'key123',
});
// ✗ Throws validation error
api.getProtected.validateHeaders({
'authorization': 'token123', // Missing "Bearer " prefix
'x-api-key': 'key123',
});Error Handling
All validation methods throw errors with descriptive messages:
try {
api.createUser.validateBody({
name: '',
email: 'invalid-email',
});
} catch (error) {
console.error(error.message);
// => "Validation failed for body of /users: String must contain at least 1 character(s), Invalid email"
}Error messages include:
- What failed (pathParams, query, body, response, headers)
- Which route (path)
- Specific validation issues from the schema library
Missing Schema Errors
If you try to validate a field that doesn't have a schema:
const contract = createContract({
getUser: {
method: 'GET',
path: '/users/:id',
// No body schema defined
responses: {
200: z.object({ id: z.string() }),
},
},
});
const api = initContract(contract).use(validatePlugin).build();
// Throws: Error: Route "/users/:id" has no body schema
api.getUser.validateBody({ name: 'Alice' });Integration with Standard Schema
The validate plugin works with any schema library implementing @standard-schema/spec:
- Zod -
z.object(),z.string(), etc. - Valibot -
v.object(),v.string(), etc. - Arktype -
type()definitions - Any custom Standard Schema implementation
Type Safety
All validation methods return properly typed values:
const contract = createContract({
createUser: {
method: 'POST',
path: '/users',
body: z.object({
name: z.string(),
email: z.string().email(),
}),
responses: {
201: z.object({
id: z.string(),
name: z.string(),
}),
},
},
});
const api = initContract(contract).use(validatePlugin).build();
const body = api.createUser.validateBody(unknownData);
// body is typed as { name: string; email: string }
const response = api.createUser.validateResponse(201, unknownData);
// response is typed as { id: string; name: string }Performance
- Bundle size: ~800 bytes minified + gzipped (plus your schema library)
- Runtime: Depends on schema library (Zod, Valibot, Arktype)
- Memory: No state or caching
Validation overhead varies by schema library:
- Zod: ~10-50ms for typical schemas
- Valibot: ~5-20ms (faster)
- Arktype: ~5-15ms (fastest)
Type Registry
The plugin registers its types using declaration merging:
declare module '@ts-contract/core' {
interface PluginTypeRegistry<R> {
validate: {
validatePathParams: R extends RouteDef
? (params: unknown) => InferPathParams<R>
: never;
validateQuery: R extends RouteDef
? (query: unknown) => InferQuery<R>
: never;
validateBody: R extends RouteDef
? (body: unknown) => InferBody<R>
: never;
validateResponse: R extends RouteDef
? <S extends keyof R['responses'] & HttpStatusCodes>(
status: S,
data: unknown,
) => InferResponseBody<R, S>
: never;
validateHeaders: R extends RouteDef
? (headers: Record<string, unknown>) => InferHeaders<R>
: never;
};
}
}This enables full type safety for all validation methods.
Common Patterns
Server-Side Validation
import express from 'express';
app.post('/users', (req, res) => {
try {
const body = api.createUser.validateBody(req.body);
const user = database.createUser(body);
res.status(201).json(user);
} catch (error) {
res.status(400).json({ message: error.message });
}
});Client-Side Validation
async function fetchUser(id: string) {
const response = await fetch(`/users/${id}`);
const data = await response.json();
if (response.status === 404) {
const error = api.getUser.validateResponse(404, data);
throw new Error(error.message);
}
return api.getUser.validateResponse(200, data);
}Conditional Validation
const shouldValidate = process.env.NODE_ENV === 'development';
async function fetchUser(id: string) {
const response = await fetch(`/users/${id}`);
const data = await response.json();
return shouldValidate
? api.getUser.validateResponse(200, data)
: data;
}See Also
- Validate Plugin Guide - Detailed guide with examples
- pathPlugin - URL building plugin
- initContract - Initialize contracts with plugins
- Creating Custom Plugins - Build your own plugins