Integrating Web Frontends with API Backends using the Actor Model
TL;DR: One thing I took away from my years writing Erlang is its knack for scaling and decoupling processes through the message-passing…
Integrating Web Frontends with API Backends using the Actor Model
TL;DR: One thing I took away from my years writing Erlang is its knack for scaling and decoupling processes through the message-passing Actor model. In the browser-and-server world, the integration between the front end and the back end is almost always handled via RESTful APIs. In a large enterprise, there’s usually a frontend team building the browser UI and a backend team building the server APIs, and the two must agree on which collection or entity each URI maps to, in a CRUD-ish fashion, using HTTP GET, POST, PUT and DELETE. That’s the textbook approach, and it works often enough. But I’ve run into a fair share of grief that traces back to a misreading of the RESTful methodology, particularly the HTTP PUT method. In this article I’ll sketch an alternative interface built on the Actor model, and make the case for treating the wire between front and back ends as a stream of messages rather than a set of CRUD operations.
Before the dial tone: a word about the telephone exchange
Here’s a question worth sitting with before we touch a single line of JavaScript: when you pick up a phone and dial a number, how much do you actually know about what happens next?
Essentially nothing, and that’s the point. You express an intent (“connect me to this number”), and the exchange does the rest, i.e. it works out the routing, finds a path across the trunk lines, handles the busy tones, meters the call for billing, and tears the whole thing down when you hang up. You don’t know its wiring; you don’t want to. Now picture the world it replaced: a human operator sat in front of a switchboard, and a call got connected because somebody who knew that board physically patched a cable from one jack to another. The caller’s intent was identical in both eras; what changed was who had to understand the machine.
I raise this because the modern automatic exchange is more or less where Erlang was born. Ericsson built Erlang in the 1980s to run telephone switches that simply were not allowed to fall over, and the model they reached for, independent processes passing messages, turned out to be a general-purpose way of building systems that decouple cleanly and scale sideways. Hold that image of the exchange; we’ll keep coming back to it, because the argument of this whole piece is that too many web frontends are still wired up like the old manual switchboard when they could be dialling a number instead.

