DEV Community

Alexander Kim
Alexander Kim

Posted on • Updated on

My Fetch Wrapper with async/await and TypeScript

Posting my wrapper for a native fetch() API:

const API_URL: string = process.env.YOUR_ENV_NAME || 'https://example.com';

export default async <T, B>(
  url: string,
  method = 'get',
  body: B | undefined = undefined,
  headers = {}
): Promise<T | { error: string }> => {
  const controller = new AbortController();
  try {
    const res = await fetch(`${API_URL}${url}`, {
      method: method.toUpperCase(),
      signal: controller.signal,
      body: typeof body === 'object' ? JSON.stringify(body) : undefined,
      mode: 'cors',
      headers: {
        'Content-type': 'application/json',
        ...headers
      }
    });
    if (!res.ok) {
      const error = await res.json();
      return { error: error.code };
    }
    return await res.json();
  } catch (err) {
    return { error: err };
  } finally {
    controller.abort();
  }
};

Enter fullscreen mode Exit fullscreen mode

And we can use it this way:

const result = await api<IResponse, IBody>('url', 'post', { name: 'asd' });
if (result.error) {
  // handle error;
} else {
  // handle successful response
}
Enter fullscreen mode Exit fullscreen mode

We can type our response as 1st type argument, and body as 2nd.

I wrote it to use in my React app. Improvements of this code snippet are welcome!

Top comments (7)

Collapse
 
tomekbuszewski profile image
Tomasz Buszewski

Hey, very nice! A few remarks:

  • this function has a lot of arguments, perhaps try to use an object merged with default values;
  • method should be an enum, otherwise typos are welcome (path instead of patch for example);
  • it would be nice if you'd provide a structured response with status code – sometimes it makes a difference;
  • body shouldn't be allowed on get requests.

There are very minor things, no deal-breakers. Great job!

Collapse
 
avxkim profile image
Alexander Kim • Edited

Thank you for your tips, definitely need to improve this.

Collapse
 
patarapolw profile image
Pacharapol Withayasakpunt

POST is OK, but GET shouldn't have a response body. You should use querystrings.

Collapse
 
avxkim profile image
Alexander Kim

Yeah, that's why i set body to undefined by default, when calling this wrapper you also can omit a body.

Collapse
 
patarapolw profile image
Pacharapol Withayasakpunt • Edited

You still have application/json header, which is affected by CORS.

Now, my goto is axios, which auto-detect body type

  • GET + { params: Object } => will convert URL to querystring. No header needed
  • POST + { data: Object } => application/json
  • POST + { data: Stringified query string } => application/x-www-form-urlencoded

Axios also allowes raise_for_status and error interceptors.

However, axios uses XMLHttpRequest. Not sure if this matters.

Thread Thread
 
avxkim profile image
Alexander Kim • Edited

Well, i've used axios with Vue a lot, it's more convenient, tbh. But it's an additional dependency, also they are not actively working on currently stalled issues in their repo. Axios has many neat features built-in. But i wonder about the future of this project.

Collapse
 
jeansmaug profile image
jean-smaug

Nice !

For those who want a wrapper around fetch there is Ky ;)