Frontend engineering
Building a Typed API Layer with TypeScript
How a reusable TypeScript API layer can separate transport details from product screens, unify errors, support pagination, and make React and React Native features easier to maintain.
Why screens should not call fetch directly
Direct fetch calls inside screens look fast at the beginning, but they spread authentication, language headers, base URLs, parsing, timeout behaviour, and error handling across the application. When the backend changes, every screen becomes a separate integration point.
A typed API layer creates one place for transport concerns. Screens ask for product operations such as listCars, chooseCar, analyseText, or loadCases instead of rebuilding HTTP rules every time.
Start with one request primitive
The request primitive should know how to build the URL, attach headers, serialise the body, parse the response, and convert failures into one application error shape. It should stay small enough to understand and flexible enough for JSON, form data, and authenticated requests.
- Add authentication and locale headers centrally.
- Return typed data rather than raw Response objects to feature code.
- Include endpoint, status, and safe backend details in normalised errors.
- Support cancellation so abandoned screens do not keep unnecessary work alive.
Model contracts instead of guessing shapes
TypeScript is most useful when the type represents a real contract. Required, optional, and nullable fields should match the API instead of being widened until the compiler stops complaining. Request payloads and response payloads should be separate types because they often evolve independently.
When an API has legacy inconsistencies, I normalise them at the boundary. The component should receive one dependable model rather than branches for every historical response shape.
Keep server state outside local UI state
Libraries such as TanStack Query are effective when the API layer remains independent. The API function performs the operation; the query layer handles caching, invalidation, retries, stale time, and request lifecycle; the component renders the product state.
This separation also supports React Native and web applications sharing contracts without forcing both platforms to share presentation code.
Pagination and search belong in the contract
Server-side selects, searchable tables, and infinite lists need explicit query parameters and metadata. I model page, perPage, search, filters, total, and next-page information instead of storing them as unrelated component variables.
A predictable contract makes it easier to debounce search, reset pagination when filters change, prefetch the next page, and show accurate empty or end-of-list states.
Related project work
Case studies connected to the decisions in this article.
Real Estate Platform
Hompper
A bilingual real estate discovery platform for buyers and renters across Syria, combining property listings, agents, map browsing, and direct contact actions in one responsive experience.
View case studyMedical Education Platform
IASS
A bilingual digital academy for aesthetic medicine, bringing courses, medical books, articles, institutional content, and API-driven learning catalogs into one structured platform.
View case studyAI Education Product · Freelance
AI Detektor
A Swedish AI-text detection product for schools, designed around a focused analysis workspace, authenticated access, clear risk signals, and practical evidence for teacher-led review.
View case studyContinue reading