Preface

Goal: A class example to fetch JSON using ES async/await.

We are going to run this ecmascript in nodejs in CLI mode.

Source Examples

You can obtain source examples here:

What Standard 🤔?

Standard being used in this article.

  1. Fat Arrow: ES 2015 (ES6)
  2. Classes: ES 2015 (ES6)
  3. Const: ES 2015 (ES6)
  4. Template Strings: ES 2015 (ES6)
  5. Async Function: ES 2017 (ES8)
  6. Fetch: WHATWG

Fetch is not an ecmacscript standard. This is why in NodeJS you need an additional node-fetch package.


JSON Provider

JSON is usually utilized to access API. The API utilization increasing with JAM stack. To start learning we need an online API example in JSON format.

Fake REST API

For tutorial purpose, We are going to use jsonplaceholder: A fake online REST API for testing and prototyping

The first example copied from that site above is below code:

fetch('https://jsonplaceholder.typicode.com/posts/1')
  .then((response) => response.json())
  .then((json) => console.log(json))

Example Fetch API Usage

There are about six example from above site:

  1. Getting a resource
  2. Listing all resources
  3. Creating a resource
  4. Updating a resource with PUT
  5. Updating a resource with PATCH
  6. Deleting a resource

Since we are going to use JSON from jsonplaceholder, we are also going to use these pattern.


1: Simple Promise

Preparing: Install

Since fetch is not available in nodejs, we require to install node-fetch package in local folder.

❯ npm i -s -D node-fetch
+ node-fetch@2.6.1
updated 1 package and audited 99 packages in 1.978s

Node Fetch: NPM Install

Getting a Resource

Consider rewrite the provided example as ES6 below:

// Getting a resource
const fetch   = require("node-fetch")
const baseURL = 'https://jsonplaceholder.typicode.com'
const id      = 1

fetch(`${baseURL}/posts/${id}`)
fetch(baseURL+'/posts/1')
  .then(response => response.json())
  .then(json => console.log(json))

We are going to get the result, already in JSON format as below:

$ node 01-01-read-id.js
{
  userId: 1,
  id: 1,
  title: 'sunt aut facere repellat provident occaecati excepturi optio reprehenderit',
  body: 'quia et suscipit\n' +
    'suscipit recusandae consequuntur expedita et cum\n' +
    'reprehenderit molestiae ut ut quas totam\n' +
    'nostrum rerum est autem sunt rem eveniet architecto'
}

Simple Promise: Getting a Resource

This used javascript promise. It means it run in synchronous manner.

Fetch Options

Options can be attached to a fetch call. For example this update below:

// Updating a resource with PUT
const fetch   = require("node-fetch")
const headers = { 'Content-type': 'application/json; charset=UTF-8' }
const baseURL = 'https://jsonplaceholder.typicode.com'
const id      = 1

fetch(`${baseURL}/posts/${id}`, {
  method: 'PUT',
  body: JSON.stringify({
    id: 1,
    title: 'foo',
    body: 'bar',
    userId: 1,
  }),
  headers: headers
})
  .then((response) => response.json())
  .then((json) => console.log(json))

This has these three options:

  1. Method: might be GET, POST, PUT, PATCH, or DELETE.
  2. Body: The data.
  3. Headers: Using JSON.

There are more options that you can read:

And The Rest …

I do not think I have to pour them all in a blog, since it is already explained in original jsonplaceholder site anyway. However you can have a look at all the codes in my repository.

  1. Promised: Getting a resource
  2. Promised: Listing all resources
  3. Promised: Creating a resource
  4. Promised: Updating a resource with PUT
  5. Promised: Updating a resource with PATCH
  6. Promised: Deleting a resource

2: Asynchronus Await

Reading Reference

There is a better explanation about await here:

I do not have to repeat myself.

Fetch Using Async/Await

Consider rewrite the provided the code above in asynchronous manner. This time using knowledge from article above.

// Getting a resource
const fetch   = require("node-fetch")
const baseURL = 'https://jsonplaceholder.typicode.com'
const id      = 1

const request = async () => {
  const response = await fetch(`${baseURL}/posts/${id}`)
  const json = await response.json()
  return json
}

request()
  .then(data => console.log(data))
  .catch(reason => console.log(reason.message))

This code looks fine right?

Try … Catch

What’s the catch?

We cannot just put the code above in try/catch clause, without proper understanding of asynchronous process.

Instead of returning value, we can use function straight away, and let the function handle the response.

// Getting a resource
const fetch   = require("node-fetch")
const baseURL = 'https://jsonplaceholder.typicode.com'
const id      = 1
const show    = json => console.log(json)

