skip to Main Content

This is how I handle fetch now following the next’s document.
https://nextjs.org/docs/app/building-your-application/data-fetching/fetching-caching-and-revalidating#fetching-data-on-the-server-with-fetch

async function getSuite(propertyId: string, suiteId: string) {
  const cookieStore = cookies();
  const accessToken = cookieStore.get("accessToken");

  const res = await fetch(
    `${process.env.NEXT_PUBLIC_API_BASE_URL}/suite`,
    {
      headers: {
        "X-Authorization": "Bearer " + accessToken?.value,
      },
    }
  );

  if (!res.ok) {
    throw new Error("Failed to fetch data");
  }

  const suite: Suite=
    await res.json();

  return suite;
}

async function getSensorData(deviceId: string) {
  const cookieStore = cookies();
  const accessToken = cookieStore.get("accessToken");

  const res = await fetch(
    `${process.env.NEXT_PUBLIC_API_BASE_URL}/sensors`,
    {
      headers: {
        "X-Authorization": "Bearer " + accessToken?.value,
      },
    }
  );
  if (!res.ok) {
    throw new Error("Failed to fetch data");
  }

  const sensors: Sensor[]  =
    await res.json();

  return sensors;
}

async Function ServerCompoment(){
  const suite = await getSuite();
  const sensors = await getSensors();
}

However, I found that I keep repeating the same code such as the baseURL, error handling and Authorization. I am trying to figure out the best practise. The only thing I can think of is to create a wrapper function. How do you guys handle fetch in server component?

2

Answers


  1. I just wrote a wrapper for this purpose a few days ago.

    I found it most useful to separate it into two steps, an HTTP Method specific helper and a generic fetch helper.

    Example for GET request helper:

    async function get(path: string, tags?: string[], revalidate = 60) {
      const request = new Request(`${baseUrl}${path}`, {
        headers: {
          'Content-Type': 'application/json',
        },
        method: 'GET',
      });
      return fetchWrapper(request, tags, revalidate);
    }
    

    Then it calls this generic fetch wrapper (shared by the post, put, destroy helpers)

    async function fetchWrapper(request: Request, tags?: string[], revalidate?: number) {
      const res = await fetch(request, { next: { tags, revalidate } });
      // The return value is *not* serialized
      // You can return Date, Map, Set, etc.
    
      if (!res.ok) {
        // This will activate the closest `error.js` Error Boundary
        throw new Error(`Failed to fetch data: ${JSON.stringify(await res.json())}`);
      }
    
      // We already have a successful response
      // the body is usually JSON,
      // but the response may have no body so return an empty object
      return res.json().catch(() => ({}));
    }
    

    I export all 4 helpers as an api object making the final usage something like

    await api.get("/my-route", ["GET_MY_ROUTE_TAG"], 3600);
    

    There may be limitations to this implementation that I have not encountered yet, but it seems to work well so far. This implementation allows you to use Next.js features for manual and time-based revalidation.

    Your authentication code could go in the fetchWrapper.

    Login or Signup to reply.
  2. I use a very similar approach as Brian, and want to provide it as an alternative just in case it’s helpful. This is a function that can be called from anywhere and uses .then instead of awaiting.

    function CallApi(url: string, callType: string, data?: string){
      const abortController = new AbortController();
    
      return fetch(url, {
        signal: abortController.signal,
        method: callType,
        headers: { 'Content-Type': 'application/json' },
        body: callType === 'POST' || callType === 'PUT' ? data : null,
    })
    .then(result => {
      if (result.status === 200 || result.status ===201) {
        return result.text();
      } else if (result.status === 401) {
        window.location.reload();
      } else if (result.status === 503 || result.status === 500) {
        {/* set your desired action here. I retry 3 times and then push an error if the issue continues */}
      }
      else if (!result.ok) {
        {/* again, set your desired action here */}
      }
    })
    .then(returnData => {
      return (returnData ? JSON.parse(returnData) : {});
    })
    .catch(error => {
      console.error(error.message);
    });
    

    }

    This function can then be called from anywhere and reused with three simple parameters (url, callType, and data if needed). For the call, if you need return data, you can do something like this:

    CallApi(myUrl, 'GET').then(returnData => {
      setSomeState(returnData)
    }
    

    That’s it! Works very smoothly for me, and can be modified to fit your needs.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search