❌

Normal view

How fuzzy APIs are remaking the web

23 June 2026 at 10:00

For nearly as long as the web has existed, web development has wrestled mightily with the right way to connect components over the network. This is the question of the remote API. It influences every aspect of the software we build. We sort of arrived at a tolerable compromise with JSON APIs. While these have their limitations, you have to appreciate their underlying simplicity.

But the advent of AI-enabled endpoints that can mediate intent is changing the basic workings of the internet. This change is gradually reawakening an old dream, the service-oriented architecture (SOA). This time around, with luck, we’ll finally gain the flexible, discoverable, and maintainable automated service discovery we’ve longed for. Fingers crossed.

Why old-school SOA failed

Let’s call this burgeoning influence of AI on web architecture SOA 2.0.

To understand why SOA 2.0 is different from SOA 1.0, we have to remember the trauma of the 2000s. (This may be painful but also cathartic.) The original dream of SOA was beautiful: a world where disparate business services—inventory, billing, shipping, you name it—could automatically discover each other, understand capabilities, and orchestrate complex tasks without human intervention.

To achieve this, we built a monument to complexity. We had SOAP (Simple Object Access Protocol) for messaging, WSDL (Web Services Description Language) to define contracts, and UDDI registries for service discovery. At the center of it all sat the Enterprise Service Bus (ESB), a massive piece of middleware that was supposed to route everything gracefully, seamlessly. In case you young’uns are confused, that is all based on XML.

By the time you were done understanding the infrastructure well enough to know how to do something, you had forgotten what you set out to do.

It failed. It was egregiously heavy. Just to do some simple thing like create a “New Item” endpoint, you immediately had to begin scaling a wall of rigid definitions.

Because computers historically required absolute, deterministic perfection, if a single XML tag in a SOAP envelope was missing, or if a service updated its WSDL without every client re-generating its stubs, the entire multi-million-dollar pipeline would violently unravel. Some of us may be familiar with a similar challenge in containerized microservices (like Kubernates), where trying to determine where in the mesh a problem originated is… awkward.

Classic SOA was a house of cards, too brittle to survive the fuzzy reality of the internet.

The typical JSON API of today is a reaction against SOA. (It may be an overreaction.) We abandoned SOA for the relative simplicity of REST, giving up on the dream of autonomous service orchestration in exchange for manual integrations that just work.

The new intention-to-execution middleware

A sea change is already happening with app-level architecture. 

The effect of AI endpoints in an app’s service profile goes beyond just a new capability. It changes how the rest of the services work together. The overall effect is something like the app gaining an understanding of itself, and what it can do. This is not dissimilar to what WSDL was supposed to accomplish. But instead of a hard-coded descriptor, where some person had to keep what was available and what was described in sync, you now have a layer that can accept dynamically produced descriptors and unite them with fuzzy user intention and produce meaningful action.

You tie in AI endpoints to bridge between what the user is trying to accomplish, with the various strict capabilities available. These capabilities may exist within the app at the back end, at the front end, or at another service layer. The main thing is that there is a flexible AI layer that mitigates the need to hard-code the links between services.

In classic SOA, the contract was a rigid, unforgiving WSDL document. In modern common practice, the contract is a strongly coupled RESTful endpoint. In SOA 2.0, the contract has a hitherto unknown degree of flexibility, thanks to the natural language capabilities of an LLM.

When a user or a system expresses an intent—say, “Provision a new staging environment for the billing service”—the AI middleware doesn’t look for a hard-coded, point-to-point integration. Instead, it digests the intent and performs semantic routing, consulting a registry and selecting the appropriate tools. That registry, rather than a heavy UDDI, might be a vector database of available internal API endpoints, or a collection of available functions.

Modern LLMs equipped with function-calling capabilities act as the ultimate dynamic orchestrators. They read the JSON schema of a target REST API, understand its parameters, and dynamically map the user’s fuzzy, unstructured intent into a perfectly formatted JSON payload. If a field is missing, the LLM can either infer it from context or pause execution to ask the user for clarification.

The brittleness of SOA 1.0 is replaced by a shock absorber. If the target API changes a parameter name from customerID to clientId, the AI middleware can read the updated schema and adjust its mapping on the fly. No client code needs to be recompiled. No stubs need to be regenerated. The multi-million-dollar pipeline survives.

When software becomes smart

These are not just abstract ideas. I recently did my taxes, using a popular mainstream service that I will not name. I had several unusual and grumpy areas to deal with, including the new crypto regulations. It was not pretty.

