Good Bye Web APIs
Tags: webdev, javascript, typescript, api
Published: 2020-11-23 20:26:17

When building a [single-page application](https://en.wikipedia.org/wiki/Single-page_application) or a mobile application, we usually need to implement a web API (REST, GraphQL, etc.) to connect the frontend and the backend. Technically, it's not very difficult, but it has some unfortunate consequences.

Imagine two planets. The planet "frontend" speaks JavaScript and the planet "backend" also speaks JavaScript or any other advanced language.

Now let's say that these planets need to collaborate extensively to form a whole called "application".

Unfortunately, the planets are unable to communicate with each other directly using their native language and they have to rely on a third party called "web API" which speaks a much less sophisticated language.

Indeed, the language of most web APIs is limited to a combination of URLs, a few HTTP verbs (GET, POST, DELETE, etc.), and some JSON.

![Frontend + Web API + Backend](https://dev-to-uploads.s3.amazonaws.com/i/4jf4ioy2841npvbihshz.png)

The web APIs that speak GraphQL are more advanced but they remain far behind the possibilities of a programming language such as JavaScript:

- The programming paradigm is procedural or functional (no object-oriented programming).
- Only the most basic types are supported (forget about Date, Map, Set, etc.).
- The concept of reference is missing (you can only pass objects by value).

Placing a rudimentary language between the frontend and the backend adds a lot of boilerplate and ruins the development experience.

Another problem is that a web API is an extra layer to worry about. It must be designed, implemented, tested, documented, etc. And all this is frankly a pain in the ass.

But the worst thing is that building a web API generally forces you to degrade the quality of your codebase. Indeed, it's quite challenging to keep your code DRY and cohesive when your frontend and your backend are separated by a web API.

Now imagine that we could get rid of the web API. Imagine that the frontend could communicate directly with the backend using its native language. Wouldn't it be great?

![Frontend + Backend](https://dev-to-uploads.s3.amazonaws.com/i/ggelge7utvo2cbpgte2l.png)

The good news is that it's possible today thanks to a set of libraries called [Layr](https://github.com/layrjs/layr).

## Hello, Layr!

With [Layr](https://github.com/layrjs/layr), the frontend and the backend are _physically_ separated (they run in different environments) but _logically_ reunited (it's as if they were in the same environment).

How does it work?

1. The backend is composed of one or more classes whose some of their attributes and methods are explicitly exposed to the frontend.
2. The frontend generates some proxies to the backend classes and can use these proxies as if they were regular JavaScript classes.

Under the hood, Layr relies on an [RPC](https://en.wikipedia.org/wiki/Remote_procedure_call) mechanism. So, superficially, it can be seen as something like [CORBA](https://en.wikipedia.org/wiki/Common_Object_Request_Broker_Architecture), [Java RMI](https://en.wikipedia.org/wiki/Java_remote_method_invocation), or [.NET CWF](https://en.wikipedia.org/wiki/Windows_Communication_Foundation).

But Layr is radically different:

- It's not a [distributed object system](https://en.wikipedia.org/wiki/Distributed_object). A Layr backend is stateless, so there are no shared objects across the stack.
- It doesn't involve any boilerplate code, generated code, configuration files, or artifacts.
- It uses a simple but powerful serialization protocol ([Deepr](https://deepr.io)) that enables unique features such as chained invocation, automatic batching, or partial execution.

Layr starts its journey in JavaScript/TypeScript, but the problem it tackles is universal, and it could be ported to any object-oriented language.

## Example

Let's implement the classic "Counter" example to see what it looks like to build a full-stack application with Layer.

First, we implement the "data model" and the "business logic" in the backend:

```js
// backend.js

import {
  Component,
  primaryIdentifier,
  attribute,
  method,
  expose
} from '@layr/component';
import {ComponentHTTPServer} from '@layr/component-http-server';

class Counter extends Component {
  // We need a primary identifier so a Counter instance
  // can be transported between the frontend and the backend
  // while keeping it's identity
  @expose({get: true, set: true}) @primaryIdentifier() id;

  // The counter value is exposed to the frontend
  @expose({get: true, set: true}) @attribute() value = 0;

  // And the "business logic" is exposed as well
  @expose({call: true}) @method() increment() {
    this.value++;
  }
}

// Lastly, we serve the Counter class through an HTTP server
const server = new ComponentHTTPServer(Counter, {port: 3210});
server.start();
```

Oh my! All that code just for a simple "Counter" example? Sure, it seems overkill, but we've actually implemented a full-grade backend with a data model, some business logic, and an HTTP server exposing the whole thing.

Now that we have a backend, we can consume it from a frontend:

```js
// frontend.js

import {ComponentHTTPClient} from '@layr/component-http-client';

(async () => {
  // We create a client to connect to the backend server
  const client = new ComponentHTTPClient('http://localhost:3210');

  // We get a proxy to the Counter backend class
  const Counter = await client.getComponent();

  // Lastly, we consume the Counter
  const counter = new Counter();
  console.log(counter.value); // => 0
  await counter.increment();
  console.log(counter.value); // => 1
  await counter.increment();
  console.log(counter.value); // => 2
})();
```

What's going on here? By invoking the `counter.increment()` method the counter value is incremented. Note that this method does not exist in the frontend. It is implemented in the backend and is therefore executed in this environment. But from the perspective of the frontend, the actual execution environment doesn't matter. The fact that the method is executed remotely can be seen as an implementation detail.

The `Counter` class in the frontend can be extended to implement features that are specific to the frontend. Here's an example of how to override the `increment()` method to display a message when the counter reaches a certain value:

```js
class ExtendedCounter extends Counter {
  async increment() {
    // We call the `increment()` method in the backend
    await super.increment();

    // We execute some additional code in the frontend
    if (this.value === 3)
      console.log('The counter value is 3');
    }
  }
}
```

This is what it looks like when the frontend and the backend are reunited. Pretty cool isn't it?

## What's the Catch?

Why does everyone build web APIs when we could do without them?

There is one good reason to implement a web API, it's when you want to expose your backend to some external developers through an established protocol such as REST. But let's be honest, the vast majority of applications don't have this requirement. And if it turns out that you need a web API, it is possible to add it afterward while continuing to use the "API-less" approach for all your internal needs.

Another reason is if you work on a large-scale application with millions of users. Indeed, the convenience provided by Layr doesn't come without a cost, so if you want the most optimized application possible, you'd better go with a lower-level solution.

Finally, if you want to implement a frontend or a backend in a language other than JavaScript, you can still use Layr on one side of the stack, but you will then have to implement an API client or server that can speak the [Deepr](https://deepr.io) protocol on the other side of the stack.

## Conclusion

Removing the web API allows you to build a full-stack application much faster while increasing the quality of your codebase.

By using [Layr](https://github.com/layrjs/layr) on several projects, including some production projects, I was able to reduce the amount of code by [50% on average](https://github.com/layrjs/react-layr-realworld-example-app/blob/master/docs/comparison.md) and greatly increase my productivity.

Another important aspect is the development experience. Since the frontend and the backend are no longer separated by a web API, you get a feeling similar to developing a standalone application, and it's a lot more fun.