🚧 This project is under active development and not yet ready for production use. No alpha version has been published.
An opinionated schema-first TypeScript contract library for defining type-safe HTTP and WebSocket APIs.
Minimal by design. No framework integrations.
Every integration adds surface area, complexity, and long-term maintenance burden. ts-contract stays focused on a small, durable core with first-class TypeScript inference and composable primitives that make it easy to integrate with any stack.
- Small core. Zero integrations. — No lock-in to any server or client framework.
- Schema-first. — Define your API shape with the validation library you already use.
- Excellent TypeScript inference. — Extract path params, query, body, headers, and response types from your contract.
- Composable primitives. — Add capabilities through a plugin system, not monolithic abstractions.
- Integrate it your way. — Use the inference types to wire up Hono, Express, Fastify, React Query, or anything else.
- Supports @standard-schema/spec. — Works with Zod, Valibot, Arktype, and any Standard Schema compliant library.
import { createContract, initContract } from '@ts-contract/core';
import { pathPlugin, validatePlugin } from '@ts-contract/plugins';
import { z } from 'zod';
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(pathPlugin).use(validatePlugin).build();
// Type-safe URL construction
const url = api.getUser.buildPath({ id: '123' });
// => "/users/123"
// Runtime validation against your schema
const user = api.getUser.validateResponse(200, data);
// => { id: string, name: string, email: string }import { createContract, initContract } from '@ts-contract/core';
import {
websocketPathPlugin,
websocketValidatePlugin,
} from '@ts-contract/plugins';
import { z } from 'zod';
const contract = createContract({
chat: {
type: 'websocket',
path: '/ws/chat/:roomId',
pathParams: z.object({ roomId: z.string() }),
query: z.object({ token: z.string() }),
clientMessages: {
new_msg: z.object({
type: z.literal('new_msg'),
body: z.string(),
}),
},
serverMessages: {
new_msg: z.object({
type: z.literal('new_msg'),
id: z.string(),
body: z.string(),
userId: z.string(),
}),
},
},
});
const api = initContract(contract)
.useWebSocket(websocketPathPlugin)
.useWebSocket(websocketValidatePlugin)
.build();
// Build WebSocket URL
const url = api.chat.buildPath({ roomId: '123' }, { token: 'abc' });
// => "/ws/chat/123?token=abc"
// Validate outgoing message
const msg = api.chat.validateClientMessage('new_msg', {
type: 'new_msg',
body: 'Hello!',
});
// Validate incoming message (e.g., with Phoenix.js)
channel.on('new_msg', (data) => {
const validated = api.chat.validateServerMessage('new_msg', data);
console.log(validated.body);
});| Package | Description |
|---|---|
@ts-contract/core |
Contract definitions, type inference helpers, and plugin system |
@ts-contract/plugins |
Built-in plugins for path building and schema validation |
Documentation is available at the docs site (coming soon).
To run the docs locally:
pnpm install
pnpm --filter @ts-contract/docs devThis is a monorepo using pnpm workspaces.
# Install dependencies
pnpm install
# Build all packages
pnpm -r build
# Run tests
pnpm -r test
# Run docs dev server
pnpm --filter @ts-contract/docs devWe welcome contributions! Please see our Contributing Guide for details on:
- Setting up your development environment
- Our development workflow
- How to create changesets for version management
- Pull request process
- Release process