This blog post introduces various API architectures like REST API, GraphQL, gRPC, and more.

The application interfaces built on top of the protocol stacks we have learned so far, specifically programmable ones, are called application programming interfaces (APIs) and facilitate communication between services on the Internet. When designing an API for your service, it’s important to follow certain rules or constraints so that third parties, even with no background, can easily understand and use your service. In this article, I will cover several common API architectures designed for different types of services.
REST API
REST (Representational State Transfer) is arguably the simplest API architecture, utilizing HTTP with some constraints. In a REST API, the request must correspond to operations on resources that are independent and idempotent. This means that operations should not affect the outcome of another operation, and the resulting message of an operation should be the same regardless of the situation. Additionally, communication must be stateless, meaning the message must contain all the necessary information required for the operation.
REST APIs also follow syntactical principles. The HTTP methods—GET
, POST
, PUT
, and DELETE
—must correspond to
retrieving, creating, updating, and deleting a resource, respectively. Additionally, the URIs should be descriptive
of the resources they represent. REST APIs primarily use HTTP 1.1 and JSON as the content type to encode all the states
in a request and response.
// Bad Example 1
GET /hi HTTP 1.1
Content-Type: application/json
{
"method": "delete",
"item": "product",
"id": 1,
}
// Bad Example 2
GET /hi?method=delete&item=product&id=1 HTTP 1.1
// Good Example
DELETE /product?id=1 HTTP 1.1
In the first bad example, a GET
request is used with a JSON body to delete a product with an ID of 1 via the /hi
endpoint.
This is incorrect because GET
requests are not supposed to have a body. The second example includes all the information
in the URI but still uses the wrong HTTP method (GET
instead of DELETE
), and the endpoint /hi
is not descriptive.
The third example correctly uses the DELETE
method with a descriptive URI.
GraphQL
One potential drawback of REST APIs is the limited way clients can access resources. If a query requires multiple resources, the client must send requests to multiple endpoints. Or, if the client only needs part of the response, they must still retrieve the entire dataset (Overfetching). GraphQL (Graph Query Language), typically using HTTP 1.1, aims to solve this by providing a single endpoint capable of handling flexible queries. Here’s an example schema in GraphQL:
type Blogger {
id: ID!
name: String
blogs: [Blog]
}
type Blog {
url: String!
blogger: Blogger
}
type Query {
blogs: [Blog]
blogger(id: String!) : Blogger
}
type Mutation {
uploadBlog(url: String): Blog
}
In GraphQL, schemas are set up for objects, indicating their fields, types, and relationships.
The Query
schema defines how clients can access the objects defined by the schemas,
and the Mutation
schema defines how data can be modified in GraphQL. The server must
implement resolvers to handle these queries and return the corresponding data, typically in JSON.
While GraphQL offers clients a more flexible and efficient experience,
developers may need to work harder when setting up aspects like authorization and rate limiting.
gRPC
A relatively new alternative to the above two architectures is gRPC, which is an implementation of Remote Procedure Call (RPC) by Google. RPC allows the client to call functions implemented by the server as if they were defined by the client, offering intuitive API calls. Google implemented RPC using Protocol Buffers (.proto), a file format supported by HTTP 2.0. Below is an example of how a protocol buffer looks for gRPC.
syntax = "proto3";
package "blogPackage";
service Blog {
rpc getBlogById(ID) returns (BlogPost)
rpc getAllBlogs(noParams) returns (BlogPosts);
}
message ID {
int32 id = 1;
}
message noParams {}
message BlogPost {
int32 id = 1;
string author = 2;
string content = 3;
}
message BlogPosts {
repeated BlogPost posts = 1;
}
In gRPC, protocol buffers contain the schemas of objects and the API calls, which can be compiled into many languages (C++, Python, JavaScript, etc.) and accessed by both the server and the client in their supported native languages. The server provides the implementation of the API calls, and the client uses the functions to send corresponding requests to the server. Below is an example of a server and client in JavaScript using gRPC. Both the server and the client can access the RPCs and objects defined in the protocol buffer above.
const grpc = require("grpc");
const protoLoader = require("@grpc/proto-loader");
const packageDef = protoLoader.loadSync("blog.proto", {}); // load .proto
const grpcObject = grpc.loadPackageDefinition(packageDef); // loading objects definition
const blogPackage = grpc.blogPackage;
// -- Server Code in `server.js` --
const server = new grpc.Server();
server.bind("localhost:4000", grpc.ServerCredentials.createInsecure());
server.addService(blogPackage.Blog.service, {
"getBlogById": getBlogById,
"getAllBlogs": getAllBlogs,
});
server.start();
function getBlogById (call, callback) {
...
};
function getAllBlogs (call, callback) {
...
}
// -- Client Code in `client.js` --
const client = new blogPackage.Blog("localhost:4000", grpc.credentials.createInsecure());
client.getBlogById({
"id": 1
}, (err, response) => {
...
})
client.getAllBlogs({}, (err, response) => {
...
})
Not only do protocol buffers support serializing requests and responses into binary, but the use of HTTP 2.0 allows gRPC to take advantage of HTTP 2.0's compression and multiplexing features, which make communication even faster. Unlike REST API and GraphQL, gRPC also allows for bidirectional streaming. (For more details, I recommend checking out gRPC Crash Course - Modes, Examples, Pros & Cons and more by Nasser, H. (2020)). Due to its efficiency and ease of use, many microservices utilize gRPC. However, most web browsers do not support gRPC.
WebSocket
If gRPC is generally unavailable in web browsers, how can we establish bidirectional communications between the browser and the web server? The solution is WebSocket, which allows connections to be opened, and both the server and client can send messages to each other. To establish a WebSocket connection, the client and server perform a WebSocket handshake, where they exchange the following request and response.
// Client Request
GET / HTTP 1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
...
// Server Response
HTTP 1.1 101 Switching Protocols
Upgrade: websocket
Connection Upgrade
...
The WebSocket connection is stateful, meaning that the client and server must maintain the state of the connection. This means careful consideration is needed when scaling the web service horizontally (you'll need to maintain the connection with the same web server or set up communication between web servers). However, WebSockets make it easy to create various web applications that require real-time data, such as marketplaces, live feeds, and games.
Webhooks
When your web server makes a request to a service that takes time (like Stripe), the server can only wait for a limited amount of time before the request times out. To avoid this, you could continuously send requests to the service and receive the processing status from the service until it sends back a completed status—this is called polling. (Polling can also be an alternative to WebSockets.) However, this approach requires multiple requests, which is costly for both the web server and the service.
A clever solution to this problem is webhooks, where the client sets up an endpoint to receive a response from the server and includes it as part of the request. Once the server finishes processing, it uses the webhook URL to send the response back to the client as a separate HTTP message. To ensure the message is received successfully, it is common practice to set up polling as a fallback and periodically send requests until the client receives the response.
Conclusion
In this article, we've covered several API architectures, but how do you choose between them? You can choose REST API if you want to quickly build a simple web application, and you can use GraphQL if you want to allow the client more flexibility in querying data. If the web application involves real-time data, you might consider using polling or WebSockets. If the API requires significant processing time, webhooks are a good choice. Finally, if you're building microservices that require fast and flexible communications, gRPC could be your best option.
Resources
- Nasser, H. 2019. WebSockets Crash Course - Handshake, Use-cases, Pros & Cons and more. YouTube.