Type Inference
TypeFetch infers endpoint input and output types automatically from Zod schemas.
const user = await api.user.getUser({
path: {
id: '123',
},
})user is inferred as:
{
id: string
name: string
}Invalid input fails at compile time when possible and at runtime through Zod validation.
Example:
// api/client.ts
import { ApiClient } from '@tahanabavi/typefetch'
import { contracts } from './contracts'
export const client = new ApiClient(
{
baseUrl: import.meta.env.VITE_API_URL,
tokenProvider: async () => localStorage.getItem('token') ?? '',
},
contracts
)
client.init()
export const api = client.modulesTesting
TypeFetch is designed to be easy to test with mocked fetch.
Example:
global.fetch = vi.fn()
;(fetch as any).mockResolvedValueOnce({
ok: true,
json: async () => ({
id: '1',
name: 'Taha',
}),
})
const user = await api.user.getUser({
path: {
id: '1',
},
})
expect(user.name).toBe('Taha')Contract-driven API testing can also be run through the TypeFetch CLI
(@tahanabavi/typewire-cli).
npx typewire init
npx typewire test --mode schema
npx typewire test --mode mock
npx typewire test --mode live --base-url http://localhost:3000Generated reports can be exported as Markdown, HTML, or JSON:
npx typewire test --mode full --format markdown,json,html --output ./typefetch-report/reportRecommended test coverage:
Request validation
Response validation
Path parameter handling
Query string generation
JSON body serialization
Header merging
Auth token injection
Token provider behavior
Middleware execution order
Retry behavior
Timeout and abort behavior
Mock mode
Response wrappers
Error normalization
Encryption middleware
Instrumentation events (start / success / error)
Runtime overrides (mock / error / latency / schema swap)
Backend (NestJS)
The official companion package @tahanabavi/typefetch-nestjs ↗ lets you import the same contracts object on a NestJS backend to bind routes and validate request input and response output against each contract's Zod request/response schemas. One contract, end-to-end type-and-runtime safety — the route can't drift from the client.
Compatible with TypeFetch >= 1.6.0.
Installation
npm i @tahanabavi/typefetch-nestjs @tahanabavi/typefetch zodPeer dependencies: @nestjs/common, @nestjs/core, rxjs, reflect-metadata, and zod@^4.
Example
import { Controller } from '@nestjs/common'
import {
TypeFetchEndpoint,
ContractInput,
InferRequest,
InferResponse,
} from '@tahanabavi/typefetch-nestjs'
import { contracts } from './contracts'
@Controller()
export class UserController {
@TypeFetchEndpoint(contracts.user.getUser) // GET /users/:id from the contract
async getUser(
@ContractInput() input: InferRequest<typeof contracts.user.getUser>
): Promise<InferResponse<typeof contracts.user.getUser>> {
return { id: input.path.id, name: 'Taha' }
}
}Core API
| Export | Purpose |
|---|---|
@TypeFetchEndpoint(contract) | Binds the HTTP method and path from the contract and wires request/response validation. |
@UseContract(contract) | Validation-only decorator for retrofitting existing @Get/@Post routes. |
@ContractInput() | Injects the full validated, typed input, shaped exactly like what the client passed. |
@ContractPath() / @ContractQuery() / @ContractBody() / @ContractHeaders() | Inject an individual validated input section. |
InferRequest<T> / InferResponse<T> | Type helpers for a contract's input and output. |
TypeFetchModule.forRoot({ ... }) | Global options. |
getContractEndpoint(ctx) | Lets a guard read the contract, e.g. to honor its auth flag. |
Query and path strings are auto-coerced to the contract's declared types (numbers, booleans, dates, arrays), mirroring the client's URLSearchParams serialization. Validation failures return a RichError-compatible 400 ({ message, code, errors }), so the client's RichError surfaces field errors directly.
Mirrored features
The backend mirrors TypeFetch's client capabilities:
OpenAPI 3.0 / Swagger generation from your contracts (
setupContractSwagger,buildOpenApiDocument).Multipart / file-upload validation for
bodyType: "form-data"contracts.Response envelope matching the client's
setResponseWrapper({ success, data }/{ success, message }).Field-level encryption mirroring
encryptionMiddleware— decrypts request fields before validation, encrypts response fields after — byte-compatible viacrypto-js/node-forge.
Full documentation lives at @tahanabavi/typefetch-nestjs on GitHub ↗ and on npm ↗.