If you're the type of person to read technical Javascript posts in your free time (you are), you don't need me to tell you that JQuery is dead. JQuery themselves have proclaimed JQuery to be dead. The only cool thing about JQuery is who can remove it from their legacy stack the fastest, which begs the question: why is the third most popular page on this site an old post about JQuery?

Maintaining a blog of tutorials has taught me a lot about the gap between perception and reality. While we content publishers sling Medium posts from our ivory towers, we quickly create a perception of what "everybody" is doing, but it turns out "everybody" only includes individuals who are exceptionally visible. That demographic makes up significantly less than 10-20% of the active workforce. I would have assumed any post with the word "React" would immediately explode, when in reality people are more interested in using Handlebars with ExpressJS (I'm not proud of that post by the way, please don't read it).

I want to provide an alternative to using AJAX calls when interacting with REST APIs to clear my conscious of ever enabling bad behavior in the first place. Hopefully, those who have lost their way might find something to take from it. Considering how deep I've gone down the GraphQL rabbit hole myself, this may be the last chance to bother writing about REST at all.

Library of Choice: node-fetch

Like everything in Javascript, there are way too many packages doing the same thing and solving the same problem. Making API requests is no exception. http is a bit primitive, request breaks when building with Webpack, r2 seems like a pointless clone, and so on. Don't get me started with async libraries with 40 different methods for chaining requests. Who is gunslinging API requests to the point where we need this many options to pipe or parallel API requests anyway?

After using all of these libraries, node-fetch is the weapon of choice for today. To put it simply: it's straightforward, and the only one that actually works out of the box with Webpack without absurd configuration nonsense.

The other request library worth mentioning is isomorphic-fetch, which is intended to be a drop-in replacement for node-fetch. isometric-fetch mimics the syntax of node-fetch, but impressively works on both the client and server-side. When used on the client side, isomorphicfetch works by first importing the es6-promise polyfill.

Getting Set Up

Start a Node project and install node-fetch:

npm install --save node-fetch
Install node-fetch

In the JS file we'd like to make a request, we can reference node-fetch using require():

const fetch = require('node-fetch');
index.js

Creating a node-fetch Request

We'll start with the most basic GET request possible:

const fetch = require('node-fetch');

fetch('https://example.com')
  .then(response => response.json())
  .then(data => {
    console.log(data)
  })
  .catch(err => ...)
index.js

Indeed, that's all it takes a base level. Without specifying a method, node-fetch assumes we're making a GET request. From we generate JSON from the request body and print the result to the console.

Chances are you're not going to get much value out of any request without passing headers, parameters, or a body to the target endpoint. Here's how we'd make a more complicated (and realistic) POST call:

const url ='https://example.com';
const headers = {
  "Content-Type": "application/json",
  "client_id": "1001125",
  "client_secret": "876JHG76UKFJYGVHf867rFUTFGHCJ8JHV"
}
const data = {
  "name": "Wade Wilson",
  "occupation": "Murderer",
  "age": "30 (forever)"
}

fetch(url, { method: 'POST', headers: headers, body: data})
  .then((res) => {
     return res.json()
})
.then((json) => {
   // Do something with the returned data.
  console.log(json);
  
});
index.js

That's more like it: now we're passing headers and a JSON body. If needed, the fetch() method also accepts a credentials parameter for authentication.

Note that we are avoiding callback hell by keeping logic that utilizes the response JSON in our then() arrow functions. We can chain together as many of these statements as we want.

Properties of a Response

The response object contains much more than just the response body JSON:

fetch('https://example.com')
.then(res => {
  res.text()       // response body (=> Promise)
  res.json()       // parse via JSON (=> Promise)
  res.status       //=> 200
  res.statusText   //=> 'OK'
  res.redirected   //=> false
  res.ok           //=> true
  res.url          //=> 'https://example.com'
  res.type         //=> 'basic'
                   //   ('cors' 'default' 'error'
                   //    'opaque' 'opaqueredirect')
  res.headers.get('Content-Type')
})
Attributes of response

res.status is particularly handy when building functionality around catching errors:

fetch('https://example.com').then(checkStatus)
  
function checkStatus (res) {
  if (res.status >= 200 && res.status < 300) {
    return res
  } else {
    let err = new Error(res.statusText)
    err.response = res
    throw err
  }
}
index.js

Making Asynchronous Requests

Chances are that when we make an API request, we're planning to do something with the resulting data. Once we start building logic which depends on the outcome of a request, this is when we start running into Callback Hell: perhaps the worst part of JavaScript. In a nutshell, JavaScript will not wait for a request to execute the next line of code, therefore making a request and referencing it immediately will result in no data returned. We can get around this by using a combination of async and await.

async is a keyword which denotes that a function is to be executed asynchronously (as in async function my_func(){...}). await can be used when calling async functions to wait on the result of an async function to be returned (ie: const response = await my_func()).

Here's an example of async/await in action:

const fetch = require("node-fetch");

const url = "https://example.com";

const get_data = async url => {
  try {
    const response = await fetch(url);
    const json = await response.json();
    console.log(json);
  } catch (error) {
    console.log(error);
  }
};

getData(url);
index.js