Different Ways of Accessing an API with JavaScript

Comparing the good old jQuery with modern alternatives. Fetch is an upcoming native standard. Axios is an elegant promise based HTTP client. In the end everything is wrapped together with Vue.js to create an example online base62 decoder.

Creating a Simple API

For demonstrating purposes lets create a simple JSON API. You can also call it a REST API but that might end with endless discussion on what is REST and what is not. As a basis I used a slimmed down version of Slim 3 API Skeleton.

$ composer create-project --no-interaction --stability=dev \
  tuupola/slim-api-skeleton api.base62.net

After installing I removed features such as authentication which are not needed for this demonstration.

API itself contains two routes. First one for encoding data into base62.

$app->post("/encode", function ($request, $response, $arguments) {
    $body = $request->getParsedBody();
    $encoded = (new Base62)->encode($body["data"]);
    return $response->withStatus(200)
        ->withHeader("Content-Type", "application/json")
        ->write(json_encode(["encoded" => $encoded], JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT));
});

Second one for decoding the base62 string back to original.

$app->post("/decode", function ($request, $response, $arguments) {
    $body = $request->getParsedBody();
    $decoded = (new Base62)->decode($body["data"]);
    return $response->withStatus(200)
        ->withHeader("Content-Type", "application/json")
        ->write(json_encode(["decoded" => $decoded], JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT));
});

Both expect a JSON object as input with data property containing the payload.

$ curl https://api.base62.net/encode \
    --request POST \
    --header "Content-Type: application/json" \
    --data '{ "data": "Hello world!" }'

{
    "encoded": "T8dgcjRGuYUueWht"
}
$ curl https://api.base62.net/decode \
    --request POST \
    --header "Content-Type: application/json" \
    --data '{ "data": "T8dgcjRGuYUueWht" }'

{
    "decoded": "Hello world!"
}

jQuery

jQuery has been the trusted workhorse for a long time. It still gets the job done and does it very well. However altering the DOM manually will get cumbersome with bigger projects. It is also not considered fashionable anymore.

There is a $.post() shortcut. However it will send the request as application/x-www-form-urlencode and you have to construct the JSON string and set the Content-Type header manually.

<p id="encoded">Encoding...</p>
<p id="decoded">Decoding...</p>
<p id="error"></p>
$.ajax("https://api.base62.net/encode", {
    type: "POST",
    data: JSON.stringify({ data: "Hello world!" }),
    contentType: "application/json",
}).done(function (data) {
    $("#encoded").html(data.encoded);
}).fail(function (xhr, status, error) {
    $("#error").html("Could not reach the API: " + error);
});

$.ajax("https://api.base62.net/decode", {
    type: "POST",
    data: JSON.stringify({ data: "T8dgcjRGuYUueWht" }),
    contentType: "application/json",
}).done(function (data) {
    $("#decoded").html(data.decoded);
}).fail(function (xhr, status, error) {
    $("#error").html("Could not reach the API: " + error);
});

JSFiddle

Fetch

Fetch is a modern equivalent to XMLHttpRequest. It provides much more sane API for making HTTP requests. Fetch is still experimental but will be a future standard. Browser support is decent. Unsurprisinly IE does not support fetch but there is there is a polyfill which fixes the problem.

Usage is quite similar to jQuery. You still have to set the headers and convert the payload to JSON string manually. There are no shortcuts for different request methods. There also are some gotchas with error handling. For example a 404 Not Found response will not cause the promise to be rejected. Instead you have to check the response.ok property and throw an error yourself.

In example below I am still altering the DOM manually. This time only with vanilla JavaScript.

<p id="encoded">Encoding...</p>
<p id="decoded">Decoding...</p>
<p id="error"></p>
fetch("https://api.base62.net/encode", {
    method: "POST",
    headers: {
        "Content-Type": "application/json"
    },
    body: JSON.stringify({ data: "Hello world!" }),
    mode: "cors"
}).then(function(response) {
    if (response.ok) {
        return response.json();
    } else {
        throw new Error("Could not reach the API: " + response.statusText);
    }
}).then(function(data) {
    document.getElementById("encoded").innerHTML = data.encoded;
}).catch(function(error) {
    document.getElementById("error").innerHTML = error.message;
});

fetch("https://api.base62.net/decode", {
    method: "POST",
    headers: {
        "Content-Type": "application/json"
    },
    body: JSON.stringify({ data: "T8dgcjRGuYUueWht" }),
    mode: "cors"
}).then(function(response) {
    if (response.ok) {
        return response.json();
    } else {
        throw new Error("Could not reach the API: " + response.statusText);
    }
}).then(function(data) {
    document.getElementById("decoded").innerHTML = data.decoded;
}).catch(function(error) {
    document.getElementById("error").innerHTML = error.message;
});

JSFiddle

Axios With Vue.js

Axios is a promise based HTTP client for the browser and node.js. Usage is similar to fetch() with the exception that axios provides more developer friendly high level API. Data is converted automatically from and to JSON. Error handling has less surprises. Meaning 404 is considered an error.

Example below uses Vue for modifying the DOM. It uses declarative rendering. When value of app.encoded, app.decoded or app.error changes the HTML will also update automatically. For a small example like this the amount of code looks almost the same. In the long run, however framework like Vue will save you from the spaghetti code which will result from writing the UI code yourself.

<div id="app">
  <p>{{ encoded }}</p>
  <p>{{ decoded }}</p>
  <p>{{ error }}</p>
</div>
var app = new Vue({
    el: "#app",
    data: {
        encoded: "Encoding...",
        decoded: "Decoding...",
        error: null
    },
    created: function () {
        var self = this
        axios
            .post("https://api.base62.net/encode", {
                data: "Hello world!"
            })
            .then(function (response) {
                self.encoded = response.data.encoded
            })
            .catch(function (error) {
                self.error = "Could not reach the API: " + error
            })

        axios
            .post("https://api.base62.net/decode", {
                data: "T8dgcjRGuYUueWht"
            })
            .then(function (response) {
                self.decoded = response.data.decoded
            })
            .catch(function (error) {
                self.error = "Could not reach the API: " + error
            })
    }
});

JSFiddle

Slightly modified version of the above code is also live on base62.net.

Other Contenders

There are other contenders such as SuperAgent and qwest. I tried several but have narroved it down to either axios or fetch. I like the simplicity of axios. It is a perfect match for simplicity of Vue.

Posted in JavaScript  API  on 4 May 2017.