ts-rest Core

End-to-end type-safe REST API contracts in TypeScript, with an RPC-like client and zero code generation.

Library
npm
v3.52.1
3,338stars
MIT License

Repository Health

Pre-computed score based on development activity, maintenance, community, maturity, and trend momentum.How we score it →
53/100Fair
Development Activity4
Maintenance48
Community64
Maturity56
Momentum40

Technical Analysis

AI-assessed by reading the actual repository — architecture, code quality, innovation, and documentation.How we score it →
80/100Excellent
Architecture84
Code Quality82
Innovation78
Learning Curve75

@ts-rest/core is the foundation of ts-rest, a library for defining a single shared contract for a REST API that both server and client can consume with full type safety. Instead of generating code from an OpenAPI spec or hand-writing duplicate types on both ends, you describe each route once — its path, method, params, query, headers, and typed responses — using either plain TypeScript types or a runtime validation library, and ts-rest infers everything else.

The package ships the contract DSL (initContract, router, query, mutation), a fetch-based RPC-like client that calls plain REST endpoints under the hood, and the type-inference machinery that turns a contract definition into fully typed client and server function signatures. As of the Standard Schema adoption, validation is no longer tied to Zod: any Standard Schema-compliant library (Zod, Valibot, ArkType, and others) can be used to validate params, query, body, and headers, with a legacy compatibility layer for pre-Standard-Schema Zod versions.

@ts-rest/core is consumed directly by application code and also underpins the rest of the ts-rest ecosystem — framework adapters like @ts-rest/express, @ts-rest/nest, @ts-rest/fastify, and @ts-rest/next, plus client-side integrations for React Query, Vue Query, and Solid Query, and an @ts-rest/open-api generator that derives OpenAPI documents from the same contract.

What You Get

  • A contract DSL (initContract, router, query, mutation, responses) for declaring routes, path params, query params, headers, and per-status-code response shapes in one place
  • A fetch-based RPC-like client (initClient) that turns a contract into a fully typed function per route, so calling an endpoint looks like calling a local async function
  • Type-inference utilities (ClientInferRequest, ClientInferResponses, ServerInferRequest, ServerInferResponses) that derive request and response types directly from the contract for both client and server code
  • Standard Schema support so validation can be powered by Zod, Valibot, ArkType, or any other Standard Schema-compliant library, with backward compatibility for legacy Zod schemas
  • Support for strict status codes, custom content types (JSON, multipart form data, URL-encoded), and per-route metadata and deprecation flags
  • A pluggable ApiFetcher interface so the underlying HTTP call can be swapped out (custom fetch wrappers, interceptors, alternate runtimes) while keeping the same typed calling convention

Common Use Cases

  • Sharing one contract file between a Node.js/TypeScript backend and frontend so both stay in sync automatically as routes change, with type errors surfacing at compile time instead of runtime
  • Adding end-to-end type safety to an existing REST API incrementally, route by route, without rewriting the API as GraphQL or a new RPC protocol
  • Building typed SDKs for a public REST API that consumers can import directly, getting autocomplete and type checking for every endpoint
  • Generating OpenAPI documentation from the same contract used to implement and call the API, via the companion @ts-rest/open-api package
  • Wiring a typed contract into framework-specific servers (Express, Fastify, NestJS, Next.js, serverless handlers) via ts-rest’s adapter packages while reusing @ts-rest/core’s contract and type-inference layer

Under The Hood

Architecture @ts-rest/core is organized around a small set of composable modules under src/lib: dsl.ts defines the contract shape (AppRoute, AppRouter) and the initContract() builder that recursively applies base headers, path prefixes, and common responses across nested routers; client.ts turns a contract into a typed proxy object via initClient, evaluating each call’s arguments into fetch parameters through evaluateFetchApiArgs and fetchApi, with a swappable ApiFetcher for custom transport; standard-schema-utils.ts and standard-schema.ts isolate all validation-library-agnostic logic behind the Standard Schema interface, including a runtime polyfill for legacy pre-Standard-Schema Zod versions so older contracts keep working; and infer-types.ts provides the conditional-type machinery that derives client/server request and response types purely from the contract, without any runtime code generation. The separation between contract definition, type inference, and client transport means the same core package underpins very different consumers (a fetch-based client, Express/Nest/Fastify servers, React Query hooks) without duplicating logic — each adapter package composes these primitives rather than reimplementing them.

Tech Stack The package is pure TypeScript (target ES2019, strict mode with strictNullChecks on) with zero required runtime dependencies — zod is only a type-only import used for legacy contract typing, and @types/node is an optional peer dependency. It’s built and tested inside an Nx monorepo (ts-rest/ts-rest) alongside its many companion packages, using pnpm workspaces, Jest for unit tests, and Changesets for versioned releases across the whole workspace. The package has no bundler-specific build magic of its own beyond the shared Nx TypeScript build target, keeping its output a plain, dependency-light library suitable for both browser and Node.js consumers.

Code Quality The core package has extensive unit test coverage — a .spec.ts file sits alongside nearly every source module (client, dsl, fetch, infer-types, paths, query, response-error, standard-schema-utils, type-guards, type-utils, zod-utils), exercising both the runtime helpers and, via Expect/Equal type-level test helpers, the inferred TypeScript types themselves. Error handling is explicit and typed, with dedicated error classes (ResponseValidationError, UnknownStatusError, StandardSchemaError) rather than generic thrown strings. CI (build-test.yml) runs lint, test, and build via Nx’s affected-graph targeting on every PR and push to main, plus a separate end-to-end test job, and the workspace ships its own ESLint configuration enforcing consistent style across all packages.

What Makes It Unique Unlike code-generation-based typed-API tools, ts-rest infers types purely from a TypeScript contract object with no build step, codegen CLI, or .proto/schema files to keep in sync — the contract is just TypeScript you import directly. Its adoption of the Standard Schema specification decouples it from any single validation library, letting Zod, Valibot, and ArkType users share the same contract format, and its legacy-Zod polyfill layer shows a deliberate, tested approach to backward compatibility rather than a breaking cutover. Because the contract and client live in @ts-rest/core independently of any server framework, the same typed contract can be implemented against Express, Fastify, NestJS, Next.js, or a serverless handler, and consumed by a plain fetch client or a React/Vue/Solid Query hook, all through thin adapter packages rather than framework lock-in.

Join founders buildingwith open source

Opinionated takes, migration guides, cost-saving tips, and insights from the open source ecosystem.

Subscribe on Substack
Join 750+ subscribers

Search