fabric

RPC Over JSON vs REST API Styles

Two of the most common styles of web API design are RPC over JSON and REST. The choice between them is a matter of preference rather than functional and is akin to the choice between tabs and spaces. Luckily, with Microbus you don’t have to choose: you can have both.

RPC Over JSON

RPC is an acronym of remote procedure call and indeed this API style comes from a backend-centric perspective where each operation on the backend is a procedure. Endpoints on the backend are mapped to a URL path that reflects their name. Input arguments are passed as JSON in the body of the HTTP request or as query arguments. Output arguments are returned in the body of the HTTP response. In Microbus, RPC is the default API style exactly because it is a consistent and unambiguous way to map the HTTP request to the underlying Go implementation of the endpoint.

Consider a simple example of an RPC Add(x int, y int) (sum int). Microbus automatically generates marshaling and unmarshaling code that can process the following GET or POST requests:

In a GET request, input arguments are read from the query arguments.

GET /add?x=5&y=6 HTTP/1.1

In a POST request, the input arguments are read from the request body. Each of the arguments is expected to be a named field in a single JSON object.

POST /add HTTP/1.1
Content-Type: application/json

{"x":5,"y":6}

Alternatively, a URL-encoded form data can also be POST-ed.

POST /add HTTP/1.1
Content-Type: application/x-www-form-urlencoded

x=5&y=6

In all three cases, the response is a JSON object where each of the return values (in this example only one) is a named field:

HTTP/1.1 200 OK
Content-Type: application/json

{"sum":11}

RESTful

The REST API style comes from a web-centric perspective. Its philosophy is that everything on the web is a resource that can be identified with a URI and so a web API should reflect that. In REST, the request’s path represents the resource and the HTTP method (GET, POST, DELETE, PUT) represents the operation done on that resource. This style works best for CRUD APIs but may not translate well when the API has no object that is being worked on. For example, the Add example from earlier doesn’t translate well to REST.

The following example defines a typical RESTful API for CRUD operations on an Object. Notice how it utilizes the HTTP magic arguments httpRequestBody, httpResponseBody and httpStatusCode.

Create(httpRequestBody *Object) (id int, httpStatusCode int) expects a POST /objects request with the object to be created in the body of the request. It returns the assigned id along with HTTP status code 201 Created.

POST /objects HTTP/1.1
Content-Type: application/json

{"foo":"bar","count":5,"etc":"..."}
HTTP/1.1 201 Created
Content-Type: application/json

{"id":1}

Read(id int) (httpResponseBody *Object) expects a GET /objects/{id} request with the ID of the object in the path. It returns the matching object.

GET /objects/1 HTTP/1.1
HTTP/1.1 200 OK
Content-Type: application/json

{"foo":"bar","count":5,"etc":"..."}

Update(id int, httpRequestBody *Object) expects a PUT /objects/{id} request with the ID of the object in the path and the object to be created in the body of the request. It returns nothing.

PUT /objects/1 HTTP/1.1
Content-Type: application/json

{"foo":"bar","count":6,"etc":"..."}
HTTP/1.1 200 OK
Content-Type: application/json

{}

Delete(id int) expects a DELETE /objects/{id} request with the ID of the object in the path. It returns nothing.

DELETE /objects/1 HTTP/1.1
HTTP/1.1 200 OK
Content-Type: application/json

{}

ListAll(sortBy string, limit int, offset int) (httpResponseBody []*Object) expects a GET /objects?sortBy=created_at&limit=100&offset=0 request with sort order and cursor information as query arguments. It returns a list of objects.

GET /objects?sortBy=created_at&limit=100&offset=0 HTTP/1.1
HTTP/1.1 200 OK
Content-Type: application/json

[
    {"foo":"bar","count":6,"etc":"..."},
    {"foo":"baz","count":8,"etc":"..."},
    {"foo":"bam","count":9,"etc":"..."}
]

Best of Both Worlds

Even if you have a preference as to one of these API styles, users of your API are likely to have a different preference. Luckily, implementing both styles is rather simple: create the RPC-styled endpoint first, then create the RESTful endpoint and call the RPC-styled endpoint under the hood.