https://nextjs.org/learn/dashboard-app/fetching-data
DB 생성, Seed 말고 다른 데이터 페칭 방법도 알아보자
데이터 페칭 방법: APIs, ORMs, SQL 등
서버 컴포넌트가 더 보안적으로 백엔드에 접근하는 방법
네트워크 waterfalls가 뭔가
JS 패턴을 통해 병행 데이터 페칭 구현 방법
APIs는 앱과 DB 사이의 중간층이다.
다음의 상황에서 사용한다
Next.js에서, Route Handlers를 사용해 API endpoints를 생성할 수 있다.
풀 스택 앱 개발 시, DB와 상호작용하는 로직 작성 필요.
Postgres같은 관계형 DB위해, ORM 사용 가능
다음은 DB 작성이 필요한 경우이다.
Next.js는 기본값으로 리액트 서버 컴포넌트를 사용한다.
서버 컴포넌트로 데이터 페칭은 다음과 같은 장점이 있다
대시보드 프로젝트를 위해, Vercel Postgres SDK과 SQL을 사용해 쿼리를 작성할 수 있다.
다음은 SQL 사용 이유이다
/app.lib/data.ts로 가면 @vercel/postgres에서 sql 함수를 불로오는 구문을 볼 수 있다.
import { sql } from '@vercel/postgres';
어느 서버 컴포넌트에서는 sql을 호출할 수 있지만, 네비게이트를 쉽게 하기 위해, 모든 데이터 쿼리를 data.ts에 넣고 컴포넌트에서 호출할 것이다.
대시보드에 데이터를 페칭해보자.
/app/dashboard/page.tsx로 가서 다음 코드를 붙여넣는다.
import { Card } from '@/app/ui/dashboard/cards';
import RevenueChart from '@/app/ui/dashboard/revenue-chart';
import LatestInvoices from '@/app/ui/dashboard/latest-invoices';
import { lusitana } from '@/app/ui/fonts';
export default async function Page() {
return (
<main>
<h1 className={`${lusitana.className} mb-4 text-xl md:text-2xl`}>
Dashboard
</h1>
<div className="grid gap-6 sm:grid-cols-2 lg:grid-cols-4">
{/* <Card title="Collected" value={totalPaidInvoices} type="collected" /> */}
{/* <Card title="Pending" value={totalPendingInvoices} type="pending" /> */}
{/* <Card title="Total Invoices" value={numberOfInvoices} type="invoices" /> */}
{/* <Card
title="Total Customers"
value={numberOfCustomers}
type="customers"
/> */}
</div>
<div className="mt-6 grid grid-cols-1 gap-6 md:grid-cols-4 lg:grid-cols-8">
{/* <RevenueChart revenue={revenue} /> */}
{/* <LatestInvoices latestInvoices={latestInvoices} /> */}
</div>
</main>
);
}
RevenueChart 컴포넌트를 위한 데이터 페칭을 위해, data.ts에서 fetchRevenue 함수를 불러온 후 호출한다.
import { fetchRevenue } from '@/app/lib/data';
const revenue=await fetchRevenue();
그 후 /app/ui/dashboard/revenue-chart.tsx로 이동해 RevenueChart 컴포넌트를 주석 해제 한다.
다음과 같은 revenue 데이터를 볼 수 있다.

LatestInvoices 컴포넌트를 위해, 5개의 날짜별로 정렬된 최신의 인보이스가 필요하다.
JS로 모든 인보이스를 페칭하고 정렬할 수 있다.
메모리 내 인보이스를 정렬하는 것 대신, 오직 5개의 인보이스만 페치하기 위해 sql을 쓸 수 있다.
다음은 data.ts 내 예시이다.
// Fetch the last 5 invoices, sorted by date
const data = await sql<LatestInvoiceRaw>`
SELECT invoices.amount, customers.name, customers.image_url,
customers.email
FROM invoices
JOIN customers ON invoices.customer_id = customers.id
ORDER BY invoices.date DESC
LIMIT 5`;
페이지에서, fetchLatestInvoices 함수를 불러온다.
import { fetchRevenue, fetchLatestInvoices } from '@/app/lib/data';
const latestInvoices = await fetchLatestInvoices();
그 후 LastestInvoices 컴포넌트를 주석 해제한다.
LastestInvoice 컴포넌트에서도 역시 주석 해제 한다. (/app/ui/dashboard/latest-invoices)
페이지 방문 시, 오직 5개의 인보이스만 반환된 것을 볼 수 있다.

Card는 다음 데이터를 보여준다
인보이스와 고객을 모두 페치하고 데이터 조작을 위해 JS를 써야 한다.
예시로, Array.length를 사용할 수 있다.
const totalInvoices = allInvoices.length;
const totalCustomers = allCustomers.length;
하지만 sql로는 필요한 데이터만 가져올 수 있다.
위 함수보다 좀 더 길 수 있지만 더 적은 데이터가 요청될 것을 의미한다.
const invoiceCountPromise = sql`SELECT COUNT(*) FROM invoices`;
const customerCountPromise = sql`SELECT COUNT(*) FROM customers`;
이 함수는 fetchCardData에서 불러와져야한다.
힌트
/app/dashboard/page.tsx
import {
fetchRevenue,
fetchLatestInvoices,
fetchCardData,
} from '@/app/lib/data';
const {
numberOfInvoices,
numberOfCustomers,
totalPaidInvoices,
totalPendingInvoices,
} = await fetchCardData();

알고 있을 것 두가지
이전 요청 완료에 의존하는 네트워크 요청 순서
데이터 페칭의 경우에, 각 요청은 이전 요청이 데이터를 반환했을 때만 시작된다.

예시로, fetchLatestInvoices() 시작 전에, fetchRevenue() 실행을 기다린다.
const revenue = await fetchRevenue();
const latestInvoices = await fetchLatestInvoices(); // wait for fetchRevenue() to finish
const {
numberOfInvoices,
numberOfCustomers,
totalPaidInvoices,
totalPendingInvoices,
} = await fetchCardData(); // wait for fetchLatestInvoices() to finish
이 패턴은 항상 나쁜 건 아니다.
다음 요청 시, 이전 요청 조건의 만족을 원할 때 waterfall을 원하는 경우도 있다.
예시로, 사용자의 id,프로필 정보를 미리 페치하는 경우.
id를 페치한 후 그들의 친구 목록을 가져온다.
이 경우에, 각 요청은 이전 요청에서 반환 된 데이터에 의존적이다.
하지만, 이 행위는 의도하지 않은 영향을 끼칠 수 있다.
waterfall을 피하는 일반적 방법은 동시에 모든 데이터 요청을 초기화 하는 것이다 - 병렬로
JS에서는 동시에 모든 프로미스 초기화를 위해 Promise.all() 혹은 Promise.allSettled()를 쓸 수 있다.
예시로, data.ts에서 fetchCardData() 내에서 Promise.all()을 쓴다.
export async function fetchCardData() {
try {
const data = await Promise.all([
invoiceCountPromise,
customerCountPromise,
invoiceStatusPromise,
]);
// ...
}
}
이 패턴을 통해 할 수 있는 것
하지만 하나의 단점 : 만약 한 데이터 요청이 다음보다 느려진다면?