const request = async () => {
  try {
    const response = await fetch(`${baseURL}/posts/${id}`)
    const json = await response.json()
    show(json)
  } catch(err) {
    console.log(err.message)
  }
}

request()

Consider turn of the wifi, and experience the error message:

$ node 02-01-read-id.js
request to https://jsonplaceholder.typicode.com/posts/1 failed, reason: getaddrinfo EAI_AGAIN jsonplaceholder.typicode.com

Asynchronus Await: Getting a Resource

Apply This Pattern

With the same pattern we can rewrite all the codes. For example this update below:

// Updating a resource with PUT
const fetch   = require("node-fetch")
const headers = { 'Content-type': 'application/json; charset=UTF-8' }
const baseURL = 'https://jsonplaceholder.typicode.com'
const id      = 1
const show    = json => console.log(json)

const options = {
  method: 'PUT',
  body: JSON.stringify({
    id: 1,
    title: 'foo',
    body: 'bar',
    userId: 1,
  }),
  headers: headers
}

const request = async () => {
  try {
    const response = await fetch(`${baseURL}/posts/${id}`, options)
    const json = await response.json()
    show(json)
  } catch(err) {
    console.log(err.message)
  }
}

request()

And The Rest …

I do not think I have to pour them all in a blog, since you can have a look at all the codes in my repository anyway. Each script can be shown as below, along with the result.

  1. Asynchronus: Getting a resource

  2. Asynchronus: Listing all resources

  3. Asynchronus: Creating a resource

$ node 02-03-create.js
{ title: 'foo', body: 'bar', userId: 1, id: 101 }
  1. Asynchronus: Updating a resource with PUT
$ node 02-04-update.js
{ id: 1, title: 'foo', body: 'bar', userId: 1 }
  1. Asynchronus: Updating a resource with PATCH
$ node 02-05-patch.js
{
  userId: 1,
  id: 1,
  title: 'foo',
  body: 'quia et suscipit\n' +
    'suscipit recusandae consequuntur expedita et cum\n' +
    'reprehenderit molestiae ut ut quas totam\n' +
    'nostrum rerum est autem sunt rem eveniet architecto'
}
  1. Asynchronus: Deleting a resource
$ node 02-06-delete.js

Asynchronus Await: All Result

The last one is delete. This will show nothing.


3: Class Wrapper

So many file, so little differences.

The six source code above, have a lot in common. We can manage all this code into one class.

Constructor

We can start with constructor. And place common variable over there.

const fetch = require("node-fetch")

class PlaceHolder {
  constructor() {
    this.baseURL = 'https://jsonplaceholder.typicode.com'
    this.headers = { 'Content-type': 'application/json; charset=UTF-8' }
  }

  
}

Private Method

This is the core of our class. We can have async function in a class.

  // Private Method (using # at tc39)
  async _request(url, options = null) {
    try {
      const response = options === null ?
        await fetch(url) : await fetch(url, options)
      const json = await response.json()
      return json
    } catch(err) {
      console.log(err.message)
    }
  }

This way, we can call the _request, or each public method later, in a simple way.

  read(id) {
    return this._request(`${this.baseURL}/posts/${id}`)
  }

And then we call the method later with:

placeholder.read(1)
  .then(json => console.log(json))

Most of the time, I do not know what later implementation, will be working on the data. I’d better keep these methods in returned fashioned, and decide what to do with it later.

Public Method

We have six public method. All five are here.

  // Public Method
  readAll() {
    return this._request(`${this.baseURL}/posts`)
  }

  read(id) {
    return this._request(`${this.baseURL}/posts/${id}`)
  }

  create(body) {
    const options = {
      method: 'POST',
      body: JSON.stringify(body),
      headers: this.headers
    }
    return this._request(`${this.baseURL}/posts`, options)
  }

  update(id, body) {
    const options = {
      method: 'PUT',
      body: JSON.stringify(body),
      headers: this.headers
    }
    return this._request(`${this.baseURL}/posts/${id}`, options)
  }

  patch(id, body) {
    const options = {
      method: 'PATCH',
      body: JSON.stringify(body),
      headers: this.headers
    }
    return this._request(`${this.baseURL}/posts/${id}`, options)
  }

Delete Method

The choice is yours.

The last is the delete method. Depends on your requirement, this can run a callback, or just do nothing. This example show different pattern for delete function. Thus, using its own method, instead of using already made this._request method.

  async delete(id) {
    try {
      const url = `${this.baseURL}/posts/${id}`
      const response = await fetch(url, { method: 'DELETE' })
      const json = await response.json()
    } catch(err) {
      console.log(err.message)
    }
  }

