JSON Uniform Response
JSON Uniform Response
You are watching version 2, which supersedes the previous version. Version 1 is no longer maintened (you can still access it through the tag menu right above).
Json Uniform Response is my attempt at making the job of developers that consumes API easier by a set of defined rules to make the data retrieving task the same regardless of the nature of the data we are trying to fetch or alter. This might seems a little bit abstract said like this, so quickly jump into the real life example if you need some real use cases.
JUR is optimized for REST API that respond with JSON data.
No matter which endpoint you request, nor which type of HTTP request you make, this is what you will always have in response:
GET http://example.com/api/task
{
"message": null,
"request": "get",
"data": [
{
"id": 1,
"name": "Fix the :hover state of the submit button",
"updated_at": "2018-06-20T10:07:37+02:00"
},
{
"id": 2,
"name": "Add Youtube social icon on the menu",
"updated_at": "2018-06-20T11:29:01+02:00"
}
],
"debug": {
"elapsed": 120000,
"issued_at": 1529843640000000,
"resolved_at": 1529843640120000
},
}
This is the right place to inform the end user of what is happening with his request.
The user can be a developer, but also a non-developer like a customer in a shop website. So this message should be targeted to offer a good comprehension, without exposing sensible information about the server or any other critical data.
This is the attribute that saves the type of request that have been processed. This attribute can help developers in 2 ways:
This is the attribute where all the necessary data to provide are stored. It can be set to null when there is no data to return, which is different than the absence of result (which would be an empty array/object).
This is the attribute designed for the consumer of the API. It let you know when the request has been issued and resolved according to the server, and how many time did the server took to resolve this request.
elapsed
The time elapsed by the controller to resolved the request. In microseconds.
issued_at
The time at which the request has been handled by the controller. Timestamp in microseconds.
resolved_at
The time at which the request has been resolved by the controller. Timestamp in microseconds.
This example features:
We are in the context of a task application, and the user is displaying the task list.
<script type="text/javascript">
document.addEventListener('DOMContentLoaded', function() {
fetch('/api/task', { method: 'GET' }).then(function(response) {
if( resp.ok ) {
let response = new Jur().parse(response.json());
console.log(response.data());
}
});
});
</script>
namespace App\Http\Controllers;
use App\Task;
class TaskController extends Controller {
public function index(): string {
return response()->json( jur()->data( Task::all() )->toArray() );
}
}
If you have seen the version 1, you will notice there is no status
(success
, fail
or error
).
You can now check for errors using the correct manner, which is to check on the HTTP status code of the request.
Here is an example of a simple error management using javascript:
fetch('/api/task', { method: 'POST', body: JSON.stringify({name: 'Do the dishes'}) }).then(function(response) {
if( ! response.ok ) {
handleErrors(response.status, response.json().message);
}
return response;
}).then(function(response) {
// process the response as usual
});
And in our handleErrors
function:
function handleErrors(code, message) {
switch(code) {
case 400:
// display an alert in orange
case 500:
// display an error in red
// ...
}
}
One interesting thing about the debug attribute is that you can use it to compute latency between the time your client sent the request until the moment the controller is about to start resolving the request.
Keep in mind the request could also go through many layers like framework middlewares, CDNs, … All of these should be taken into an account.
So for example in Javascript we could compute this like this:
const client_issued_at = Date.now() + new Date().getMilliseconds();
fetch('/api/task', { method: 'POST', body: JSON.stringify({ name: 'rework JUR standard' }) }).then(function(response) {
const resp = response.json();
const sever_issued_at = resp.debug.issued_at;
const latency = server_issued_at - client_issued_at;
alert(resp.message + '. Done in ' + Math.round(resp.debug.elapsed / 1000) + 'ms (+ ' + Math.round(latency) + 'ms).');
});
Which would return to the client an alert with the following message:
Task saved. Done in 120ms (+ 90ms).
Note
You can also use existing third-party library that implements such behavior, like khalyomede/jur-js, which make this job easier:
var jur = new Jur().issued();
fetch('/api/task', { method: 'POST', body: JSON.stringify({ name: 'rework JUR standard' }) }).then(function(response) {
jur.parse(response.json());
alert(jur.message() + ' Done in ' + jur.elapsed('millisecond') + 'ms (+' + jur.latency('millisecond') + 'ms).');
});
Which will display:
Task saved. Done in 120ms (+90ms).
In case of a breaking change in your tables, you might want to version your API to not force all your front-end developper to immediately change their code. Simply add a v1
, to your routes.
As the message attribute is intended for being displayed to the end user, you will also want to personalize the message regarding the locale of the device. For this, I propose you to add the locale in the route like:
/api/v1/en-us/task
/api/v1/fr-fr/configuration/notification
Specifying the locale in the route instead of on the header or the body/query parameters has the advantage of making this a requirement more than an option, so it is allow the back-end developer to have less verification to do or remove some tasks (like having to split by ;
the language of the Accept-Language
header, in case there is multiple language, or to interpret *
and having to fetch the default language, …).
attribute | parent | type | format | example |
---|---|---|---|---|
message | string,null | “Task created successfuly.” | ||
null | ||||
request | string | get | ||
post | ||||
put | ||||
patch | ||||
delete | ||||
data | * | [{“id”: 1, “name”: “Do the dishes”}, {“id”: 2, “name”: “Buy catnips”}] | ||
null | ||||
{“lat”: 48.8773406, “lng”: “2.327774”} | ||||
debug | object | {“elapsed”: 120000, “isued_at”: 1529843640000000, “resolved_at”: 1529843640120000”} | ||
elapsed | debug | integer | microsecond | 120000 |
issued_at | debug | integer | microsecond | 1529843640000000 |
resolved_at | debug | integr | microsecond | 1529843640120000 |
Made an implementation? Please fill in an issue and I will add it to the list (or make a Pull Request for this).