A good interface lets the caller state intent and forget the wiring; a leaky one forces the caller to learn the switchboard.
So how did we get to REST?
The HTTP/REST protocol originated in Roy Fielding’s PhD dissertation in 2000. The idea was to set down a standard architectural style for web software, with a handful of constraints that buy you scalability. One of the things that fell out of it was the RESTful Web Services style, where a URI endpoint stands in for a resource (an entity, or a collection of them), and the HTTP verbs carry the semantics: GET retrieves the resource or collection state, POST sends state data to a resource for processing, PUT creates or replaces the named resource with the supplied state, and DELETE removes it.
That’s the backbone of RESTful client/server interaction. In practice, the team picks the URIs and abstracts the conversation into something CRUD-shaped. Take a video library: videos and members are collection resources, and the singletons sit underneath them, e.g.
GET /videos : retrieve the list of videos
GET /videos/{video-id} : retrieve a single video
GET /members : retrieve the list of members
GET /members/{member-id} : retrieve a single member
To create a new title we assemble a JSON map representing the resource:
{
"video-id" : "00001",
"video-title" : "Star Wars 1",
"language" : "English",
"genre" : "Sci-Fi"
}
…and POST it to the collection URI, POST /videos. Members get created the same way against /members. PUT swaps out a named singleton with whatever JSON we hand it.
And here’s the genuinely nice part of Fielding’s design, the part worth keeping: the client has no idea what the origin server does for storage. Could be a SQL database; could be a flat file; could be carrier pigeons. The resource interface hides all of it. So far, so good.
But somewhere along the way we lost the plot. In an age of decoupled microservices and SPA frontends (Angular, React, take your pick), I still see teams bolt the frontend tightly to the backend; worse, I see business logic creep into the frontend so it can drive the backend CRUD-style, PUT-ing here and POST-ing there, which means the frontend has to know and care about the backend’s data schemas. We are humans after all, and the deadline is always nearer than the spec, so we reach for whatever verb is closest to hand. Hopefully this article helps a few developers spot the trap before they’re standing in it.
REST gave us a clean resource interface; we then quietly handed the backend’s schema back to the frontend and called it integration.
Where the REST contract starts to leak: the trouble with PUT
So which verb causes the most grief? In my experience, PUT.
The specification is unambiguous: PUT replaces the entire resource at a URI with the representation you send, and it’s meant to be idempotent, i.e. PUT the same thing ten times and the end state is identical to PUT-ing it once. That definition has a sharp edge most people walk straight into. Because PUT replaces the entire resource, the frontend that wants to change one field has to send back the complete resource, including every field, in a valid shape. And to do that, it must carry a faithful copy of the backend’s schema in its own head: which fields exist, which are required, what counts as a valid state. The humble “edit a member’s email address” button is now quietly responsible for the entire member record.
The usual escape hatch is to bend PUT into a partial update, sending only the changed field and hoping the server merges it. That isn’t PUT; that’s PATCH wearing PUT’s coat, and it throws away the idempotency guarantee you were supposedly buying. Either way you’ve lost something. Hold the line on PUT, and the frontend owns the schema; bend PUT into a merge and the contract no longer means what it says.
Suffice to say, the verb isn’t the villain here; the coupling is. The moment the frontend has to assemble a backend-shaped payload to get anything done, the two teams are no longer decoupled, whatever the architecture diagram on the wall claims.
If your “update” button needs to know every field on the record, your frontend is operating the switchboard, not dialling a number.
What the switchboard taught Erlang
I’ve banged on for years about the virtues of the Erlang platform. The language is styled after Prolog, which makes short work of the way my brain phrases problems; I’m simply a more productive developer in it than in most imperative languages. Perhaps I’m wired for predicate logic (the same pull draws me to the LISP and Scheme family). One thing I take entirely for granted in the Erlang world is the send-and-receive paradigm. The inventors weren’t setting out to implement the Actor model; they were chasing concurrency and scalability for those telephone switches, and message passing was simply the tool that fit the hand. And it worked.
Fundamentally, what the Actor model does is hide from the caller how the receiving process does its job, i.e. how it reads the payload, interprets the intent behind the message, processes the data, and perhaps writes to some store whose schema only it knows. The sender doesn’t know the schema or the procedure. Send the same message to a different process, and you may well get different behaviour. From an OOP angle, this is just abstraction and polymorphism arriving by another road, which is more or less the point Alan Kay keeps making, that we collectively misread OOP, and that the big idea was always message passing between objects rather than the class machinery we fixate on. Through messages, an object never has to know the internals of whatever it’s talking to.
That is the whole principle of decoupling in the Actor model: independent systems integrated by message passing, and nothing more. It’s the automatic exchange, not the switchboard. The caller dials; the switch works out the call.
Decoupling isn’t a layer you bolt on; it’s a fact about who is allowed to know what. Messages keep that boundary honest.
So what does this look like for the web?
So how do we drag this onto an HTTP wire? The trick is to stop thinking of the request as a CRUD operation against a known resource and start thinking of it as a message that carries the frontend’s intent. The frontend POSTs events, not records; the backend, playing the part of the actor, receives the message, works out what it means, and does whatever needs doing, i.e. validation, ID assignment, persistence, knock-on processes, none of which the frontend sees or cares about. That single POST is the only protocol the two sides need to share.
Back to the video library. The CRUD instinct says: the frontend builds a full video record, mints or fetches an ID, knows the schema, and POSTs (or PUTs) it to /videos. The Actor approach says: the frontend describes what just happened, and sends that.
POST /catalogue
{
"event" : "MemberRequestedNewTitle",
"payload" : {
"title" : "Star Wars: A New Hope",
"language" : "English",
"genre" : "Sci-Fi"
}
}
Notice what’s missing. There’s no video-id, because minting identifiers is the backend’s business, not the frontend’s. There’s no PUT, no DELETE, no knowledge of how a “title” is stored or what else firing this event sets in motion (perhaps it also pings a recommendation service, or checks a licensing rule, etc.). The frontend said what the member did; the backend decided what that means. One HTTP verb in play, and the schema the frontend must understand shrinks to the shape of its own events.
A few principles fall out of this once you commit to it:
- Intent over instruction. The frontend sends what happened (
MemberRequestedNewTitle), not how to record it (anINSERTdressed up as a PUT). The backend owns the “how”. - One door, many messages. Instead of a sprawl of resource URIs each with its own verb dance, you have a small set of message endpoints and a vocabulary of events. The surface the two teams must agree on is the message catalogue, not the database schema.
- Schema ownership stays put. The backend’s data model never leaves the backend. Rename a column, split a table, swap the store; as long as the message contract holds, the frontend never notices. This is the decoupling REST promised, and coupling-by-PUT quietly took back.
- Business logic goes home. Validation, identity, state transitions and the rest live on the server, where they can be tested and trusted, rather than being smeared across a JavaScript bundle shipped to every browser on the planet.
- The wire becomes a log of events. Because each message is a self-contained statement of intent, you get something close to an event stream for free, which is a friendly on-ramp if you ever drift towards event sourcing or CQRS (borrowing the vocabulary loosely here).
And to be clear, none of this requires Erlang on the backend. You can receive and interpret these messages in Python, Go, Node, whatever you already run; the Actor model here is a mental model for the interface, not a mandate for your runtime. In my own projects the receiving side has often been plain Python doing exactly this, with not a process mailbox in sight.
Stop shipping the frontend a schema and a set of verbs; ship it a vocabulary of things that can happen, and let the backend be the exchange.
“But haven’t you just reinvented RPC?”
A fair challenge, and worth meeting head-on rather than waving away. If the frontend POSTs MemberRequestedNewTitle to a single endpoint, isn’t that just a remote procedure call, RPC in a fresh coat of paint? Not quite, and the difference earns its keep. An RPC names a procedure and expects it to run; a message names an event and leaves the interpretation to the receiver, who is free to do nothing, do several things, or do something different next quarter without the caller changing a line. The coupling in RPC points at a function; the coupling here points at a shared meaning. That’s a looser knot, and a more honest one.
The honest costs are real too, and I won’t pretend otherwise. You give up the lovely uniformity of REST, the off-the-shelf caching, the HTTP semantics that proxies and tooling already understand; a POST of an opaque event does not cache like a GET of a resource, and there’s no getting around that. You take on the job of versioning your message vocabulary as it grows, and of documenting it well enough that the frontend team isn’t reverse-engineering events out of server logs (we are humans after all, and an undocumented message bus rots every bit as fast as an undocumented API). Discoverability suffers as well: there’s no tidy /videos you can curl to see what’s on offer. None of these is a dealbreaker, but they are the bill, and somebody pays it.
Messages buy you decoupling and pay for it in uniformity and tooling; whether that’s a bargain depends on your app, not on a blog post.
Summary:
REST, as Fielding actually designed it, is a fine piece of engineering, and for a great many applications, the content-heavy, cacheable, resource-shaped ones, it remains the right default; I’m not here to bury it. The argument is narrower than that. The trouble starts when we drag the backend’s data schema across the wire and ask the frontend to drive it CRUD-style, PUT by PUT, until “decoupled” is just a word on a diagram. The Actor model offers a different posture: let the frontend say what happened, and let the backend, like a telephone exchange, decide what to do about it. Intent in, work hidden, schema kept at home.
Could you achieve the same decoupling using a carefully designed REST API that properly abstracts its internals? Honestly, yes; a disciplined resource design gets you most of the way there. But all too often the backend API is just a thin veneer over granular database calls, with the frontend left holding the business logic and the schema, and at that point a message-passing interface earns its keep, because it removes the temptation rather than asking discipline to hold the line forever. As with most things in this craft, weigh it against your own context, your team’s shape, your caching needs, your appetite for maintaining a message vocabulary, and pick the trade-off you can live with. Just don’t hand the frontend the switchboard and call it integration.
메타데이터
- post_id
- 27c7a08e53e7
- slug
- integrating-web-frontends-with-api-backends-using-the-actor-model-27c7a08e53e7
- url
- https://medium.com/@gestapoh/integrating-web-frontends-with-api-backends-using-the-actor-model-27c7a08e53e7
- canonical_url
- https://medium.com/@gestapoh/integrating-web-frontends-with-api-backends-using-the-actor-model-27c7a08e53e7
- author_url
- https://medium.com/@gestapoh
- status
- ok
- fetched_at
- 2026-07-09 13:13:48