A Brief History of API: RPC, REST, GraphQL, tRPC

JS - Jan 11 '23 - - Dev Community

Background

Last week I joined an event about GraphQL VS TRPC discussion hosted by Guild in London. The creator of tPRC Alex and urql GraphQL core team member Phil(urql is a highly customisable and versatile GraphQL client for Javascript and its frameworks) had a fantastic discussion about each camp's general usage and problem. You can watch the whole discussion below:

A little surprise to me is that there are quite a few young guys who have only used tRPC since it’s really very efficient to start from scratch, not knowing much about GraphQL. So as an old developer coming from the ice age, I probably could give a brief history about that. Why?

As Yuval Noah Harari says in HOMO DEUS:

This is the best reason to learn history: not in order to predict the future but to free yourself of the past and imagine alternative destinies. Of course, this is not total freedom – we cannot avoid being shaped by the past. But some freedom is better than none.

Hope it will free you to see more alternatives.

What is API

Let’s start with the definition. As defined in Wikipedia:

An application programming interface(API) is a way for two or more computer programs to communicate with each other.

In our scope, it’s all about how the client sends and receives messages from the server through the internet.

API

The Ice Age

If you are from the same age as me, I bet the first network-related program you have ever seen or written is the chat application. 😄

During that time, if you want to talk with the server, you need to use the Sockets library(which has nothing to do with the Socket.io or Websocket) provided by the operating system to send the message. The code on the server side would be something like the below:



int main() {
    // Bind
    if (bind(server_socket, (struct sockaddr *) &server, sizeof(server)) < 0) {
        printf("Error binding\n");
        exit(1);
    }

    // Listen
    listen(server_socket, max_clients);

    // Accept and incoming connection
    printf("Listening for incoming connections...\n");
    int c = sizeof(struct sockaddr_in);
    while ((client_socket = accept(server_socket, (struct sockaddr *) &client, (socklen_t*) &c))) {
        printf("Connection from %s\n", inet_ntoa(client.sin_addr));

        // Send welcome message to client
        char *message = "Welcome to the chat room!\n";
        send(client_socket, message, strlen(message), 0);

        // Create new thread for incoming connection
        pthread_t sniffer_thread;
        if (pthread_create(&sniffer_thread, NULL, connection_handler, (void*) &client_socket) < 0) {
            printf("Error creating thread\n");
            exit(1);
        }
    }

    return 0;
}

void *connection_handler(void *socket_desc) {
    // Get the socket descriptor
    int sock = *(int*) socket_desc;
    int read_size;
    char buffer[BUFFER_SIZE], message[BUFFER_SIZE];

    // Receive a message from client
    while ((read_size = recv(sock, buffer, BUFFER_SIZE, 0)) > 0) {
        printf("Client %d: %s", sock, buffer);
        snprintf(message, sizeof message, "Client %d: %s", sock, buffer);
    }
}


Enter fullscreen mode Exit fullscreen mode

So here what the API provides you is simply the send and read function to send/receive the char bits. The rest is entirely on your own. You can imagine how troublesome and error-prone to deal with the char bits to do business.

The Iron Age

As the trouble is mainly caused by handling bits transmitted over the network, people start to wonder if we could be agnostic about the network, simply directly call the function in the remote server like what we call the local function. That gives birth to the RPC(Remote Procedure Call).

Actually, during the casual chat after the event, one guy said “I still don’t quite get what tRPC does”. I responded to him using the definition of the RPC “tRPC is to allow you to call the remote function simply like calling your local function with Typescript”. Then he seems to get it. See that could be a justification for the benefit of learning history. 😄

In order to do that, you first need to define your API in the IDL(Interface Definition Language) file like the below:



//file hello.idl
[
    uuid(7a98c250-6808-11cf-b73b-00aa00b677a7),
    version(1.0)
]
interface hello
{
    void HelloProc([in, string] unsigned char * pszString);
    void Shutdown(void);
}


Enter fullscreen mode Exit fullscreen mode

Then you can use the IDL compiler tool to generate the code stub to take care of the serialization and deserialization of the message for both the client and server sides:

RPC

Does it should familiar? Yes, that requires the code generation from the schema file which is the same case for GraphQL now.

Although it simplifies the work that needs to be done to talk to the server, there are several cons:

  • Tight coupling to the underlying system. It makes the client and server tightly coupled, so it’s more suitable for the internal API rather than the external API.
  • Low discoverability. there’s no way to introspect the API or send a request and start understanding what function to call based on its requests.
  • Hard to debug. The data serialization and deserialization rules are defined by NDR(Network Data Representation) including how to deal with the pointer data in 32bit and 64bit systems which are extremely hard to debug if there is anything wrong.

gRPC vs tRPC