But what I was most struck by was how dumb the software was, compared to the AI chatbot I was using to help guide me. I wanted to be able to tell the (stupid) software what I was trying to do. Such as “Carry my NOL from last year!” Or “I don’t know if I need a schedule K, you tell me!”

I don’t want another chatbot. I mean, I already have a good chatbot. I want the application to be well-integrated with AI services that understand the app, understand my current situation within the app, and meet me at the level of intent, applying the lessons learned by others who have used the same tools.

This kind of targeted, intelligent leveling up of intention is, from all I can see, the next stage of software development, and it is going to be massive.

Latency, non-determinism, and other challenges

We are trading the deterministic brittleness of classic SOA for the probabilistic fuzziness of SOA 2.0. And that trade is going to be demanded with ever more insistence by users. But it comes with a new set of trade-offs.

First, there is the latency tax. The old enterprise service bus was heavy to configure, but at run time, the messaging was just routed XML. Injecting an LLM into the critical path of an application adds hundreds of milliseconds, if not seconds, of latency. For asynchronous tasks or complex orchestrations, this is a welcome trade-off. For real-time, high-throughput microservices, it is a deal-breaker.

Second, there is the problem of non-determinism. We spent decades training ourselves (and our systems) to expect that given input A, a system will always produce output B. That deterministic equation was our bottom line faith. The intent layer doesn’t work that way. An LLM might route a request beautifully 99 times, then hallucinate a parameter on the 100th. Or it might choose an entirely different execution path based on a subtle shift in the user’s phrasing.

A third fly in the ointment is the so-called non-functional requirements, or NFRs. These are your pesky sidebar issues that refuse to be ignored, like security and reliability. 

Security concerns are magnified by model capabilities like function calling (or “function passing”). If you pair a user’s desires with what the AI can do, and you then let the AI decide, what happens next is clearly an act of faith unless guardrails are put in place. These guardrails must go beyond typical web security (i.e., make sure important function calls are hardened on the server, not exposed on the client) and must be internalized by the AI or (more likely) imposed from a layer outside the AI. There are a number of ways to do this, varying in degree of power and complexity. 

We certainly will continue to use standard practices (like RBAC and SSO) to enforce authentication. We will continue to implement standard authorization techniques (like OAUTH and JWT). But we will bring these to bear in the context of that intent layer and its capabilities.

Reliability is another challenge. For example, I recently hit a snag with Google’s Imagen API. Everything was working beautifully, then suddenly, some of the images stopped generating. There were no errors in the client or server logs; however, there were 500 errors in the network. Upon deeper examination, the prompting had morphed (between app context and user content) to include what the Imagen API rules deemed to be dangerous content. This was not obviously flagged prompting. It was fairly pedestrian creative writing, along the lines of “A dark, surreal, and glitchy cyberpunk landscape with menacing figures….” That kind of thing.

These are some of the ways that even simple, direct use of LLM APIs can surprise you. The question I am mulling is, what will be the unexpected outcomes on software writ large?

Dawn of a probabilistic web

Since its inception, the unpredictable, probabilistic nature of the internet came primarily from the humans using it (and background radiation flipping transistors, network failures, geopolitical effects on the ground, and the like). But AI-mediated APIs introduce an intentional, semantically controlled form of probability.

As developers, we will naturally discover the techniques that make consuming AI endpoints more effective. Here I am thinking about practices like structured responses and function calling. But the larger question is, what will the nature of software become?

In a world of binary states, strict protocols, and rigid URIs, if you send a GET request to a specific endpoint, you expect an exact, predictable response. We have spent the last 40 years treating the web like a vast, unimaginably complex state machine.

But as LLM-mediated APIs permeate our architecture with stochastics, the very fabric of the internet begins to change. By injecting AI into the routing and discovery layers, we are introducing a massive dose of probability into the foundation of our networks. When a request is no longer a hard-coded URI call but a natural language intent parsed by an LLM, the connection between node A and node B ceases to be a rigid wire. It becomes a weighted probability.

In essence, we are remaking the internet to mirror the architecture of the AI models we are deploying. Just as a neural network relies on the probabilistic firing of synapses rather than deterministic if/then statements, the next iteration of the web will rely on fluid, semantic discovery. Services won’t just “link” to each other; they will gravitate toward one another based on the conceptual proximity of their capabilities within a shared latent space.

