Data Fetching in Server Components — Async/Await with Databases and APIs
In this tutorial, you will learn about Data Fetching in Server Components. We cover key concepts, practical examples, and best practices to help you master this topic.
Server Components support async/await natively, letting you fetch data directly from databases or APIs without useEffect, getServerSideProps, or any client-side data fetching library.
What You'll Learn
You will understand how to fetch data in Server Components using async/await, query databases directly, use the native fetch API with caching, and handle loading and error states.
Why It Matters
Server-side data fetching eliminates client-server waterfalls, reduces bundle size, and keeps sensitive credentials on the server. It is one of the primary benefits of React Server Components.
Real-World Use
Durga Antivirus Pro fetches threat intelligence data directly from its PostgreSQL database in Server Components, rendering the threat dashboard without exposing database credentials to the browser.
flowchart TD
A[Server Component] --> B[async/await]
B --> C{Data source}
C --> D[Database Query]
C --> E[Fetch API]
C --> F[File System]
D --> G[Rendered HTML]
E --> G
F --> G
G --> H[Client receives HTML]
style A fill:#1e293b,color:#fff
style G fill:#0f172a,color:#fff
Fetching Data with the Native Fetch API
The native fetch API works directly in Server Components. You can use it with Next.js caching options for fine-grained control.
async function getLatestPosts() {
const res = await fetch('https://jsonplaceholder.typicode.com/posts', {
next: { revalidate: 60 }
});
if (!res.ok) throw new Error('Failed to fetch posts');
return res.json();
}
export default async function BlogPage() {
const posts = await getLatestPosts();
return (
<div>
<h1>Latest Posts</h1>
{posts.slice(0, 5).map(post => (
<article key={post.id}>
<h2>{post.title}</h2>
<p>{post.body}</p>
</article>
))}
</div>
);
}
Expected output: A page showing the five most recent blog posts. The data is fetched on the server every 60 seconds (revalidation). The client receives only the rendered HTML.
Querying a Database Directly
Server Components can import and use database drivers directly. No API layer is needed between the component and the database.
import { db } from '@/lib/database';
async function getUserDashboard(userId) {
const [user, recentActivity, stats] = await Promise.all([
db.users.findById(userId),
db.activity.findRecent(userId, 10),
db.stats.getUserSummary(userId),
]);
return { user, recentActivity, stats };
}
export default async function DashboardPage({ params }) {
const data = await getUserDashboard(params.userId);
return (
<div>
<h1>Welcome, {data.user.name}</h1>
<h2>Recent Activity</h2>
<ul>
{data.recentActivity.map(a => (
<li key={a.id}>{a.action} — {a.timestamp}</li>
))}
</ul>
<p>Total contributions: {data.stats.total}</p>
</div>
);
}
Expected output: A dashboard page with user information, recent activity list, and summary statistics. Three parallel database queries run on the server, and the client receives the fully rendered HTML.
Error Handling in Server Components
Async Server Components support standard try/catch blocks for error handling. Throw exceptions to trigger error boundaries.
async function ProductPage({ params }) {
try {
const product = await db.products.findById(params.id);
if (!product) {
throw new Error('Product not found');
}
return (
<div>
<h1>{product.name}</h1>
<p>{product.description}</p>
<p>Price: ${product.price}</p>
</div>
);
} catch (error) {
throw new Error(`Failed to load product: ${error.message}`);
}
}
Expected output: If the product exists, the page renders product details. If not found or an error occurs, the error propagates to the nearest error boundary, which shows a fallback UI.
Parallel Data Fetching
Use Promise.all to fetch data in parallel and avoid request waterfalls.
async function ProfilePage({ params }) {
const [profile, posts, followers] = await Promise.all([
db.users.findProfile(params.username),
db.posts.findByAuthor(params.username, { limit: 10 }),
db.followers.count(params.username),
]);
return (
<div>
<UserCard profile={profile} />
<PostList posts={posts} />
<p>{followers} followers</p>
</div>
);
}
Expected output: All three data fetches run simultaneously. The page renders only when all data is available, avoiding multiple loading states.
Common Mistakes
Not handling fetch errors: Always check
res.okor wrap in try/catch. Unhandled fetch errors cause the component to crash without a useful error message.Creating waterfalls with sequential awaits: Using
await a(); await b();instead ofPromise.all([a(), b()])slows down page rendering by running requests sequentially.Exposing sensitive data in the response: Even though Server Components run on the server, any data passed to Client Components via props is serialized and sent to the browser.
Forgetting to handle empty data: Always check for empty arrays or null values before rendering. A database query might return zero results.
Using the wrong cache Strategy: Static data should use
force-cache, dynamic data should useno-store, and semi-dynamic data should userevalidate.
Practice Questions
- How do you fetch data in a Server Component?
Using async/await directly in the component function. The component is marked as async and returns JSX after the data is resolved.
- What is the benefit of parallel data fetching with Promise.all?
Multiple data sources are queried simultaneously instead of sequentially, reducing the total wait time to the slowest single request.
- How do you handle errors in Server Component data fetching?
Use try/catch blocks inside the async component. Thrown errors propagate to the nearest error boundary.
- What is the difference between
cache: 'force-cache'andcache: 'no-store'?
force-cachecaches the response and serves it until a revalidation triggers.no-storealways fetches fresh data on every request.
- Can Server Components fetch data from a Graphql API?
Yes. Server Components can fetch from any HTTP endpoint using fetch or any database using its driver.
Challenge
Build a page that fetches user data, their recent orders, and product recommendations in parallel using Promise.all, with error handling for each data source separately.
Frequently Asked Questions
Mini Project
Create a user profile page that fetches user details, their post history, and a list of mutual connections using three parallel database queries in a Server Component.
What's Next
Learn about Async Components in depth and how they simplify the data-fetching pattern in React.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro