Building AI Applications with TypeScript and React: Frontend and Backend Efficiency

Building AI applications is not just about connecting an API and rendering text. It is about managing uncertainty. Responses arrive in chunks, data structures change, and errors behave very differently from those in a conventional backend. TypeScript and React, when combined effectively, provide one of the most robust approaches to addressing these challenges. In this article, we explore the key obstacles developers face when building AI-powered applications and how to tackle them.
The Typing Challenge When AI Generates Non-Deterministic Data
Language models do not always return the same structure. A response may include optional fields, empty arrays, or free text where you expected an object. Without proper typing, these situations can become silent production errors.
TypeScript forces you to define explicit contracts between the model and the application. For LLM responses using structured outputs or tool calls, combining Zod for runtime validation with types derived through z.infer<> covers most real-world scenarios.

This approach does not eliminate the unpredictability of the model, but it contains it. If the response does not match the expected schema, the error becomes explicit and manageable instead of turning into an "undefined is not a function" issue several layers deeper in the application.
Managing State in React When the Data Source Is an AI Model
Token streaming challenges the traditional “request → response” mental model. State is no longer binary (loading or ready); instead, it includes multiple stages such as connecting, receiving data, completed, cancelled, and partial error.
For this type of state machine, useReducer is often a better fit than useState:

When it comes to cancellation, the Streams API combined with AbortController is the standard approach. TanStack Query adds value in workflows that require caching or revalidation. However, for pure streaming use cases, managing state locally can often be simpler. The key is to model all intermediate states explicitly instead of collapsing them into a single isLoading boolean.
End-to-End Frontend and Backend Architecture with TypeScript
When the backend is also written in TypeScript, sharing types between client and server eliminates an entire category of bugs. Two common approaches are:
- Monorepo with a shared package: A
packages/typesmodule imported by both frontend and backend applications. Simple and effective. - tRPC: Endpoints are defined as typed server functions, while the client consumes them with full type inference and autocomplete support, without the need for code generation.
In projects with multiple AI integrations, where contracts between layers evolve frequently, end-to-end type consistency drastically reduces debugging time. A change in the model response type surfaces as a compilation error long before it reaches production.
Using NestJS as a Backend for AI Models and Integrations
NestJS brings structure to projects that can otherwise become difficult to manage. Its dependency injection system makes it possible to encapsulate the logic of each provider (such as Anthropic, OpenAI, or custom models) into interchangeable services.

This makes AI services independently testable and decoupled from the rest of the application. Conversation context management, memory handling, and prompt templates fit naturally into this architecture. A ConversationService can maintain message history, a PromptBuilder can generate the final prompt, and an HTTP controller can orchestrate the entire flow.
Testing React Components That Consume AI Services
Mocking an LLM is not optional during testing. Real responses are slow, variable, and costly. The most effective strategy is to separate testing by layers:
- Unit tests: Mock the AI service with predefined responses. Verify that components behave correctly when faced with slow responses, rate-limit errors, or unexpected content.
- Integration tests: Use a mock server such as MSW to simulate token-by-token streaming.
- End-to-end tests with Playwright: Intercept real API calls and replace them with fixtures, allowing complete workflow validation without depending on model availability.
The most important scenarios to test are not the happy paths but rather how the interface behaves when a response takes eight seconds to arrive, arrives partially, or never arrives at all.
Performance in Real-Time and Streaming Interfaces
Rendering every incoming token as an individual React re-render is one of the fastest ways to create a sluggish user experience. Several optimization techniques can make a significant difference:
- Update batching: Accumulate tokens in a buffer and update state every ~100ms instead of on every token received.
- useDeferredValue: Separate input updates from content rendering updates.
- Virtualization: In long conversations, tools such as React Virtual or TanStack Virtual prevent messages outside the viewport from being rendered.
- Selective memoization: Use
React.memofor historical messages that will not change.
Performance bottlenecks are rarely caused by JavaScript execution itself. More often, they result from React re-rendering larger portions of the component tree than necessary.
TypeScript and React for AI Modernization Projects
This combination is not limited to greenfield projects. In legacy application modernization initiatives, TypeScript acts as a safety net. It enables teams to introduce AI modules incrementally without disrupting the existing system. Types document implicit contracts that previously existed only in the minds of the developers who built the original code.
When combined with other modernization practices (such as automatic API documentation, Power Automate workflows and Playwright testing) end-to-end TypeScript becomes the connective tissue that allows all components of the solution to work together seamlessly.
If you are evaluating how to introduce AI into your technology stack or modernize an existing application, contact us. We can perform an initial assessment to identify the most suitable entry points for your specific scenario.