This alters the character of software engineering. We lose (the illusion) of being entirely in control. Its strange paradox is that engineering using explicitly probabilistic components may make for a more resilient system. There is a longstanding debate about the best metaphor for software development. For the longest time, the construction of a building always seemed to be an apt analogy, or perhaps the mechanics of a vehicle. But these days, the gardening or cultivation metaphor is looking ever more relevant.

Despite the challenges posed by inserting AI in the stack, we are finally circling back to the original promise of the early 2000s. This time, fingers crossed, we are equipped with the right tools for the job.

We tried to build autonomous service discovery using rigid logic and deterministic XML, and it collapsed under its own weight. Now, we are building it with neural networks that understand the “intent” behind the integration. We are still building middleware, but instead of an enterprise service bus, we are building an enterprise reasoning bus.

The era of manually hard-coding every integration between every microservice may be coming to a close.

Designing frontend systems for cloud latency, not just cloud failure

17 June 2026 at 10:00

Frontend reliability is often discussed in terms of outages. Teams prepare for failed API calls, downtime and visible crashes because those failures are easy to recognize and measure. However, in many modern applications, the bigger challenge is not complete failure but latency. Systems rarely go fully offline. Instead, they become slow enough that users lose confidence in the interface long before anything technically breaks.

Most frontend engineers have experienced this in production. A page eventually loads, but only after several seconds of waiting. A save action succeeds in the backend, yet the interface remains unchanged long enough that the user clicks the button again. A dashboard renders immediately, but the critical data appears so late that the application feels unstable. In practice, users rarely distinguish between “slow” and “broken.” If an interaction feels uncertain or delayed, trust drops quickly.

As frontend systems become increasingly dependent on distributed cloud infrastructure, latency becomes a normal operating condition rather than an occasional exception. APIs may depend on multiple downstream services, serverless systems may introduce startup delays and state updates may propagate asynchronously across regions or caches. Frontend reliability therefore can no longer be defined only by uptime. It also depends on how clearly the interface behaves while waiting on slow cloud dependencies.

Reliability is more than preventing outages

Frontend teams traditionally define reliability around visible failure. A stable application is one that avoids blank screens, uncaught exceptions and broken API requests. While those issues still matter, they represent only one category of reliability problems.

In many production systems, the application remains technically available while still feeling unreliable to users. An API request that takes ten seconds may still return successfully. A delayed state update may eventually synchronize correctly. A dashboard may fully render after several refresh cycles. From a monitoring perspective, these systems appear healthy. From a user perspective, however, the experience feels inconsistent and unpredictable.

What makes this difficult is that many frontend applications are still designed around ideal conditions. Interfaces often assume fast responses, immediate consistency and stable network behavior. Cloud environments rarely behave with that level of predictability. Distributed systems naturally introduce latency, asynchronous updates and intermittent delays, even when everything is technically functioning correctly.

This changes how frontend engineers need to think about resilience. Modern interfaces are no longer designed only for success and failure. They also need to handle slow success, delayed feedback and partial availability in ways that remain understandable to users.

Slow APIs and the user experience problem

One of the most common latency issues frontend teams encounter is the slow API response. Technically, the request succeeds, but the delay changes how users interpret the application. A user waiting several seconds for a response rarely thinks about infrastructure behavior or distributed systems. They assume the interface is frozen, unresponsive or broken.

This becomes especially noticeable in cloud-connected systems where response times vary depending on caching layers, regional routing, serverless initialization or downstream dependencies. Frontend applications designed around consistently fast responses often struggle in these conditions because they treat latency as exceptional instead of expected.

Many applications still rely on generic loading spinners that provide little context beyond “something is happening.” While spinners technically indicate activity, they also communicate uncertainty. Users do not know whether the system is progressing, stalled or close to failure. As waiting time increases, frustration grows quickly.

More effective interfaces treat loading as part of the user experience rather than a temporary placeholder. Skeleton screens, layout-preserving placeholders and contextual loading feedback provide users with a clearer understanding of what is happening. Even relatively small decisions can make a noticeable difference. For example, users generally tolerate a dashboard chart loading later far more than they tolerate waiting several seconds before the navigation menu appears. Similarly, an e-commerce page that progressively loads recommendations often feels faster than one that blocks the entire interface until every service responds.

These patterns do not reduce actual latency, but they significantly improve perceived responsiveness. When users understand that the system is actively processing their request, they are far more tolerant of delays.

Delayed state updates and perceived instability

Another common issue in cloud-based systems is delayed state synchronization. A user submits an action successfully, but the interface does not immediately reflect the result. Even if the backend processes the request correctly, the delay creates uncertainty around whether the action worked at all.

