Source: Articles on Smashing Magazine — For Web Designers And Developers | Read More
In today’s web development landscape, the concept of a monolithic application has become increasingly rare. Modern applications are composed of multiple specialized services, each of which handles specific aspects of functionality. This shift didn’t happen overnight — it’s the result of decades of evolution in how we think about and implement data transfer between systems. Let’s explore this journey and see how it shapes modern architectures, particularly in the context of headless CMS solutions.
When computers first started talking to each other, the methods were remarkably simple. In the early days of the Internet, systems exchanged files via FTP or communicated via raw TCP/IP sockets. This direct approach worked well for simple use cases but quickly showed its limitations as applications grew more complex.
# Basic socket server example import socket server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) server_socket.bind(('localhost', 12345)) server_socket.listen(1) while True: connection, address = server_socket.accept() data = connection.recv(1024) # Process data connection.send(response)
The real breakthrough in enabling complex communication between computers on a network came with the introduction of Remote Procedure Calls (RPC) in the 1980s. RPC allowed developers to call procedures on remote systems as if they were local functions, abstracting away the complexity of network communication. This pattern laid the foundation for many of the modern integration approaches we use today.
At its core, RPC implements a client-server model where the client prepares and serializes a procedure call with parameters, sends the message to a remote server, the server deserializes and executes the procedure, and then sends the response back to the client.
Here’s a simplified example using Python’s XML-RPC.
# Server from xmlrpc.server import SimpleXMLRPCServer def calculate_total(items): return sum(items) server = SimpleXMLRPCServer(("localhost", 8000)) server.register_function(calculate_total) server.serve_forever() # Client import xmlrpc.client proxy = xmlrpc.client.ServerProxy("http://localhost:8000/") try: result = proxy.calculate_total([1, 2, 3, 4, 5]) except ConnectionError: print("Network error occurred")
RPC can operate in both synchronous (blocking) and asynchronous modes.
Modern implementations such as gRPC support streaming and bi-directional communication. In the example below, we define a gRPC service called Calculator
with two RPC methods, Calculate
, which takes a Numbers
message and returns a Result
message, and CalculateStream
, which sends a stream of Result
messages in response.
// protobuf service Calculator rpc Calculate(Numbers) returns (Result); rpc CalculateStream(Numbers) returns (stream Result); , [productSlug]);
And return a template with all our data:
<h1>productData.content.title</h1> <p>productData.content.description</p> <h2>Price: $inventory.variants[0].price</h2> <h3>Related Products</h3> <ul> relatedProducts.map((product) => ( <li key=product.objectID>product.name</li> )) </ul>
We could then use an event-driven approach and create a server that listens to our shop events and processes the checkout with Stripe (credits to Manuel Spigolon for this tutorial):
const stripe = require('stripe') module.exports = async function plugin (app, opts) const stripeClient = stripe(app.config.STRIPE_PRIVATE_KEY) server.post('/create-checkout-session', async (request, reply) => const session = await stripeClient.checkout.sessions.create( line_items: [...], // from request.body mode: 'payment', success_url: "https://your-site.com/success", cancel_url: "https://your-site.com/cancel", ) return reply.redirect(303, session.url) ) // ...
And with this approach, each service is independent of the others, which helps us achieve our business goals (performance, scalability, flexibility) with a good developer experience and a smaller and simpler application that’s easier to maintain.
The integration between headless CMSs and modern web services represents the current and future state of high-performance web applications. By using specialized, decoupled services, developers can focus on business logic and user experience. A composable ecosystem is not only modular but also resilient to the evolving needs of the modern enterprise.
These integrations highlight the importance of mastering API-driven architectures and understanding how different tools can harmoniously fit into a larger tech stack.
If you want to dive deeper into the integrations you can build with Storyblok and other services, check out Storyblok’s integrations page. You can also take your projects further by creating your own plugins with Storyblok’s plugin development resources.