I see lots of people asking about the difference between gPRC and tRPC as they look really like twins. Although they were both born in the modern age, gRPC is more like the lineal descendant of RPC with some advanced equipment like:

  • Protocol Buffer as IDL(Interface Description Language)
  • use of HTTP/2
  • Bidirectional streaming and flow control
  • Authentication

Whereas tRPC is more like the distant relative probably only gets the last name of it. So we will talk about it later.

SOAP

Before REST bombarding us, there was SOAP. SOAP was released by Microsoft. Apparently, it looks like nothing related to RPC, but if you know its father’s name: XML-RPC, you might see the point. Actually, SOAP inherited a lot of RPC, it uses the WSDL(Web Service Description Language) as its schema file, which is represented by XML so it is both language and platform-agnostic which allows different programming languages and IDEs to quickly set up communication. Moreover, It provides privacy and integrity inside the transactions while allowing for encryption on the message level, which meets an enterprise-grade transaction quality.

I happened to work in the protocol suite team at Microsoft. There was a big adoption of SOAP where almost all the newly created protocols are based on SOAP, a small part of which are REST.

However, REST soon overtook it and was dominating the world.

Why?

While water can carry a boat, it can also overturn it.

  • It is XML that makes it language and platform-agnostic; it is also XML that causing it to consume more bandwidth and is slow to process.

  • It is the security that makes it enterprise-grade quality; it is also security that make it less easy to set up if you want to scaffold a project quickly

So what really changed to make those cons unbearable?

It’s the mobile internet blooms that brings us to the modern age

The Modern age

REST

Unlike others, REST doesn’t have strict rules or standard toolkit. It is an architectural style and it defines a set of architectural constraints and agreements. An API that complies with the REST constraints is RESTful.

So the most advantage it has is simple and intuitive. It uses a standard HTTP method and resource centralised ways to define API like below:

REST

It’s like bringing the OOP concept to the API world. REST is popularised because it helps everyone to easily define the RESTful API.

BTW, I have seen lots of projects only use GET and POST, which won’t affect the power REST granted at all.

However, as I mentioned earlier, this loose standard also causes some problems:

  • As the project grows, the code becomes hard to maintain and error-prone.
  • It usually makes the frontend and backend team tightly coupled, as any changes in API requires both side to cooperate with one another.
  • It takes a number of calls to API to return the needed staff.
  • It usually has the over-fetching and under-fetching problem.
  • Sometimes developers have to create duplicate API endpoints to adapt to different client consumers like the browser and mobile app

Because of the reason listed above, GraphQL came to the stage and became a game changer.

GraphQL

The main measure it takes to overcome the problem of the loose standard is to bring back the schema file.



type Book {
  title: String
  author: Author
}

type Author {
  name: String
  books: [Book]
}

type Query {
  books: [Book]
  authors: [Author]
}

type Mutation {
  addBook(title: String, author: String): Book
}


Enter fullscreen mode Exit fullscreen mode

And once the schema is defined, it becomes the single source of the truth. The backend team and whatever different front-end team can work independently based on the schema. With the help of code generation, the request will be verified by the compiler to ensure the server will be able to respond to it.

All the problems mentioned about REST are resolved which you can simply see from the front page of the official website of GraphQL.

My favourite feature about GraphQL was the fact that the client gets to decide what the response will return which means they never need to push the backend to add fields for the response.

gql-gif

Until now I think GraphQL is the most efficient solution for a big project with a separate front-end and back-end team. As long as you get an experienced engineer to make the right schema design, it will pay off when you scale, especially if the API needs to be consumed externally.

The Future

Welcome to the future, if you have a business idea, just build it.

Mincraft

Don’t worry, it’s much easier now. Let's see what the world offers.

tRPC

TRPC

Although the suffix is called RPC, it only inherits the concept to call the remote function locally but in a more simplistic minimal way that doesn’t have the IDL file and the code generation process. You really have exactly the same experience of creating the local program:

tRPC-gif

So what about the tightly coupled issue and low discoverability issues? it still exists, but the world has changed.

With the development of Typescript and the framework like Next.js, it is the first time in history that you can build a full complete web app using one language in one single project. So it’s definitely tightly coupled, and there is no need for discoverability as you can directly go to the definition in the code.

Although without schema there are some disadvantages like the client can’t decide what the response is or it might be difficult to organize the functions when the project scales, remember tRPC is just two years old, it’s the future that matters. Having been implemented within Netflix, Pleo and a number of other companies, what tRPC has achieved is impressive itself.

trpc star

How about we hold together and build a brighter future together?

If you don't want to spend too much time on API design and implementation, you could checkout ZenStack: It enhances Prisma ORM with with access control policy(Authorization)layer and automatically generate APIs(REST/tRPC) and frontend hooks for you.

alex

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .