4
\$\begingroup\$

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
\$\endgroup\$
3
\$\begingroup\$

A few things stood out to me:

  1. 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')
  1. 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)
}
  1. Be careful with the headers. The fetch api can also accept a Headers object and you can't merge it in with Object.assign. Headers is iterable, which is nice so you can use a for...of loop to manually extract the keys and set them into your object

https://developer.mozilla.org/en-US/docs/Web/API/Headers

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.