typecompass
v1.0.4
Published
Type-safe API contracts with OpenAPI generation for TypeScript
Maintainers
Readme
TypeCompass
Navigate your API with type-safe precision 🧭
TypeCompass is a lightweight TypeScript library that lets you define API contracts once and use them everywhere — with full type safety, automatic OpenAPI generation, and zero runtime overhead.
Features
✨ Type-Safe Contracts - Define routes with Zod schemas, get full TypeScript inference
🔄 Client & Server - Use the same contract for backend validation and frontend calls
📚 Auto OpenAPI - Generate OpenAPI 3.0 specs automatically from your contracts
🎯 Zero Runtime Cost - Contracts are just types on the client side
🔗 Combinable - Merge multiple service contracts into one unified API
🎨 Framework Agnostic - Works with Express, Fastify, Hono, or any Node.js framework
Installation
npm install typecompass zodQuick Start
1. Define a Contract
// contracts/auth.contract.ts
import { defineRoutes } from 'typecompass'
import { z } from 'zod'
export const AuthContract = defineRoutes({
login: {
method: "POST",
path: "/auth/login",
summary: "User Login",
tags: ["Auth"],
body: z.object({
email: z.string().email(),
password: z.string().min(8),
}),
responses: {
200: {
schema: z.object({
token: z.string(),
user: z.object({
id: z.string(),
email: z.string(),
}),
}),
},
},
},
register: {
method: "POST",
path: "/auth/register",
summary: "User Registration",
tags: ["Auth"],
body: z.object({
email: z.string().email(),
password: z.string().min(8),
name: z.string(),
}),
},
})2. Use on the Backend
// server.ts
import express from 'express'
import { AuthContract } from './contracts/auth.contract'
const app = express()
app.post('/auth/login', async (req, res) => {
// Validate with the contract schema
const data = AuthContract.login.body.parse(req.body)
// TypeScript knows the shape of data!
const user = await loginUser(data.email, data.password)
res.json({ token: user.token, user })
})
// Generate OpenAPI documentation
app.get('/openapi.json', (req, res) => {
const spec = AuthContract.generateOpenApi({
title: "Auth API",
version: "1.0.0",
servers: [{ url: "https://api.example.com", description: "Production" }],
})
res.json(spec)
})3. Use on the Frontend
// api/client.ts
import { AuthContract } from '@company/api-contracts'
import { initClient } from 'typecompass/client'
import axios from 'axios'
const axiosInstance = axios.create({
baseURL: 'https://api.example.com',
})
const client = initClient(AuthContract, axiosInstance)
// Fully typed API calls!
const response = await client.login({
body: {
email: "[email protected]",
password: "secret123",
}
})
// TypeScript knows response.data.token exists
console.log(response.data.token)Combining Multiple Contracts
For larger applications with multiple services:
// contracts/index.ts
import { combineContracts } from 'typecompass'
import { AuthContract } from './auth.contract'
import { UsersContract } from './users.contract'
import { PaymentsContract } from './payments.contract'
export const ApiContract = combineContracts({
auth: AuthContract,
users: UsersContract,
payments: PaymentsContract,
})
// Generate unified OpenAPI spec
export const openApiSpec = ApiContract.generateOpenApi({
title: "Company API",
version: "1.0.0",
servers: [
{ url: "https://api.company.com", description: "Production" },
{ url: "http://localhost:3000", description: "Development" },
],
securitySchemes: {
BearerAuth: {
type: "http",
scheme: "bearer",
},
},
defaultSecurity: ["BearerAuth"],
})Client with Combined Contracts
// Frontend usage
import { ApiContract } from '@company/api-contracts'
import { initCombinedClient } from 'typecompass/client'
const client = initCombinedClient(ApiContract, axiosInstance)
// Namespaced by service
await client.auth.login({ body: {...} })
await client.users.getProfile({ params: { id: "123" } })
await client.payments.createCharge({ body: {...} })Advanced Features
Path Parameters
const UsersContract = defineRoutes({
getUser: {
method: "GET",
path: "/users/:id",
summary: "Get User by ID",
params: z.object({
id: z.string(),
}),
},
})
// Client usage
await client.getUser({ params: { id: "user-123" } })Query Parameters
const ProductsContract = defineRoutes({
searchProducts: {
method: "GET",
path: "/products",
summary: "Search Products",
query: z.object({
q: z.string(),
category: z.string().optional(),
limit: z.number().default(10),
}),
},
})
// Client usage
await client.searchProducts({
query: { q: "laptop", category: "electronics", limit: 20 }
})Custom Security
const AdminContract = defineRoutes({
deleteUser: {
method: "DELETE",
path: "/admin/users/:id",
summary: "Delete User",
tags: ["Admin"],
security: ["BearerAuth", "AdminRole"],
params: z.object({
id: z.string(),
}),
},
})Schema Examples
Add examples to your schemas for better documentation:
import { z } from 'zod'
const createUserSchema = z.object({
email: z.string().email().example("[email protected]"),
name: z.string().example("John Doe"),
age: z.number().min(18).example(25),
})Publishing Contracts
For teams with separate frontend/backend repos, publish your contracts as an npm package:
1. Create Contracts Package
packages/api-contracts/package.json
{
"name": "@company/api-contracts",
"version": "1.0.0",
"private": true,
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"dependencies": {
"typecompass": "^1.0.0"
},
"peerDependencies": {
"zod": "^3.0.0"
}
}2. Export Combined Contract
// packages/api-contracts/src/index.ts
export { ApiContract } from './contracts'
export type { ApiContract as ApiContractType } from './contracts'3. Install in Frontend
npm install @company/api-contractsimport { ApiContract } from '@company/api-contracts'
import { initCombinedClient } from 'typecompass/client'
const api = initCombinedClient(ApiContract, axiosInstance)API Reference
defineRoutes(routes)
Define a single service contract.
Parameters:
routes- Object mapping route names to route configurations
Returns: Contract<T>
combineContracts(contracts)
Combine multiple contracts into one.
Parameters:
contracts- Object mapping namespace to contracts
Returns: CombinedContract<T>
generateOpenApi(contract, config)
Generate OpenAPI 3.0 specification.
Parameters:
contract- Contract or CombinedContractconfig- OpenAPI configuration (title, version, servers, etc.)
Returns: OpenAPI JSON object
initClient(contract, api)
Initialize type-safe client for a single contract.
Parameters:
contract- Contract to useapi- Axios-like API provider
Returns: Typed client object
initCombinedClient(contract, api)
Initialize type-safe client for combined contracts.
Parameters:
contract- CombinedContract to useapi- Axios-like API provider
Returns: Namespaced typed client object
Route Configuration
interface RouteConfig {
method: "GET" | "POST" | "PUT" | "PATCH" | "DELETE"
path: string // e.g., "/users/:id"
summary: string // Short description
tags?: string[] // For OpenAPI grouping
security?: string[] // Security scheme names
body?: z.ZodType // Request body schema
query?: z.ZodType // Query parameters schema
params?: z.ZodType // Path parameters schema
responses?: Record<number, ResponseConfig>
description?: string // Long description
}License
MIT © Jesulonimii
Contributing
Contributions welcome! Please open an issue or PR.
