Type Helpers
Complete API reference for TypeScript type inference helpers.
Overview
ts-contract provides type helpers that extract TypeScript types from your route definitions. These enable end-to-end type safety without code generation.
All type helpers are compile-time only - they have zero runtime overhead.
Import
import type {
InferPathParams,
InferQuery,
InferBody,
InferHeaders,
InferResponseBody,
InferResponses,
InferArgs,
} from '@ts-contract/core';Type Helpers Reference
InferPathParams
Extract path parameter types from a route.
type InferPathParams<R extends RouteDef>Type Parameters:
- R - A route definition type
Returns:
- Object type with path parameter properties, or
undefinedif nopathParams
Example:
const contract = createContract({
getPost: {
method: 'GET',
path: '/users/:userId/posts/:postId',
pathParams: z.object({
userId: z.string(),
postId: z.string(),
}),
responses: {
200: z.object({ id: z.string() }),
},
},
});
type Params = InferPathParams<typeof contract.getPost>;
// => { userId: string; postId: string }Usage:
function handleRequest(params: Params) {
console.log(params.userId, params.postId);
}InferQuery
Extract query parameter types from a route.
type InferQuery<R extends RouteDef>Type Parameters:
- R - A route definition type
Returns:
- Object type with query parameter properties, or
undefinedif noquery
Example:
const contract = createContract({
listUsers: {
method: 'GET',
path: '/users',
query: z.object({
page: z.string().optional(),
limit: z.string().optional(),
sort: z.enum(['asc', 'desc']).optional(),
}),
responses: {
200: z.array(z.object({ id: z.string() })),
},
},
});
type Query = InferQuery<typeof contract.listUsers>;
// => { page?: string; limit?: string; sort?: 'asc' | 'desc' }Usage:
function buildQueryString(query: Query): string {
const params = new URLSearchParams();
if (query.page) params.set('page', query.page);
if (query.limit) params.set('limit', query.limit);
if (query.sort) params.set('sort', query.sort);
return params.toString();
}InferBody
Extract request body type from a route.
type InferBody<R extends RouteDef>Type Parameters:
- R - A route definition type
Returns:
- Request body type, or
undefinedif nobody
Example:
const contract = createContract({
createUser: {
method: 'POST',
path: '/users',
body: z.object({
name: z.string(),
email: z.string().email(),
age: z.number().int().min(0),
}),
responses: {
201: z.object({ id: z.string() }),
},
},
});
type Body = InferBody<typeof contract.createUser>;
// => { name: string; email: string; age: number }Usage:
async function createUser(body: Body) {
const response = await fetch('/users', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body),
});
return response.json();
}InferHeaders
Extract request header types from a route.
type InferHeaders<R extends RouteDef>Type Parameters:
- R - A route definition type
Returns:
- Object type with header properties, or
undefinedif noheaders
Example:
const contract = createContract({
getProtected: {
method: 'GET',
path: '/protected',
headers: {
'authorization': z.string(),
'x-api-key': z.string(),
},
responses: {
200: z.object({ data: z.string() }),
},
},
});
type Headers = InferHeaders<typeof contract.getProtected>;
// => { authorization: string; 'x-api-key': string }Usage:
async function fetchProtected(headers: Headers) {
const response = await fetch('/protected', { headers });
return response.json();
}InferResponseBody
Extract a specific response type by status code.
type InferResponseBody<R extends RouteDef, S extends HttpStatusCodes>Type Parameters:
- R - A route definition type
- S - An HTTP status code
Returns:
- Response body type for the specified 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(),
}),
404: z.object({
message: z.string(),
}),
500: z.object({
message: z.string(),
code: z.string(),
}),
},
},
});
type SuccessResponse = InferResponseBody<typeof contract.getUser, 200>;
// => { id: string; name: string; email: string }
type NotFoundResponse = InferResponseBody<typeof contract.getUser, 404>;
// => { message: string }
type ErrorResponse = InferResponseBody<typeof contract.getUser, 500>;
// => { message: string; code: string }Usage:
async function getUser(id: string): Promise<SuccessResponse> {
const response = await fetch(`/users/${id}`);
if (response.status === 404) {
const error: NotFoundResponse = await response.json();
throw new Error(error.message);
}
if (!response.ok) {
throw new Error('Request failed');
}
const user: SuccessResponse = await response.json();
return user;
}InferResponses
Extract all responses as a discriminated union.
type InferResponses<R extends RouteDef>Type Parameters:
- R - A route definition type
Returns:
- Discriminated union of
{ status: StatusCode; body: ResponseBody }for all responses
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() }),
404: z.object({ message: z.string() }),
500: z.object({ message: z.string() }),
},
},
});
type Response = InferResponses<typeof contract.getUser>;
// =>
// | { status: 200; body: { id: string; name: string } }
// | { status: 404; body: { message: string } }
// | { status: 500; body: { message: string } }Usage:
function handleResponse(response: Response) {
switch (response.status) {
case 200:
// response.body is { id: string; name: string }
console.log('User:', response.body.name);
break;
case 404:
// response.body is { message: string }
console.error('Not found:', response.body.message);
break;
case 500:
// response.body is { message: string }
console.error('Server error:', response.body.message);
break;
}
}InferArgs
Merge all input types (params, query, body, headers) into a single object.
type InferArgs<R extends RouteDef>Type Parameters:
- R - A route definition type
Returns:
- Object with optional
params,query,body, andheadersproperties
Example:
const contract = createContract({
updateUser: {
method: 'PUT',
path: '/users/:id',
pathParams: z.object({ id: z.string() }),
query: z.object({ notify: z.boolean().optional() }),
body: z.object({
name: z.string(),
email: z.string(),
}),
responses: {
200: z.object({ id: z.string() }),
},
},
});
type Args = InferArgs<typeof contract.updateUser>;
// => {
// params: { id: string };
// query: { notify?: boolean };
// body: { name: string; email: string };
// }Usage:
function buildUpdateRequest(args: Args): Request {
const url = `/users/${args.params.id}${
args.query.notify ? '?notify=true' : ''
}`;
return new Request(url, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(args.body),
});
}
// Usage
const request = buildUpdateRequest({
params: { id: '123' },
query: { notify: true },
body: { name: 'Alice', email: '[email protected]' },
});WebSocket Type Helpers
ts-contract provides type helpers for WebSocket contracts:
InferWebSocketPathParams
Extract path parameter types from a WebSocket definition:
import type { InferWebSocketPathParams } from '@ts-contract/core';
type Params = InferWebSocketPathParams<typeof contract.chat>;
// => { roomId: string }InferWebSocketQuery
Extract query parameter types:
import type { InferWebSocketQuery } from '@ts-contract/core';
type Query = InferWebSocketQuery<typeof contract.chat>;
// => { token?: string }InferWebSocketHeaders
Extract header types:
import type { InferWebSocketHeaders } from '@ts-contract/core';
type Headers = InferWebSocketHeaders<typeof contract.chat>;
// => { authorization: string }InferClientMessages
Extract all client message types:
import type { InferClientMessages } from '@ts-contract/core';
type ClientMsgs = InferClientMessages<typeof contract.chat>;
// => { new_msg: { type: 'new_msg', body: string }, ... }InferServerMessages
Extract all server message types:
import type { InferServerMessages } from '@ts-contract/core';
type ServerMsgs = InferServerMessages<typeof contract.chat>;
// => { new_msg: { type: 'new_msg', id: string, body: string }, ... }InferClientMessage
Extract a specific client message type:
import type { InferClientMessage } from '@ts-contract/core';
type NewMsg = InferClientMessage<typeof contract.chat, 'new_msg'>;
// => { type: 'new_msg', body: string }InferServerMessage
Extract a specific server message type:
import type { InferServerMessage } from '@ts-contract/core';
type NewMsg = InferServerMessage<typeof contract.chat, 'new_msg'>;
// => { type: 'new_msg', id: string, body: string }Usage:
type Params = InferWebSocketPathParams<typeof contract.chat>;
type ClientMsg = InferClientMessage<typeof contract.chat, 'new_msg'>;
type ServerMsg = InferServerMessage<typeof contract.chat, 'new_msg'>;Template Literal Types
type UserId = InferPathParams<typeof contract.getUser>['id'];
type UserKey = `user:${UserId}`;
// => "user:string"Extracting Nested Types
type UserList = InferResponseBody<typeof contract.listUsers, 200>;
type User = UserList extends Array<infer U> ? U : never;
// Extract the array element typeBest Practices
1. Use Type Aliases
Create meaningful type aliases for reuse:
// types.ts
export type User = InferResponseBody<typeof contract.getUser, 200>;
export type UserList = InferResponseBody<typeof contract.listUsers, 200>;
export type CreateUserPayload = InferBody<typeof contract.createUser>;2. Colocate with Usage
Define types close to where they're used:
// user-service.ts
import type { InferResponseBody } from '@ts-contract/core';
import { contract } from './contract';
type User = InferResponseBody<typeof contract.getUser, 200>;
export class UserService {
async getUser(id: string): Promise<User> {
// ...
}
}3. Use typeof Correctly
Always use typeof when referencing contract routes:
// ✓ Correct
type User = InferResponseBody<typeof contract.getUser, 200>;
// ✗ Wrong
type User = InferResponseBody<contract.getUser, 200>;4. Avoid any
Don't cast to any - use proper type inference:
// ✗ Avoid
const user = await response.json() as any;
// ✓ Better
type User = InferResponseBody<typeof contract.getUser, 200>;
const user = await response.json() as User;See Also
- Type Inference Guide - Learn about type inference
- createContract - Create a contract
- Routes & Schemas - Route definition details
Next Steps
- See Type Inference for practical usage
- Review Contracts for defining contracts
- Explore HTTP Routes & Schemas for HTTP details
- Read WebSocket Contracts for WebSocket details