
At TCTF, the OpenAPI spec is not documentation — it is the contract. We write the spec before writing code, generate TypeScript types from it, build a shared API client around it, and auto-generate our developer portal from it. The result: integration bugs caught at compile time, not in production.
Every team that has built a frontend and a backend has experienced the integration bug. The backend renames a field, the frontend does not know, and the feature breaks in production. The backend adds a required parameter, the frontend sends the old payload, and users see a 400 error. These bugs are preventable — not with better communication, not with more meetings, but with a contract that both sides must honor. At TCTF, that contract is the OpenAPI specification. We write the spec before writing a single line of implementation. We generate TypeScript types from the spec. We build a shared API client from the spec. We auto-generate our developer portal from the spec. The spec is the single source of truth, and everything else is derived from it.
Spec-first development inverts the typical workflow. Instead of building the backend, then documenting the API, then telling the frontend team what the endpoints look like, we start with the OpenAPI specification. The spec defines every endpoint, every request schema, every response schema, every error code, and every authentication requirement before anyone writes implementation code.
This approach forces design decisions upfront. What should the endpoint path be? What fields are required versus optional? What does the error response look like when validation fails? What HTTP status codes map to which conditions? These questions are answered in the spec, reviewed by both frontend and backend developers, and agreed upon before implementation begins.
The spec review is a design review. Frontend developers can see exactly what data they will receive and flag issues early — missing fields, awkward naming, pagination that does not match the UI requirements. Backend developers can see exactly what the frontend expects and design their data layer accordingly. The conversation happens over a YAML file, not over a broken integration in staging.
The practical workflow is straightforward. A developer creates a branch, writes or modifies the OpenAPI spec, opens a pull request, and both frontend and backend reviewers approve the spec changes. Only after the spec is merged does implementation begin. The spec is the blueprint, and the code is the construction.
This sounds slow, but it is actually faster. The time spent writing and reviewing the spec is time saved debugging integration issues, rewriting frontend code to match backend changes, and hotfixing production because the contract was ambiguous. The spec is an investment in clarity that pays dividends throughout the development cycle.
📝Write the spec before writing the code. Review the spec with both frontend and backend developers. The conversation happens over a YAML file, not over a broken integration in staging.

The OpenAPI spec is YAML. TypeScript is not YAML. The bridge between them is code generation. We use tooling that reads the OpenAPI specification and generates TypeScript interfaces for every request body, response body, query parameter, and path parameter defined in the spec. These generated types are the same types used in Lambda handlers, the API client, and React components.
When a backend developer implements a Lambda handler, the function signature uses the generated request and response types. The TypeScript compiler ensures the handler returns exactly the shape defined in the spec. If the spec says the response includes a createdAt field of type string, the handler must include it. If the handler tries to return an extra field not in the spec, the compiler flags it.
When a frontend developer calls an API endpoint, the API client method returns the generated response type. The TypeScript compiler ensures the component handles the response correctly. If the spec says the response includes a pagination object with totalCount and pageSize, the component can access those fields with full autocomplete and type checking.
The full-circle benefit is that a field rename in the spec propagates to every layer automatically. Rename userId to memberId in the spec, regenerate the types, and the TypeScript compiler immediately flags every Lambda handler and every React component that references the old field name. The rename is complete when the build passes — no grep, no find-and-replace, no missed references.
This is the TypeScript-everywhere payoff. The same language on the frontend, the backend, and the infrastructure means the same type system connects all three layers. The OpenAPI spec is the source, TypeScript types are the enforcement mechanism, and the compiler is the auditor that ensures everyone honors the contract.
🔄Generate TypeScript types from the OpenAPI spec. The same types flow through Lambda handlers, the API client, and React components. A field rename in the spec propagates to every layer — the compiler catches every reference.