This behavior is increasingly common in distributed architectures where writes may propagate asynchronously across services, regions or caches. Frontend applications that assume immediate consistency often expose these delays directly to users, leading to repeated clicks, duplicate submissions and confusion around application state.

In many cases, the real problem is not the delay itself but the absence of clear feedback. Users are generally willing to wait if the interface acknowledges that progress is occurring. Without feedback, even short delays begin to feel unreliable.

Frontend systems can reduce this uncertainty by designing around transitional states more intentionally. Pending confirmations, temporary UI updates and clearer progress indicators help maintain confidence while backend systems synchronize. Users care less about exact infrastructure timing than about whether the interface feels responsive and understandable throughout the interaction.

Progressive rendering and partial availability

Traditional frontend rendering strategies often wait for all required data before displaying meaningful content. While this simplifies state management, it also magnifies perceived latency by forcing users to stare at incomplete or empty screens.

Modern cloud systems rarely deliver all information simultaneously. Some requests complete quickly while others depend on slower downstream services. Treating the entire interface as blocked until every dependency responds unnecessarily increases the feeling of slowness.

Progressive rendering offers a more resilient alternative. Instead of waiting for the entire page, the application renders available content immediately while secondary information loads incrementally. A profile page may display account details before loading recommendations. A dashboard may render navigation and layout structure first while analytics populate later. This creates a sense of continuous progress rather than complete inactivity.

What makes this approach effective is that users tolerate delayed secondary content far better than delayed primary interaction. Even when total loading time remains unchanged, the interface feels significantly more stable because progress is visible.

Progressive rendering also changes how frontend teams think about dependency importance. Not every cloud service needs to block the entire user experience. Separating critical interaction paths from secondary content creates systems that remain usable even under imperfect network conditions.

Designing recovery, not just error messages

Latency-related problems are often made worse by weak recovery patterns. Many applications either wait indefinitely or fail abruptly with vague messaging such as “Something went wrong.” Neither approach creates confidence for users who are already uncertain about system behavior.

Frontend resilience improves significantly when interfaces guide users through delays and recovery intentionally. Instead of displaying generic failures immediately, applications can acknowledge that an operation is taking longer than expected while still preserving user progress. Retry actions, recoverable workflows and state preservation help users continue without restarting tasks entirely.

This becomes especially important in forms and transactional flows. Losing user input because of a temporary timeout often damages trust more than the delay itself. In practice, people are usually willing to retry an action. They are far less willing to repeat work they already completed once.

These recovery patterns are not simply UX enhancements. They are architectural decisions that recognize cloud latency as a normal operating condition rather than an edge case.

Designing for latency is designing for reality

As frontend systems become more dependent on distributed cloud infrastructure, latency becomes unavoidable. The most reliable interfaces are not necessarily the ones with the fastest response times. They are the ones that communicate clearly, preserve user confidence and remain usable during delays.

Frontend teams spend enormous effort optimizing rendering performance while often underestimating the impact of latency behavior on user trust. In many applications, the difference between a reliable experience and a frustrating one is not milliseconds of rendering speed but how clearly the interface handles waiting.

Frontend engineers do not need to become cloud infrastructure specialists to design for these realities. However, understanding that cloud latency is a normal part of modern application behavior fundamentally changes how interfaces should be built. Designing for latency is ultimately not about preparing for rare outages. It is about designing for the way modern systems behave every day.

This article is published as part of the Foundry Expert Contributor Network.
Want to join?

8 cutting-edge web development tools you don’t want to miss

9 June 2026 at 10:00

There is no ordained path. The hope that we were converging on some kind of consensus in web development has been eradicated by recent, ingenious developments that point in almost every direction. Yet, if there is a central theme uniting these efforts, it is the desire to mitigate the layers of liturgical embellishment that have grown up around the reactive canon. How can we look at things differently to attain the power that we need, without the heavy intricacy?

Here are eight cutting-edge web development tools that point the way.

Front-end maestro

If you put a bunch of classical musicians in a room together with sheet music and let them run, you might get to a cohesive piece—but you probably want a conductor, a maestro who coordinates all of the parts. That is Astro for your front-end frameworks.

Astro addresses the “hydration” of the front end, that is to say, the process of making the shell reactive. In conventional server-side rendering (SSR), like Next.js or Nuxt, the server not only sends the HTML, but also sends the massive framework runtime down the wire, just to attach event listeners to the page. Astro allows you to write components in React, Svelte, Vue, or Solid, and its compiler strips away all of the JavaScript before it reaches the browser. Astro ships zero JS by default, relying on its islands architecture to hydrate only the specific components that demand interactivity. 

