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 || '';

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',
    if (!res.ok) {
      const error = await res.json();
      return { error: error.code };
    return await res.json();
  } catch (err) {
    return { error: err };
  } finally {

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)

tomekbuszewski profile image
Tomek 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!

avxkim profile image
Alexander Kim • Edited

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

patarapolw profile image
Pacharapol Withayasakpunt

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

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.

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.

jeansmaug profile image

Nice !

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