Next.js Discord

Discord Forum

"use server" for database queries

Answered
Abyssinian posted this in #help-forum
Open in Discord
AbyssinianOP
I have asked this question casually a few times but I seem to struggle to get a clear answer, probably through my own fault of under explaining.

So my question is I have some mongo db queries in a file called queries.ts

example: /src/features/users/queries.ts
import UserModel from '@/models/user.model'
import dbConnect from '@/lib/dbConnect'

export async function findAllUsers() {
  const users = await UserModel.find({})
}

export async function findOneUserById(id: string) {
  const user = await UserModel.findById(id);
}


okay so I understand that these functions can be used in a react server component such as a page.tsx file

however my question is what if I wanted to fetch all my Users in a client component, this will not work currently
but if I add a "use server" to the top of my queries.ts file then it should work in a client component's "useEffect" for example.

So my main question is there any real difference between the file i pasted above and

"use server"
import UserModel from '@/models/user.model'
import dbConnect from '@/lib/dbConnect'

export async function findAllUsers() {
  const users = await UserModel.find({})
}

export async function findOneUserById(id: string) {
  const user = await UserModel.findById(id);
}


I feel like the second approach is equally secure since it runs on the server but it is now more versatile because I can use it in a client component via a useEffect or React Query for example.
Answered by Asian black bear
You should not perform data fetching using server actions because they are inherently sequential and cannot be parallelized. An example where you see a noticable problem with that is invalidating your react-query caches and refetching all the data. Every single fetcher would run sequentially and it'd take too long.
View full answer

39 Replies

Asian black bear
You should not perform data fetching using server actions because they are inherently sequential and cannot be parallelized. An example where you see a noticable problem with that is invalidating your react-query caches and refetching all the data. Every single fetcher would run sequentially and it'd take too long.
Answer
Asian black bear
Client-side data fetching in the app router is rarely necessary and only useful for rather specific use cases. In most other cases you can and should rely on SSR first.
@Asian black bear You should not perform data fetching using server actions because they are inherently sequential and cannot be parallelized. An example where you see a noticable problem with that is invalidating your react-query caches and refetching all the data. Every single fetcher would run sequentially and it'd take too long.
AbyssinianOP
hmm ill look more into some of the stuff you're saying the terminology is a bit over my head (cannot be parallelized for example), but this sounds like a proper start to self investigation so i appreciate it

so I feel what i assumed before might be right i really should use server actions for what I would normally consider POST, PUT/PATCH, and DELETE type of requests and not GET (at least to put it simply)?
Asian black bear
Yes, server actions in Next are designed to be used for server-side mutations where you don't call multiple actions at once.
In terms of what I said initially imagine this:
await Promise.allSettled([action1(), action2(), action3()])

The order in which the actions start could be random, but let's assume for simplicity that action1 starts first. Neither action2 nor action3 will run concurrently until action1 finishes despite using Promise.allSettled with the intent to actually run them concurrently.
As such this basically becomes
await action1()
await action2()
await action3()
Typically you'd think of pages being the result of a GET request and thus perform server-side fetching for use cases such as filtering.
And rely much less on client-side data fetching.
If you use client-side data fetching you have this intricate setup of react-query with fetchers and tracking state. Just using Next without additional libraries boils down to just redirecting to /category/items?filter=... and already getting the filtered data without any additional code to track this client-side.
AbyssinianOP
hmm I have done the filtering example, you shared, I guess I just get lost in when to cross that fine line of when to break away from a pure next experience and use powerful libraries such as react-query
@Asian black bear As such this basically becomes js await action1() await action2() await action3()
AbyssinianOP
and for this
is this simply by the nature of how server actions work they i guess 'override' the expectation of Promise.allSettled / Promise.all
Asian black bear
It's an artificial restriction of the Next-specific implementation of server actions. It's arbitrary.and doesn't need to be in place, but it helps to justify not using actions for data fetching after the fact.
What you get from using react-query is technically a stale-while-revalidate behavior where transitioning between pages can become nearly instant with locally cached data and then refetching it in the background to update it eventually.
Not many apps need this behavior and with Next you can achieve almost a similar behavior since Next provides great caching capabilities making requests fast enough.
You'd lose out on instant navigation which would now take a few ms for the server to return the requested data but that's less of a downside when you realize you can make your frontend code much more lightweight.
And something people overlook: requesting the same page with different parameters in Next doesn't fully rerender the entire page. It only rerenders the changed components, despite being supplied by the server.
@Asian black bear And something people overlook: requesting the same page with different parameters in Next doesn't fully rerender the entire page. It only rerenders the changed components, despite being supplied by the server.
AbyssinianOP
is this in reference to both params and searchParams?

