Lost on data fetching in a server component

Hi, all,

Happy 2024!

I am asking for help as I am lost on how to fetch data on the server side.

Referring to the LastestIssue component, I know directly getting data using prisma works. But I do not like it because:

  • it skips the api
  • no error handling

I tried to implement my own data fetching using fetch and axios, but was unsuccessful.

import axios from "axios";

const LatestIssues = async () => {
  // Fetch data using axios
  // Make sure 
  //  - use the full url
  //  - no `/` at the end of the url
  const response = await axios.get("http://localhost:3000/api/issues")
  const issues = response.data;

  return (
    <>
      <div>Latest Issues</div>
      <ul>
        {issues.map((issue) => (
          <li key={issue.id}>{issue.title}</li>
        ))}
      </ul> 
    </>
  );
};

export default LatestIssues;

While data is fetched, it can not be rendered because of the async nature of data fetching. issues are not available for rendering.

We usually solve this on the client side using useEffect and useState to set up a loading state. But this is not possible unless I change this component into a client side component.

I also looked into using getServerSideProps(), but it works only with files in `/pages’, and I think with this tutorial we are moving away from the old page routing.

So, is there a way to keep this as a server component and fetch data from API using fetch / axios?

Many thanks, and happy learning!

Sawfiz

Hi, all,

After some research, I have come to some clarity.

Next.js app router vs. page router

With the introduction of the new Next.js app router, many things are simplified.

  • no more use of @/app/pages
  • special files, e.g, pages. tsx, error.tsx, loading.tsx, etc.
  • much simpler data fetching
    – components can be `async’
    – directly fetch data using prisma, axios, fetch

Recommendation from Next.js

Whenever possible, we recommend fetching data on the server with Server Components. This allows you to:

  • Have direct access to backend data resources (e.g. databases).
  • Keep your application more secure by preventing sensitive information, such as access tokens and API keys, from being exposed to the client.
  • Fetch data and render in the same environment. This reduces both the back-and-forth communication between client and server, as well as the work on the main thread on the client.
  • Perform multiple data fetches with single round-trip instead of multiple individual requests on the client.

My understanding is, it is recommended to fetch data using prisma.

I list below 3 ways to fetch data in the case of LatestIssues

Fetching using Prisma

[!success] Pro

  • This is the simplest way when the backend had direct access to the database.
  • It is also the recommended way as without going through the API, it is faster and more secure.
  • Also, there is no need to explicitly define the type of issues

[!failure] Con

  • Only works when the backend had direct access to the database.
import prisma from "@/prisma/client";

const LatestIssues = async () => {
  // Fetch using Prisma
  const issues = await prisma.issue.findMany({
    orderBy: { createdAt: "desc" },
    take: 5,
  });

  return (
    <>
      <div>Latest Issues</div>
      <ul>
        {issues.map((issue) => (
          <li key={issue.id}>{issue.title}</li>
        ))}
      </ul>
    </>
  );
};

Note:
The data fetching can not be put into a try...catch block, doing so will make issues inaccessible in return

Fetching using axios

[!success] Pro

  • Works with external APIs

[!failure] Con

  • Need to add explicit type definition of issues
  • Complex URL, easy to make mistakes
import axios from "axios";

const LatestIssues = async () => {
  // Fetch data using axios
  // Make sure 
  //  - use the full url
  //  - no `/` at the end of the url
  const response = await axios.get(
    "http://localhost:3000/api/issues?orderBy=updatedAt&sort=desc&page=1&pageSize=5"
  );
  const issues: Issue[] = response.data.issues;

  return (
    <>
      <div>Latest Issues</div>
      {/* <ul>
        {issues.map((issue) => (
          <li key={issue.id}>{issue.title}</li>
        ))}
      </ul> */}
    </>
  );
};

export default LatestIssues;

Fetch using fetch

[!success] Pro

  • Works with external APIs

[!failure] Con

  • Need to parse json and deconstruct res
  • Type definition of issues is more complex
  • Complex URL, easy to make mistakes
import {Issue} from '@prisma/client'

const LatestIssues = async () => {
  // Fetch data using fetch
  // Make sure 
  //  - use the full url
  //  - no `/` at the end of the url
  const res = await fetch(
    "http://localhost:3000/api/issues?orderBy=updatedAt&sort=desc&page=1&pageSize=5"
  );
  // Deconstruct issues.issues
  const { issues }: { issues: Issue[] } = await res.json();

  return (
    <>
      <div>Latest Issues</div>
      <ul>
        {issues.map((issue) => (
          <li key={issue.id}>{issue.title}</li>
        ))}
      </ul>
    </>
  );
};

export default LatestIssues;

Outstanding issues

  1. For some reason, I can not use orderBy=createdAt in the urls used in axios and fetch. Other fields, e.g., status and updatedAt work…
  2. I need to figure out how to add error handling in each of the above 3 ways.

Thanks,
Sawfiz

It seems like you’re trying to fetch data from an API within a server-side component in your Next.js application. Since server-side rendering doesn’t support asynchronous operations directly within the component, you need to handle data fetching differently. One way to achieve this is by using getServerSideProps in Next.js.

Here’s how you can refactor your component to use getServerSideProps:
import axios from “axios”;

const LatestIssues = ({ issues }) => {
return (
<>

Latest Issues


    {issues.map((issue) => (
  • {issue.title}

  • ))}

</>
);
};

export async function getServerSideProps() {
try {
const response = await axios.get(“http://localhost:3000/api/issues”);
const issues = response.data;
return {
props: { issues },
};
} catch (error) {
console.error(“Error fetching data:”, error);
return {
props: { issues: }, // Return empty array if there’s an error
};
}
}

export default LatestIssues;