Because Astro isolates interactivity into distinct islands, sharing complex state between those islands (e.g., a complex filtering sidebar communicating with a separate dynamic data grid) is fundamentally harder than it is in a monolithic single-page application. If you are building a highly interactive, dashboard-heavy app where every component affects every other component, Astro’s isolated islands might begin to feel more like a straitjacket than a liberation.

See also: Qwik. If Astro unbloats by stripping away the JavaScript entirely, Qwik unbloats by delaying it. Qwik delivers instant HTML and serializes the application state, downloading and executing only the JavaScript code required for a specific interaction at the exact millisecond the user clicks a button.

Biome: Lint like it’s 2026

Rust is gradually replacing the underlying infrastructure in the JavaScript ecosystem. But while Rust gives Biome its close-to-the-metal speed, Biome’s true calling card is its unification of the sprawling toolchain under a cohesive umbrella.

The .eslintrc and .prettierrc files and the dozen associated plugins can become a dark and unhappy bog in a project. Biome is the way out of the mire. It is a single, blazingly fast binary that replaces your entire tangled formatting and linting ecosystem, providing a path to code quality that doesn’t require a sprawling web of dependencies.

Probably the biggest drawback to Biome is that you lose the wide-open extensibility—which is exactly the same feature that makes Biome lean.

See also: Rspack. Biome cleans up the linting. Rspack unbloats the build step. Also built on Rust for speed, Rspack challenges the new “unbundled” esbuild-based dev mode championed by Vite and uses bundled dev mode.

Bun: Fast and integrated back-end JavaScript

Most cutting-edge JavaScript enthusiasts are already well-aware of Bun. For those who haven’t yet experienced Bun’s enthralling blend of one-stop shopping and blistering speed first-hand, it’s a virtually irrefutable must-try.

Fast is probably an understatement. If you are used to Node and you try out Bun, you will likely be immediately impressed with the speed at which commands execute. The Bun team has also made an extensive, multi-year effort to bring its engine into close compatibility with Node’s APIs. Overall, Bun is an extraordinary engineering effort that every JS developer should explore.

However, while Bun’s Zig-based engine is in most respects a drop-in for Node, it isn’t perfect, especially when considering the gargantuan landscape of Node packages out there. Node remains the conservative, happy-path engine for server-side JavaScript. 

See also: Deno. Although Bun has justifiably earned a reputation for bleeding-edge innovation, Deno has quietly pressed ahead with an appealing set of enterprise features like an integrated deployment platform and a front-end framework (Deno Fresh).

The Bun curious also may want to check out my interview with Bun creator Jared Sumner.

HTMX: Ajax KISS

If we are talking about clever ways to de-complexify the web, HTMX could reasonably be considered the poster-child. It takes the core mechanisms of the modern web client, like Ajax and partial updates, and turns them into simple HTML attributes. That means the state lives exclusively on the server, which is responsible for sending HTMX fragments.

Of course, there are trade-offs. Perhaps most unavoidable is the extreme dependence on the network. Because there is no client-side state machine, the browser will be orphaned and helpless without a connection to the server. That is, unless you get experimental with a local-first datastore.

Long story short: if your app falls into the realm of HTMX’s ability, HTMX is likely to be the most direct RESTful way to build it. And HTMX can in fact handle quite a lot.

See also: Hotwire. A collection of tools for building single-page-style applications using HTML over the wire, Hotwire has great features like page morphing, which can diff HTML instead of cold-loading it, with a simple import. True to classic “free as in speech” software culture, the HTMX and Hotwire projects freely exchange ideas. 

PowerSync: Data layer redo

Although the local-first data revolution that PowerSync represents implies a fairly serious engineering deep dive, its core proposal — to entirely reshape the way data moves in web architecture — is something a web developer needs to be aware of.

Usually, we create architectures that require a complex middleware to broker between a reactive client and the datastore. PowerSync proposes a radical alternative: bypass the middleman entirely by dropping a robust SQLite Wasm database directly into the browser.

The UI works on local data using familiar SQL, synchronously. Latency is zero. The dreaded loading spinner vanishes entirely. In the background, PowerSync automatically reconciles your local store with your central Postgres database. It handles the complex syncing algorithms and network drops, effectively making your application offline-first by default.

