English for ts-rest Developers
Vocabulary for developers building type-safe REST APIs with ts-rest — contracts, client generation, and OpenAPI interop — for teams discussing typed REST endpoints in English.
ts-rest lets teams define a REST API’s shape once, as a TypeScript contract, and get a type-safe client, type-safe server handlers, and an OpenAPI spec all generated from that single source — without abandoning REST conventions for RPC. Because it deliberately keeps REST semantics (real HTTP verbs, real status codes) while adding type safety, review conversations mix REST vocabulary with contract vocabulary. Here’s the English you need.
Contracts
Contract — the single TypeScript definition of a REST API’s routes, methods, request bodies, and responses, which both server and client code are generated from or validated against.
“Don’t edit the route handler’s return type directly — update the contract first, and let the type error guide you to every place that needs to change.”
Route definition — a single entry in a contract describing one endpoint: its path, HTTP method, path/query parameters, body schema, and possible response shapes per status code.
“Add a new route definition for the export endpoint instead of overloading the existing GET /reports route with a query flag.”
Strict mode — a contract setting that rejects any response status code or shape not explicitly declared, catching undocumented behavior at compile time or runtime.
“Turn on strict mode for this contract — right now the handler can silently return an undocumented 500 shape and nothing catches it.”
Type-Safe Clients and Servers
Type-safe client — an API client automatically generated from the contract, so calling an endpoint with the wrong argument shape or reading a response field that doesn’t exist is a compile-time TypeScript error.
“You don’t need to check the docs for the response shape — the type-safe client already tells your editor exactly what fields exist.”
Router implementation — the server-side object that maps each contract route definition to an actual handler function, with ts-rest enforcing that every route in the contract is implemented.
“The build is failing because the contract added a new route and the router implementation hasn’t caught up yet — that’s expected, not a bug.”
Response shape per status code — the pattern of declaring a distinct response schema for each possible HTTP status code (200, 404, 422) a route can return, rather than one loose “response” type.
“Don’t collapse the 404 and 200 cases into one optional field — declare them as separate response shapes so the client can narrow on status code.”
OpenAPI Interop
OpenAPI generation — producing a standard OpenAPI/Swagger specification automatically from the contract, so external teams or tools that don’t use TypeScript can still consume accurate API docs.
“We stopped hand-writing the Swagger file — it’s generated from the same contract the TypeScript client uses, so the two can’t drift apart.”
Schema validation — runtime checking (usually via Zod) that incoming requests and outgoing responses actually match the contract’s declared shapes, not just at compile time.
“That malformed request didn’t get caught until production because schema validation wasn’t wired into the handler — the contract only guaranteed compile-time safety, not runtime.”
Common Mistakes
- Saying “the types don’t match” without specifying whether the mismatch is in the contract, the router implementation, or the generated client — each points to a different file.
- Assuming compile-time type safety from the contract also guarantees runtime validation, when schema validation must be explicitly wired into the server.
- Forgetting that OpenAPI generation and the TypeScript client are both derived from the same contract, then manually editing the generated spec and having it silently overwritten.
Practice Exercise
- Explain, in two sentences, why ts-rest can generate both a typed client and an OpenAPI spec from a single contract.
- Write a short PR description for adding strict mode to an existing contract that was returning undocumented error shapes.
- Draft a code review comment explaining why a 404 response should be its own declared shape rather than an optional field on the 200 response.
Related Resources
- English for TypeScript Developers
- English for API Design Reviews
- English for GraphQL Persisted Queries
Navigating Nuance: Handling Feedback & Collaboration
Let’s be honest – even experienced developers from native English-speaking backgrounds struggle with the precise language needed to communicate effectively about technical details. When working on a project using ts-rest, clear and concise communication isn’t just polite; it directly impacts code quality, reduces misunderstandings, and streamlines collaboration. It’s not enough to simply say “this doesn’t work.” You need to articulate why it doesn’t work, and propose a solution in a way that’s easily understood by the entire team. This is especially critical when discussing contracts and how they relate to the generated client code.
A common scenario arises during a code review. Imagine receiving this comment on a pull request: “This endpoint isn’t really reflecting the contract; it’s missing the minLength validation.” Now, a less polished response might be something like, “Fixed it.” But that doesn’t explain why the change was made or how it aligns with the design. A better approach would be: “I’ve added the minLength validation to the request body schema as per the contract definition for /users/{id}. This ensures the endpoint enforces the specified data type and length requirements, preventing invalid data from being processed. I’ve also updated the generated client code to reflect this change, ensuring consistency across our API.” Notice the emphasis on why the change was necessary and how it relates back to the original contract. Similarly, in a Slack channel discussing potential improvements to an OpenAPI definition, you might hear someone suggest, “Let’s make the response a little more detailed.” That’s vague. A useful follow-up would be: “Could we specify the fields returned by default and their data types explicitly? This will improve documentation for our consumers of the API and reduce potential ambiguity.”
Another critical area is crafting clear pull request descriptions. Rather than just saying “Implemented endpoint X,” a good description explains what was implemented, why, and how it fits into the broader architecture. For example: “Implemented the /products/{id}/reviews endpoint to allow clients to create new reviews for products. This aligns with the API contract defined in product-review.yaml, ensuring consistent data validation and generation of client code for review creation. The endpoint uses a POST request with a JSON body conforming to the schema, which is automatically generated by ts-rest based on the contract.”
Here’s an example of how you might use ts-rest to generate a basic OpenAPI definition from a TypeScript contract:
import { Contract } from 'ts-rest';
const myContract = new Contract({
path: '/users',
methods: [
{
method: 'GET',
description: 'Retrieves a user by ID.',
params: [{ name: 'id', type: 'string' }],
responses: {
200: { description: 'User found.' },
404: { description: 'User not found.' }
}
}
]
});
myContract.save('users.openapi.ts');
This example illustrates how the contract directly informs the OpenAPI definition, demonstrating a core principle of using ts-rest for improved clarity and consistency. Focusing on these precise phrases – “as per the contract,” “enforces data types,” “reflects the design” – will dramatically improve your communication and collaboration when working with type-safe REST APIs.