There Are Only 6 Constraints in REST, Let’s Use Them All

There Are Only 6 Constraints in REST, Let’s Use Them All

Posted in

On my desk, I have a copy of the O’Reilly RESTful Web Services book by Leonard Richardson and Sam Ruby. Published in December 2007, it’s astonishing how much of the book is still best practice. However, sixteen years is a long time in technology, and we’re asking more and more of our APIs.

Amid recent trends like asynchronous events, streaming data, client-side cryptography, and native language clients, REST is starting to show its weaknesses. Do we need to throw REST out and start again, or have we been missing something that can make our APIs more powerful?

I believe the answer is “code on demand” — sending code from the server to the client to run there — and I’ve created a new open standard called HyperMap that lets you use it today.

Daniel Feichtinger introduced HyperMap at the 2023 Platform Summit in Stockholm.

Code on Demand

Code on demand might be an unfamiliar term in today’s API landscape (I can’t find any videos from previous Platform Summits mentioning it!). However, it’s been one of the core principles of REST since the very beginning.

By definition, REST is the architecture of the web. One interesting thing about REST is that it was codified nearly a decade after the web took off. It attempts to capture what made the web so successful, both from a technical point of view and also in its adoption. REST was described as a set of six constraints:

  • Client-server architecture
  • Statelessness
  • Cacheability
  • Layered system
  • Uniform interface
  • Code on demand (optional)

The first four are largely what you get from using JSON or XML over HTTP. The uniform interface constraint is an interesting one: to truly meet it, you have to use hypermedia as your representation, something that’s still not common for APIs. But I want to focus on the last constraint, code on demand, which is rarely discussed in the context of REST APIs.

What is code on demand? Well, if we’re referring to the web, we can broadly say it’s running JavaScript in the browser. Web pages aren’t just declarative HTML and CSS — we ship executable JavaScript too. We do this so that we can temporarily enhance the browser with some behavior we’d like our pages to support. Web browsers are universal — I can pick one and visit any site on the web — and they have lots of built-in features like styling blocks of text, rendering forms with controls, and showing media.

Consider when Google, in the mid-2000s, wanted to ship an awesome new maps experience, where a user could drag a map around and new tiles load asynchronously in the background. This UI control, now called “slippy maps,” wasn’t a built-in feature of browsers back then, so the developers had three options:

  1. Propose a new UI element to the W3C, wait for it to be standardized, and then ship it in all major browsers.
  2. Build a new native app to support this feature while also reimplementing most browser features like text and image rendering and convince users to download it.
  3. Ship a little bit of JavaScript to support the new control.

The last option is orders of magnitude quicker and less development effort, and that’s how we got Google Maps.

JavaScript on the web is quite popular! Nearly all websites use some amount of JavaScript, and if you try disabling it, you’ll see just how essential it is to the modern web. In fact, I’d argue that if the web didn’t support code on demand, it would have been replaced by a distributed information system that did.

Given how critical code on demand is to the success of the web, thanks to the powers it unlocks, why aren’t we using this for our APIs?

Introducing HyperMap

Well, with HyperMap, you can! This is a new RESTful format that I created recently. It supports running JavaScript and WebAssembly on the client with the full suite of web APIs that you’d expect from the browser. It’s designed to be incredibly easy to adopt for existing JSON-based REST APIs — you really only need to change the content type to vnd.hypermap+json and you’re away. On the client side, users can just import a simple library called Mech and use it to talk to any HyperMap service. Mech currently has JavaScript and TypeScript bindings, but more languages are coming soon.

Let’s take a look at a few challenges with traditional REST APIs that you can solve with HyperMap:

1. Eliminate Webhooks

Webhooks are great for notifying a client that a long-running process has completed, but assumes you have a publicly reachable URL to POST back to. This isn’t the case if you’re developing native apps, developing locally, or running on CI.

Instead, with HyperMap, you can use the # key to add attributes to your API response, pointing to a script to load.

    {
      "#": {
        "script": "/eventHandler.js"
      },
      "ticketId": "2277",
      "status": "pending"
    }

In the script, you can access the HyperMap data and a simple interface to manipulate it. You also have access to the full suite of web APIs, so you can just create an EventSource, listen to Server-Sent Events, and update the HyperMap dynamically:

    const evtSource = new EventSource('/tickets/2277/events');

    evtSource.onmessage = (event) => {
      hypermap.set('status', event.data);
    };

Apps looking to react to this change can just use an event handler through Mech:

    const ticketTab = Mech.open("https://example.com/tickets/2277/");

    ticketTab.on("changed", () => {
      console.log(`New status: ${ticketTab.at("status")}`);
    });

With just a few lines of code, we’ve built something significantly simpler and more powerful than we could have achieved with webhooks.

2. Unify REST and Streaming Endpoints

Something that’s increasingly common — especially in FinTech — is to ship two APIs: a regular REST one for structured data and a WebSocket one for streaming data. With HyperMap, you can have the best of both worlds. Imagine a simple stock service, listing stock tickers with their current price:

    {
      "#": {
        "script": "/streamHandler.js"
      },
      "aapl": 180.67,
      "meta": 321.91,
      "googl": 138.80
    }

Again, we can just annotate the response with the script we want to run:

    const stockSocket = new WebSocket('wss://example.com/stocks/');

    stockSocket.onmessage = (event) => {
      const msg = JSON.parse(event.data);
      hypermap.set(msg.ticker, msg.price);
    };

Similarly, binding to the changing data in Mech is straightforward.

3. Client-Side Pre-Processing

If you need to process private or sensitive data, you might need to hard-code logic in your wrapper/SDK to scrub that information before it touches your service. With HyperMap, you can ship the logic to the client alongside the API response, ensuring it works in any supported language and is always up to date. See the privacy-preserving text sentiment analysis demo in the example_server.

The Future of REST Is Bright

The three examples above just scrape the surface of what code on demand can bring to REST APIs. I expect there will be many more exciting things to discover in this space! If you’re interested in learning more about HyperMap, check out the docs and come and chat with us on Discord.