i guess regardless since they are now promises and can be drilled into a component

if it is not drilled and awaited at the page level it still wont fully rerender the page? i find that hard to believe but of course its not something ive tested
Asian black bear
It depends on the routes you are transitioning between. It's a similar behavior when you transition between entirely different pages that share the same layout.
What Next does is that on each request it will render the entire component tree of everything server-side and the client will then deduce which of these have actually changed and replace them. It's the same behavior that existed for client-side code when you update lists etc.
Which is also a reason why key props are still important to guide React on how to reconcile server-side rendered component trees with the current client-side state.
@Asian black bear Which is also a reason why `key` props are still important to guide React on how to reconcile server-side rendered component trees with the current client-side state.
AbyssinianOP
i think i understand thats why to retrigger my suspense on my page.tsx i had to pass a key for my searchParam.query
Asian black bear
Correct!
Otherwise React on the client thinks that it's unchanged and thus won't reset it to its pending state.
In general not much has changed in terms of rerendering behavior, it's just that the source from which the new component tree is obtained is a server on the other end rather than a local rendering pass of the shadow DOM.
This is also how you can have a simple page such as this:
import { searchItemsByQuery } from "@/database";
import { Search } from "./search";
 
export default async function Page({ searchParams }: { searchParams: { q?: string } }) {
  const items = await searchItemsByQuery(searchParams.q ?? "");
  return (
    <main>
      <Search />
      {items.map(item => (
        <div key={item.id}>{item.name}</div>
      ))}
    </main>
  );
}
And only the changed items will be rerendered.
And triggering this is as easy as redirecting to a different value of the q search param.
And the Search input box doesn't even lose its client-side state.
AbyssinianOP
^ when i do this type of set up my Search is usually a client component and i use the client side hooks

  const pathname = usePathname();
  const searchParams = useSearchParams();
  const currentPage = Number(searchParams.get('page')) || 1;

  const createPageURL = (pageNumber: number | string) => {
    const params = new URLSearchParams(searchParams);
    params.set('page', pageNumber.toString());
    return `${pathname}?${params.toString()}`;
  };


this is my usually reference from the next js dashboard tutorial

but i could see if its a query I could use next.js Form component (which i actually have not tried yet)

   <Form action="/search">
      {/* On submission, the input value will be appended to
          the URL, e.g. /search?query=abc */}
      <input name="query" />
      <button type="submit">Submit</button>
    </Form>
Asian black bear
Yeah, looks good for the most part. Here's a FAQ entry I wrote ages ago on this: https://nextjs-faq.com/sharing-client-side-state-with-server-components
But going back to the topic of this thread: don't use server actions for data fetching unless you truly know what you're doing, client-side data fetching with react-query is still useful but only for very specific use cases and Next' capabilities cover most of it without having to write a ton of additional client-side code. And finally, use pages for data fetching and think of them as the result of GET requests with server actions being your mutations dealing with POST, PUT, PATCH and DELETE semantics.
thank you for all this info, especially the way it was worded assuming this thread will be readable after closing it, i want to reread it when im a bit less tired

i do have one last question

my original file
export async function findAllUsers() {
  const users = await UserModel.find({})
}

okay i can use in a page.tsx as an RSC for example fine

but if i make an equivalent fetch via a Route Handler
export async function GET() {
   const users = await UserModel.find({})
   return Response.json({ data: users })
}

and then of course us fetch in my page.tsx

is this a matter of preference or a trade off because I guess with the latter I can now use next.js fetch extensions, and it is now again more versatile because it can be in a client or server component
Asian black bear
Do not fetch your own route handlers from server-side code.
If you're inside McDonald's you wouldn't go outside to order from the Drive Through.
Route handlers are only meant for client-side fetching (never server-side fetching) and third parties accessing your public API.
AbyssinianOP
okay will definitely have to read through this faq starting with the first two you linked here
Asian black bear
You can however move their implementation into a reusable function that you call from the route handler or server-side code.