The catch, of course, is that local-first development forces a massive mental shift. You have to define data slices (similar to a view) that each client user holds. The PowerSync engine does most of the hard work, but things like schema migrations and conflict resolution (when two users edit the same record while offline) require a significantly steeper initial setup than a standard REST API.

See also: RxDB. RxDB is a slightly different flavor of local-first datastore. Whereas PowerSync relies heavily on Postgres, SQLite, and background daemons, RxDB provides a NoSQL, offline-first, reactive database that treats queries as observable streams, pushing UI updates the exact millisecond the local data changes.

RooCode: Use any AI you want

The beauty of RooCode lies in its ability to orchestrate whatever AI providers you have—for free. RooCode is an extension to Visual Studio Code that provides an AI manager layer. This layer bridges between the general abilities of the LLM and your code-specific, project-level structures.

RooCode is strong enough to be somewhat agentic in its capabilities. It doesn’t reach the powerhouse abilities of something like Cursor or Antigravity, but it is quite able to handle most small to medium-sized requests. And it does so with a minimum of unnecessary overhead. I find myself often using RooCode alongside my AI-assisted IDE to knock out lesser requirements, for less cost and without interrupting the flow of ongoing epics.

RooCode keeps you free of proprietary ecosystems. It allows you to plug in your own API keys—whether that is Claude, OpenAI, or even a local model running on your own hardware. 

The hidden tax of any AI coding assistant, however, is that it fundamentally shifts your job description from “writer” to “editor.” The unbloating of keystrokes can paradoxically lead to massively bloated codebases if developers blindly accept AI-generated boilerplate without actively reviewing its architectural impact. It is incredibly easy to let an agent spin up 500 lines of complex React when 50 lines of plain JavaScript would have done.

See also: Antigravity. RooCode is a lightweight extension that supercharges your existing environment. Google’s Antigravity is a custom-built editor designed from the ground up around AI, geared for agentic development workflows.

TanStack Query: Syncing made simple(r)

Even when client-side state management is addressed (see Zustand below), there is still a big, gaping hole in the plot: syncing across the server boundary. That is where TanStack Query steps into the breach.

Distributed computing is a notoriously thorny problem, and in fact our standard reactive model walks right into these thorns by holding the same state in two different places: on the client and the server.  Tanstack Query tries to make this inherent architectural friction as painless as possible by acting as an intelligent asynchronous layer. 

Instead of using a bunch of manual fetches tied to useState updates, along with fragile isLoading flags and complex state synchronization logic, TanStack Query abstracts the heavy lifting of API responses, background updates, and request deduplication into a few elegant hooks. You tell TanStack Query where to get the data, and it uses a pattern known as “stale-while-revalidate,” which means it will cache and reuse data on the front end (eliminating reload waits) and sync to the latest state in the background.

The catch, however, is that cache invalidation remains one of the hardest problems in computer science—and TanStack Query forces you to face it head-on. You will spend time thinking about “query keys” and deciding when a piece of data should be considered “stale.” No free lunches in software.

See also: SWR. While TanStack Query is an absolute powerhouse for complex data manipulation, SWR remains a champion of API minimalism, doing exactly what its name implies (stale-while-revalidate) with almost zero configuration.

Zustand: Minimalist state

If you have yet to encounter the monstrosity of large-scale state management in a reactive app, then spoiler alert: it can be nasty. Zustand proposes to dispense with the ceremonial boilerplate of reducers, providers, and unwieldy context wrappers in favor of a tiny, brutally simple global store.

Instead of forcing your entire application tree into a massive React context provider (sometimes leading to cascades of superfluous re-renders across the DOM), Zustand uses custom hooks to tie state directly to the specific components that need it. Zustand strives to achieve the specificity in the VDOM reactive model (instead of eliminating it entirely a la Signals).

You define a store, you call it, and the reactivity just works. It is an expression of the KISS philosophy applied to front-end architecture, scraping away the intricacies of Flux-like patterns. The trade-off for this liberation is the burden of discipline. Because Zustand is unopinionated, it won’t stop you from turning your global store into a cluttered junk drawer. You’ll need to impose your own conventions and guardrails to keep a large-scale project manageable.

See also: Jotai. If Zustand is the unbloated global store, Jotai is the unbloated atomic approach. Jotai manages state from the bottom up, calculating changes with surgical precision without triggering massive re-renders across the application tree.

New directions in web development

The most remarkable thing about these eight tools is that they deal in large part with alternative approaches that challenge the familiar. Although you may not be able to adopt them immediately, you will want to keep an eye on them. They are key factors that will continue to influence the shape of web applications and how we build them.

