I have created a wrapper for Fetch to send requests to my API. The module exports api
, and requests can be made with:
api.get({ url: '/users' })
or
api.post({ url: '/users', data: {id: 1, name: myName} })
After receiving the HTTP response from fetch
I do some additional tasks, like update a token, then extract the JSON, camelCase the JSON keys, then return the JSON.
I'm mostly interested in a review of the api
export, and handle errors in the fetch
request.
const baseURL = 'http://mydomain.com/'
const csrfTokenExtractor = (response) => {
const token = response.headers['x-csrf-token']
if (token) {
document.querySelector('meta[name=csrf-token]').setAttribute('content', token)
}
return response
}
const csrfHeader = () => {
const token = document.querySelector('meta[name=csrf-token]').getAttribute('content')
return {
'X-CSRF-Token': token,
}
}
const defaultHeader = () => {
return {
'X-App-Component': 'app',
}
}
const sessionDetector = (response) => {
if (response.status === 401) {
window.location.replace(response.data.url)
}
return response
}
const buildURLQuery = (obj) =>
Object.entries(obj)
.map((pair) => pair.map(encodeURIComponent).join('='))
.join('&')
const request = async ({ url, method, ...params }) => {
params.credentials = 'same-origin'
params.headers = Object.assign({}, params.headers || {}, defaultHeader())
params.method = method
if (method !== 'GET') {
params.headers = Object.assign({}, params.headers || {}, { 'Content-Type': 'application/json' }, csrfHeader())
}
let response
try {
response = await fetch(`${baseURL}${url}`, {
...params })
if (!response.ok) {
console.error(response)
throw response
}
} catch (error) {
sessionDetector(error)
console.error(error)
throw error
}
await csrfTokenExtractor(response)
const json = await response.json()
const formattedJson = camelcaseKeys(json, { deep: true })
return formattedJson
}
const api = {
get: ({ url, formData = {} }) => {
const options = {
method: 'GET',
mode: 'cors',
url: `${url}?${buildURLQuery(formData)}`,
}
return request(options)
},
post: ({ url, data = {} }) => {
const options = {
method: 'POST',
url,
body: JSON.stringify(data),
}
return request(options)
},
put: ({ url, data = {} }) => {
const options = {
method: 'PUT',
url,
body: JSON.stringify(data),
}
return request(options)
},
delete: ({ url }) => {
const options = {
method: 'DELETE',
url,
}
return request(options)
},
request,
}
export default api
1 Answer 1
A few things stood out to me:
You can extract out the implementation details behind where the token exists. Not a huge deal, but personally I would do this:
const setAuthToken = (token) => document.querySelector('meta[name=csrf-token]').setAttribute('content', token) const getAuthToken = () =>document.querySelector('meta[name=csrf-token]').getAttribute('content')
I don't think you should throw an error for a non-
ok
response. It's not a fatal error, so I don't think you should treat it as such.if (!response.ok) { console.error(response) // throw response return Promise.reject(response) }
Be careful with the headers. The
fetch
api can also accept aHeaders
object and you can't merge it in withObject.assign
.Headers
is iterable, which is nice so you can use afor...of
loop to manually extract the keys and set them into your object
-
\$\begingroup\$ Regarding #2: I'm no expert in Promises, but don't
throw response
andreturn Promise.reject(response)
do exactly the same thing in this context? \$\endgroup\$RoToRa– RoToRa2020年07月16日 08:05:01 +00:00Commented Jul 16, 2020 at 8:05 -
\$\begingroup\$ @RoToRa In most situations, yes they are functionally the same. But the expressiveness and intent are different. While this is starting to creep into a philosophical discussion, should a successful api call with an undesirable response from a 3rd party be considered a fatal error on the callee's part? In all honesty, I wouldn't even consider throwing or rejecting in an
ok === false
condition. There should just be user feedback stating something went wrong. \$\endgroup\$Andrew– Andrew2020年07月16日 19:10:45 +00:00Commented Jul 16, 2020 at 19:10