data-fetching
Data Fetching Architecture
Overview
This project uses a structured data fetching pattern that differs between server and client components.
Core Principles
- Server components call server functions directly
- Client components use TanStack Query to fetch from API routes
- API routes wrap server functions using
apiRouteWrapper - Mutations from client components call server functions directly
- TMDB API uses auto-generated server functions from
tmdb-server-functions.ts
Server Components
Server components can directly await server functions.
import { discoverMovies } from "@/utils/tmdb-server-functions";
function serverFunction() {
return fetch(URL, headers);
}
function ServerComponent() {
const movies = await discoverMovies({
page: 1,
with_genres: "28",
});
const myResults = await serverFunction();
return <div>...</div>;
}
Use await directly in server components. Call TMDB server functions from @/utils/tmdb-server-functions.
Client Components + API Routes
Client components use TanStack Query to call API routes, which wrap server functions.
Step 1: Create API Route
API routes use apiRouteWrapper to wrap server functions:
// src/app/api/tmdb/discover-movies/route.ts
import { apiRouteWrapper } from "@/utils/api-route-wrapper";
import { discoverMovies } from "@/utils/tmdb-server-functions";
export const GET = apiRouteWrapper(discoverMovies);
For custom server functions:
// src/app/api/some-api-route/route.ts
import { apiRouteWrapper } from "@/utils/api-route-wrapper";
import { myServerFunction } from "@/some-server-function";
export const GET = apiRouteWrapper(async (params) => {
return myServerFunction(params);
});
Step 2: Call from Client Component
Use TanStack Query with apiRequestWrapper:
"use client";
import { useSuspenseQuery } from "@tanstack/react-query";
import { apiRequestWrapper } from "@/utils/api-request-wrapper";
function ClientComponent() {
const { data: movies } = useSuspenseQuery({
queryKey: [{ scope: "movies", page: 1, with_genres: "28" }],
queryFn: () =>
apiRequestWrapper("/api/tmdb/movie-list", {
page: 1,
with_genres: "28",
}),
});
const { data: results } = useSuspenseQuery({
queryKey: [{ scope: "someApiRoute" }],
queryFn: () =>
apiRequestWrapper("/api/some-api-route", {
foo: "bar",
}),
});
return <div>...</div>;
}
Mutations from Client Components
For mutations, client components call server functions directly (no API route needed).
"use client";
import { useMutation } from "@tanstack/react-query";
import { updateMovie } from "@/server-functions/movies";
function ClientComponent() {
const mutation = useMutation({
mutationFn: updateMovie,
});
const handleUpdate = () => {
mutation.mutate({ id: 1, title: "New Title" });
};
return <button onClick={handleUpdate}>Update</button>;
}
TMDB API Integration
TMDB server functions are auto-generated from src/_generated/tmdb-server-functions.ts.
Important: These are auto-generated - DO NOT edit manually. Use pnpm codegen:tmdb to regenerate.
// Import TMDB functions
import {
discoverMovies,
getMovieDetails,
searchMovies,
} from "@/utils/tmdb-server-functions";
// Server component usage
async function MovieList() {
const movies = await discoverMovies({
page: 1,
with_genres: "28",
});
return <div>...</div>;
}
// API route for client components
// src/app/api/tmdb/discover-movies/route.ts
export const GET = apiRouteWrapper(discoverMovies);
Pattern Summary
Server Component Data Flow
Server Component → Server Function → External API/Database
Client Component Data Flow (Queries)
Client Component → TanStack Query → API Route → Server Function → External API/Database
Client Component Data Flow (Mutations)
Client Component → TanStack Query Mutation → Server Function → External API/Database
Best Practices
- Server components: Call server functions directly with
await - Client queries: Use TanStack Query + API routes
- Client mutations: Call server functions directly
- TMDB: Use auto-generated functions, never edit manually
- API routes: Use
apiRouteWrapper - Client fetching: Use
apiRequestWrapper
Common Mistakes
❌ Calling API routes from server components (use server functions directly)
❌ Using fetch directly in client components (use TanStack Query)
❌ Editing tmdb-server-functions.ts manually (regenerate with pnpm codegen:tmdb)
❌ Creating API routes for mutations (call server functions directly)