The code management is depend on your requirement. Mine might different with yours.

Example Usage

First we must make a new class instance.

const placeholder = new PlaceHolder()

Then we can call each different method, that might have different data.

  1. Usage: Getting a resource
placeholder.readAll()
  .then(json => console.log(json))
  1. Usage: Listing all resources
placeholder.read(1)
  .then(json => console.log(json))
  1. Usage: Creating a resource
const body = { title: 'foo', body: 'bar',  userId: 1 }
placeholder.create(body)
  .then(json => console.log(json))
  1. Usage: Updating a resource with PUT
const body = { id: 1, title: 'foo', body: 'bar',  userId: 1 }
placeholder.update(1, body)
  .then(json => console.log(json))
  1. Usage: Updating a resource with PATCH
const body = { id: 1, title: 'foo' }
placeholder.patch(1, body)
  .then(json => console.log(json))
  1. Usage: Deleting a resource
placeholder.delete(1)

This last one won’t return anything.

Alternative Usage

You can also write down in asynchronous fashioned:

const request = async () => { return await placeholder.read(1) }
request().then(json => console.log(json))

But I still do not know, if there is any practical reason to do this.


4: Callback in Class

Sometimes I failed 😁.

I have managed other way to show all the class, without using .then(…) over and over again.

Callback Method

You can skip this entire useless part.

Turning above function into call back is not a hard thing to do.

class PlaceHolder {
  constructor(callback=null) {
    this.baseURL = 'https://jsonplaceholder.typicode.com'
    this.headers = { 'Content-type': 'application/json; charset=UTF-8' }
    this.callback = callback
  }

  // Private Method (using # at tc39)
  _doCallback(json, response) {
    if (this.callback) this.callback(json, response)
  }
  
  async _request(url, options = null) {
    try {
      const response = options === null ?
        await fetch(url) : await fetch(url, options)
      const json = await response.json()
      this._doCallback(json, response)
    } catch(err) {
      console.log(err.message)
    }
  }

  
}

Example Usage

Now we can instantiate the class as below:

const placeholder = new PlaceHolder( (json, response) => { 
  if (response.status == '200') {
    console.log(json)
  } else {
    console.log(`status [${response.status}]`)
  }
});

const body = { id: 1, title: 'foo' }
placeholder.patch(1, body)

With the result as below:

$ node 03-02-class.js
{
  userId: 1,
  id: 1,
  title: 'foo',
  body: 'quia et suscipit\n' +
    'suscipit recusandae consequuntur expedita et cum\n' +
    'reprehenderit molestiae ut ut quas totam\n' +
    'nostrum rerum est autem sunt rem eveniet architecto'
}

Error Response

Consider test our callback.

placeholder.read(1000)

This will show

$ node 03-02-class.js
status [404]

What Failure?

The method must return data asynchronously to be used later.

Yes, this code looks fine, at the first sight. But this concept doesn’t have any practical application in real project. I still keep the code for personal reason, but I simply drop the whole idea.

Finally

That is all with fetch.


5: Axios Alternative

There is however other library. A very popular one, the axios library.

We can rewrite the above script as below:

// Getting a resource
const axios   = require('axios').default;
const baseURL = 'https://jsonplaceholder.typicode.com'
const id      = 1
const url     = `${baseURL}/posts/${id}`

axios.get(url)
  .then( response => console.log(response.data) )
  .catch( error   => console.log(error) )

Or if you wish for a more complete options example:

// Updating a resource with PUT
const axios   = require('axios').default;
const headers = { 'Content-type': 'application/json; charset=UTF-8' }
const baseURL = 'https://jsonplaceholder.typicode.com'
const id      = 1
const url     = `${baseURL}/posts/${id}`

const data    = { id: 1, title: 'foo', body: 'bar',  userId: 1 }

axios.put(url, data)
  .then( response => console.log(response.data) )
  .catch( error   => console.log(error) )

With about the same result.

And The Rest …

There are already so many axios tutorial. So I do not think I have to pour them all in a blog either.

  1. Axios: Getting a resource
  2. Axios: Listing all resources
  3. Axios: Creating a resource
  4. Axios: Updating a resource with PUT
  5. Axios: Updating a resource with PATCH
  6. Axios: Deleting a resource

Which one?

Native asynchronous fetch() or Axios.

I must say: I do not know.


Conclusion

Fetching JSON is not as hard as thought. It is become easier now since you have already had a working example.

What do you think?