Angular Signals explained: How pull-based reactivity changes how we model state

4 June 2026 at 10:00

Angular’s introduction of Signals has generated both excitement and confusion. For many developers, Signals appear to be “simpler observables” or a more convenient way to trigger updates without subscriptions. Others attempt to map them directly onto familiar RxJS patterns, expecting emissions, operators, and event-style coordination.

Both interpretations miss the point.

Signals are not primarily an event system, and they are not designed to replace RxJS. They represent a different way of modeling application behavior, one that centers on current state and explicit dependencies rather than sequences of events. This distinction is subtle at first, but it has significant consequences for how applications are structured and reasoned about over time.

In a previous article, “Angular Signal Forms: From event pipelines to signal-driven state,” we reframed form behavior as a state-driven problem rather than an event-driven one. That shift raises an important follow-up question: what kind of reactive primitive is best suited for expressing state and derived behavior? To answer that, we need to understand Signals on their own terms, independent of any specific feature such as forms.

This article examines Angular Signals as a reactivity model rather than a convenience API. By clarifying what Signals are and, just as importantly, what they are not, we can better understand where they fit alongside RxJS and why they align so naturally with state-heavy problems such as form modeling.

Signals as a state primitive (not an event system)

To understand why Signals are a good fit for form modeling, it helps to be precise about what Signals are, and just as importantly, what they are not.

Signals are not an event system. They do not represent a sequence of things that happened over time. Instead, a signal represents a current value, along with a dependency graph that describes how other values derive from it. When a signal changes, Angular does not broadcast an event. It simply marks dependent computations as stale and reevaluates them the next time they are read. This is what we mean by fine-grained change detection control.

This distinction may seem subtle, but it has profound implications for how we reason about application logic.

Reactive streams encourage developers to think in terms of emissions. When something changes, subscribers are notified, operators transform the stream, and side effects occur in response. This model is extremely powerful for asynchronous workflows, but it introduces temporal reasoning even when time is not an essential concern. Developers must ask not only what the current state is, but how it arrived there and which emission triggered a particular piece of logic.

Signals, by contrast, encourage a declarative, pull-based model. A computed signal does not react to changes as they occur. Instead, it declares that its value depends on other signals. When those dependencies change, the computed value is simply recomputed the next time it is accessed. There is no notion of subscription order, missed emissions, or stale listeners.

This pull-based model aligns naturally with form state. At any moment, a form has a well-defined set of values. From those values, validity, error messages, and UI flags can be derived. These relationships do not depend on the sequence of changes that led to the current state. They depend only on the current state itself.

This is why Signals feel simpler when applied to state-heavy problems. They shift the developer’s focus away from orchestration and toward declaration. Instead of asking “What should happen when this changes?”, the question becomes “What does this value depend on?”

It is important to note that this does not make Signals a replacement for RxJS. Angular still relies on observables for asynchronous streams, external events, and integration with APIs that produce values over time. Signals and RxJS serve different purposes. In the context of forms, Signals are best used to represent state and derived state, while RxJS remains useful for asynchronous side effects and integration points.

By keeping this distinction clear, we avoid the trap of using Signals as a less expressive event system. Instead, we use them for what they do best: modeling state in a way that is explicit, deterministic, and easy to reason about.

Designing a signal-first form model with Angular Signal Forms

Before looking at any concrete implementation, it is worth clarifying what a “signal-first” form model actually implies. The goal is not to introduce a new abstraction that replaces Angular Forms, nor is it to hide form behavior behind another layer of indirection. Instead, the intent is to reorient how form state is represented and reasoned about.

In a signal-first approach, the form’s data model is treated as the single source of truth. Signals are used to represent that state directly, rather than mirroring it through control hierarchies or intermediary objects. The form itself becomes a projection over the state, attaching semantics such as validation, interaction metadata, and submission behavior without duplicating or owning the data.

This distinction is subtle but important. Traditional form models often encourage developers to think of the form as the container of state, with values flowing in and out through events. A signal-first model reverses that relationship. State exists independently of the form, and the form derives its behavior from that state. This makes it easier to inspect, reason about, and test form behavior, because the underlying data remains explicit and accessible.

The examples in this section are therefore intentionally minimal. They are not meant to demonstrate every feature of Angular Signal Forms, but to illustrate how a state-driven representation reshapes form architecture. The emphasis is on structure and intent rather than mechanics. More detailed implementation concerns, such as asynchronous validation, persistence, and UI composition, are explored in the following article.