The shared API client package is a workspace package that both frontend applications — tctf-main and tctf-social-network — import. It is generated from the OpenAPI specification and provides typed methods for every endpoint in the platform. Frontend developers never write fetch calls, construct URLs, or parse response bodies manually. They call typed methods and get typed responses.
The API client handles the concerns that individual fetch calls would need to handle repeatedly: authentication token injection, request header management, error response parsing into typed error objects, automatic retry with exponential backoff for transient failures, and request/response transformation (converting between the API's snake_case and the frontend's camelCase, for example).
Each endpoint in the OpenAPI spec maps to a method in the API client. The getUser endpoint becomes apiClient.users.getUser({ userId }), which returns a typed Promise with the user response shape. The createProject endpoint becomes apiClient.projects.createProject({ body }), where the body parameter is typed to match the request schema. Autocomplete guides the developer through the available methods and required parameters.
Error handling is also typed. The API client parses error responses into typed error objects that match the platform's standardized error format. A ValidationError from any of the 34 backend services is parsed into the same TypeScript type, with typed access to field-level error details. The frontend error handling code does not need service-specific logic.
The shared API client is the frontend's interface to the entire backend platform. It is generated, not hand-written, which means it is always in sync with the spec. When a new endpoint is added to the spec, the API client gains a new method automatically. When an endpoint is deprecated, the method is marked as deprecated in TypeScript, and the compiler warns any code that still uses it.
📦shared API client — a generated, typed API client shared by both frontend apps. No manual fetch calls, no URL construction, no response parsing. Every endpoint is a typed method with full autocomplete.

API documentation that is written separately from the API is documentation that lies. It starts accurate, drifts over weeks, and becomes actively misleading over months. The only documentation that stays current is documentation generated from the same source as the code.
Our developer portal is generated automatically from the OpenAPI specification and published to the CSF portal. Every endpoint has its path, method, description, request schema, response schema, error codes, authentication requirements, and example payloads — all derived from the spec. When a developer adds a new endpoint to the spec, the portal updates automatically on the next build.
The portal serves both internal and external audiences. Internal developers use it to understand endpoints they have not worked with before. External partners use it to integrate with TCTF's APIs. Both audiences see the same documentation, which means there is no discrepancy between what internal developers know and what external partners are told.
Example payloads are particularly valuable. Each endpoint in the spec includes example requests and responses that demonstrate the expected data shapes. These examples are validated against the schema during the build — if an example does not match the schema, the build fails. This means the examples in the documentation are always valid, always current, and always consistent with the actual API behavior.
The auto-generated portal eliminates an entire category of work: documentation maintenance. No one needs to remember to update the docs when an endpoint changes. No one needs to review documentation PRs for accuracy. The spec is the documentation, and the portal is just a rendered view of the spec.
📚API documentation generated from the OpenAPI spec, published automatically, validated on every build. The docs are never stale because they come from the same source as the code.

Before adopting spec-first development, every API change was a potential integration bug. A backend developer would rename a field, update the handler, and merge the PR. The frontend would not know about the change until the next deployment, when the UI would break because it was still referencing the old field name. The bug would be caught in testing if we were lucky, or in production if we were not.
Spec-first development eliminates this class of bugs entirely. The workflow is: change the spec, regenerate the types, and the TypeScript compiler immediately flags every piece of code — frontend and backend — that references the changed field. The integration bug is caught at compile time, not at runtime, not in staging, not in production.
The protection extends beyond field renames. Adding a required parameter to a request body? The compiler flags every API client call that does not include the new parameter. Changing a response field from optional to required? The compiler flags every component that has a null check that is no longer necessary. Removing an endpoint? The compiler flags every call to the removed method.
The spec also prevents a subtler class of bugs: assumption drift. Without a spec, frontend and backend developers develop independent mental models of the API. The backend developer thinks the pagination parameter is called page. The frontend developer thinks it is called pageNumber. Both are reasonable assumptions, and neither is checked until integration. With a spec, the parameter name is defined once and used everywhere.
The quantitative impact is significant. Before spec-first, integration bugs were a regular occurrence — multiple per sprint, each requiring debugging time, a fix, and a redeployment. After spec-first, integration bugs are near zero. The spec is the contract, the types are the enforcement, and the compiler is the judge. If the build passes, the integration works.
🛡️ Before spec-first: integration bugs every sprint. After spec-first: near zero. The spec is the contract, the types are the enforcement, and the compiler is the judge.
The OpenAPI specification is the most underrated tool in a full-stack TypeScript project. It is not just documentation — it is the contract that keeps frontend and backend honest. Write the spec first, generate types from it, build the API client from it, generate the docs from it, and let the compiler enforce it. Integration bugs become compile errors. Documentation stays current. And every developer — frontend and backend — works from the same source of truth.
Never miss a post
Subscribe to get the latest TCTF articles delivered to your inbox.