DEV Community

Cover image for A better way to use fetch api in Javascript
Mrinal Raj
Mrinal Raj

Posted on

5 2

A better way to use fetch api in Javascript

I have a kind of love-hate relationship with JavaScript. Nonetheless, it has always intrigued me. I have been working on it for the last 3 years now, watching all the design patterns and learning new ones each day.

What makes a design pattern? How it all starts and how do people start to code something in a specific manner? Facing a new challenge while trying to make something scalable. Yes, that’s the first thing that makes us think about implementation and after that most of the time, we find an already existing way to go about the problem.
This is going to be something similar.

Let’s understand how we generally write a fetch call in javascript.

try {
const request = await fetch('https://jsonplaceholder.typicode.com/todos/1')
const response = request.json()
// do something with the data
} catch (e) {
// show a message telling what went wrong
} finally {
// un-set the loader
}
view raw try-fetch.js hosted with ❤ by GitHub

Each of these function again returns a promise, which resolves to the type of data related to each of the functions.

In a real-world working project, there are numerous fetch calls, and every time if we start writing the above syntax we will simply end up writing so much of boilerplate codes.

As it is said in programming world. Never repeat yourself.

Let’s try to write a wrapper to make GET request. This wrapper function will help us keep the headers always same for all the requests and also make sure to keep the base API_URL consistent throughout the app.

const getRequestTo = async (endpoint, data) => {
try {
const httpQuery = queryString(data) // convert params object to query string
let request = await fetch(API_URL + endpoint + httpQuery, headers })
const response = await request.json()
return response
} catch (e) {
throw new Error(e)
}
}
// Now this is how we will use this function
try{
let response = await getRequestTo('todos',{ pageNumber:1 })
}
catch(e){
// handle error
}

Here the wrapper itself returns a promise, so we achieved some of the cases that need not be repeated, but I am still not satisfied with writing all the try/catch blocks.

This case reminds me of the syntax used in golang, which goes something like following.

err, res := myFunction()

Here either err or res has a null value, depending on if there is an error. We will try and implement a RequestBuilder function that exposes a similar function that returns an array in the structure of [error, response] .

cosnt RequestBuilder = (BaseUrl, options, interceptor) => {
const API_URL = BaseUrl || ''
const this_options = options || {}
const this_interceptor = interceptor || (request => request.json())
// function makes an api call, any logs for the apis logs can be attached here
const makeRequest = async (endpoint, options) => {
const [error, response] = await fetch(endpoint, options)
.then(async response => [null, await this_interceptor(response)])
.catch(error => [error, null])
return [error, response]
}
return {
get: async (url, params, options = {}) => {
const endpoint = `${API_URL}${url}${params ? `?${queryString(params)}` : ''}`,
method = 'GET'
return makeRequest(endpoint, { method, ...options, headers: this_options.headers(method, url) })
},
post: async (url, data, params, options = {}) => {
const endpoint = `${API_URL}${url}${params ? `?${queryString(params)}` : ''}`,
method = 'POST'
return makeRequest(endpoint, {
method,
headers: this_options.headers(method, url),
...(data && { body: JSON.stringify(data), ...options }),
})
},
}
}
// Example Usage
const intercept = async request => {
const { status } = request
switch (true) {
case status === 419:
throw new Error((await request.json()).message)
case status === 400:
throw new Error((await request.json()).message)
case status === 401:
throw new Error((await request.json()).message)
case status === 404:
throw new Error((await request.json()).message || 'Not found')
case status === 500:
throw new Error('Something went wrong')
case status === 204:
return { status: 'OK' }
default:
return request.json()
}
}
const JWTHeaders = () => {
const authorizationToken = CookieStore.get(keys.TOKEN)
return {
'content-type': 'application/json',
X_DEVICE_ID: 'Web',
X_AUTH_REQUEST_TYPE: 'jwt_consumer',
Authorization: authorizationToken ? `Bearer ${authorizationToken}` : null,
}
}
const authRequest = RequestBuilder(apiBaseUrl, { headers: JWTHeaders }, intercept)

Now let’s see what we are doing here. The instance takes in BaseUrl, options, interceptor as arguments. The BaseUrl is clearly the base API URL needed by the app. The options is an object that is passed as the options to the fetch function. The last one is a function that implements the checks on the status and returns response accordingly.

Now using this authRequest object is very easy and also eliminates try/catch to make our code way cleaner. Here is how.

setLoading(true)

const [error, response] = await authRequest.get(endpoint)

if (error) {
    // handle error
} else {
    // handle success
}

setLoading(false)

Let me know how this article helped you in the comments below.

Happy coding.

Thanks to Pankaj for suggesting the topic.

SurveyJS custom survey software

Build Your Own Forms without Manual Coding

SurveyJS UI libraries let you build a JSON-based form management system that integrates with any backend, giving you full control over your data with no user limits. Includes support for custom question types, skip logic, an integrated CSS editor, PDF export, real-time analytics, and more.

Learn more

Top comments (0)

Image of Docusign

🛠️ Bring your solution into Docusign. Reach over 1.6M customers.

Docusign is now extensible. Overcome challenges with disconnected products and inaccessible data by bringing your solutions into Docusign and publishing to 1.6M customers in the App Center.

Learn more