Once we accept that form behavior is largely derived from state, the next question becomes how that idea is expressed in Angular itself. Angular’s Signal Forms API is a direct response to this shift in thinking. Rather than modeling forms as trees of controls emitting events, Signal Forms begin with a signal-backed model and layer form behavior validation, interaction state, and submission on top of it.

The starting point is still the same: a plain data model representing the values the form collects. In a signal-first approach, this model is wrapped in a writable signal and treated as the single source of truth. There is no duplication of state between the UI and the form model, and no need to synchronize multiple representations of the same data.

From this model signal, a form instance is created using Angular’s form() function. The role of this function is not to introduce a second state container, but to attach form semantics to an existing state object. The form instance provides structured access to fields, validation results, and interaction metadata, all of which are exposed as signals.

Validation is declared through a schema function passed to form(). This schema associates validation rules directly with specific fields in the model. Built-in validators such as required() and email() express constraints declaratively, and Angular automatically reevaluates them whenever the underlying values change. Validation results are not stored imperatively; they are derived and exposed through field-level signals such as invalid(), errors(), and pending().

This design is significant because it keeps validation aligned with the mental model established earlier. Validation rules do not “run” in response to events. They describe constraints on state. When state changes, derived validation state updates automatically, without subscriptions, listeners, or life-cycle hooks.

Angular Signals Form example

A minimal example illustrates the shape of this approach. The model remains a simple interface, and the signal holds the current form values.

interface RegistrationData {
  email: string;
  password: string;
  confirmPassword: string;
  acceptedTerms: boolean;
}

The form is then created by passing this model signal into form(), along with a schema that declares validation rules.

const registrationModel = signal({
  email: '',
  password: '',
  confirmPassword: '',
  acceptedTerms: false,
});

const registrationForm = form(registrationModel, (schema) => {
  required(schema.email, { message: 'Email is required' });
  email(schema.email, { message: 'Enter a valid email address' });

  required(schema.password, { message: 'Password is required' });
  required(schema.confirmPassword, { message: 'Please confirm your password' });

  required(schema.acceptedTerms, {
    message: 'You must accept the terms to continue',
  });
});

What matters here is not the syntax, but the structure. The model signal defines what the form is. The schema defines what constraints apply. Angular takes responsibility for deriving field state and exposing it through signals that the UI can consume directly.

Each field now has a clear, inspectable state. Whether a field is valid, invalid, touched, or pending is no longer inferred by tracing event streams or subscription chains. It is available as a signal, derived from the current model and the declared rules. This makes form behavior easier to reason about, test, and debug.

Just as importantly, this model scales naturally. Cross-field validation, such as checking that two password fields match, can be expressed declaratively using schema-level logic that reads from multiple fields. Form-level state, such as whether submission should be allowed, is derived rather than toggled imperatively. The form remains a projection of the state, not a controller of behavior.

I have avoided discussing templates or DOM integration here. The purpose of this section is to show that Angular’s Signal Forms align closely with the first-principles model introduced above. They do not replace that model; they formalize it.

In the next article in this series, we will connect this signal-first form to an actual Angular component. We will bind fields to inputs, render validation feedback using field state signals, and implement submission logic. This implementation will form the foundation of the GitHub example that accompanies this series and will be extended in later articles to cover asynchronous validation, persistence, and hybrid approaches.

The power of Signals

Angular Signals represent a deliberate shift in how reactivity is expressed within the framework. Rather than focusing on events, emissions, and coordination, Signals encourage developers to describe relationships between values. Computation becomes declarative, dependencies become explicit, and behavior becomes easier to reason about by inspection rather than reconstruction.

This does not diminish the role of RxJS. Event streams, asynchronous workflows, and integration with external systems remain essential parts of modern applications. Signals and RxJS solve different problems, and treating them as interchangeable inevitably leads to confusion. When each is used for what it does best — Signals for state and derivation, RxJS for coordination and side effects — the resulting architecture becomes clearer and more maintainable.

Viewed through this lens, the appeal of Signals is not novelty, but alignment. Signals map closely to how developers already think about state: as something that exists now, from which other values can be derived deterministically. This alignment reduces cognitive overhead, particularly in parts of an application where behavior is dominated by state rather than time.

With this understanding in place, we can now turn to practice. The next article applies these ideas to a concrete Angular example, showing how a signal-first approach reshapes form modeling, validation, and UI logic without reintroducing event-driven complexity.


❌