Sanity
와 Next.js
의 Route Handlers
를 사용하면 프론트엔드 개발자이더라도
간단히 백엔드 서버와 데이터베이스를 구축할 수 있습니다.
Sanity
에 대해서는 이전 포스팅을 참고해주세요.
Sanity
로 데이터베이스를 구축하기 위해 스키마를 작성합니다.
코드를 보면 좀 더 쉽게 이해할 수 있습니다.
export const card = {
title: 'Card',
name: 'card',
type: 'document',
fields: [
// 이곳에 구성할 필드를 작성합니다.
],
preview: {
select: {
card_name: 'name',
company: 'company',
max_benefit: 'discount_limit',
media: 'image_horizontal'
},
prepare: (selection) => {
const { card_name: cardName, company, max_benefit: maxBenefit, media } = selection;
return {
title: `${cardName} by (${company})`,
subtitle: `최대 혜택 월 ${maxBenefit.toLocaleString()}원`,
media
};
}
}
};
저는 카드에 대한 데이터를 설계하기 위해 위와 같은 코드를 작성하였습니다.
그래서 title
과 name
을 각각 card와 Card로 명시했습니다.
type: document
는 하나의 테이블을 구성하기 위한 타입이라고 생각하면 됩니다.
fields
속성에는 구성하고자 하는 필드값들을 배열로 작성해주면 됩니다.
preview
속성에는 select
속성으로 미리보기에 사용할 값들을 fields
에서 지정한 field
에서 가져오고 가져온 값들을 prepare
메서드의 인자로 받아서 사용합니다.
field
는 다음과 같이 작성할 수 있습니다.
// 예시
{
title: 'Name',
name: 'name',
type: 'string'
},
{
title: 'Type',
name: 'type',
type: 'string',
options: {
list: [
{ title: 'Credit', value: 'credit' },
{ title: 'Check', value: 'check' }
]
}
},
...
위와 같이 설정한 값들은 sanity studio
로 확인할 수 있습니다.
더 많은 정보는 Sanity 스키마 공식문서에 있습니다.
// route.ts
export const GET = async (request: Request) => {
const { searchParams } = new URL(request.url);
// 쿼리 파라미터를 받는 로직
// ...
if (!type) {
return new Response('bad request', { status: 400 });
}
const filteredCards = await getFilteredCards(
type,
company as CardCompany[],
categoryEn as BenefitCategories[]
);
return NextResponse.json(filteredCards);
};
GET요청을 받는 라우트 핸들러를 작성했습니다. 그러면 이제 getFilteredCards
함수를 보며
Sanity
에서 사용하는 쿼리 언어인 GROQ
에 대해 알아보겠습니다.
export const getFilteredCards = async (
type: 'credit' | 'check',
company?: CardCompany[],
category?: BenefitCategories[]
): Promise<CardResponseType[]> =>
const isValidCompany =
company && company.every((c) => CARD_COMPANIES.map((comp) => comp.title).includes(c));
const isValidCategory =
category &&
category.every((c) => CARD_BENEFIT_CATEGORIES.map((cate) => cate.title_en).includes(c));
const companyQuery = isValidCompany ? `&& company in ${JSON.stringify(company)}` : '';
const query = `
*[_type == "card" && type == "${type}" ${companyQuery}]${CardResponseFilde}
`;
const cards = await client.fetch(query);
let result;
if (isValidCategory) {
result = cards.filter((card: CardResponseType) =>
card.benefits.some((benefit) => category.includes(benefit.category))
);
} else {
result = cards;
}
return result;
};
getFilteredCards
의 인자 중 company와 category는 옵셔널한 값입니다.
따라서 그 값의 유무에 따라 client.fetch
메서드에 전달하는 파라미터인 쿼리가 달라지게 됩니다.
Sanity
에서 관리하는 데이터를 개발자가 원하는 대로 가져오기 위해서는 GROQ
에 대한 이해가 필요합니다. 쿼리문에 대한 설명은 이 곳에 자세히 나와있습니다.
이전에 구성했던 스키마 field의 name
값을 참조하여 원하는 데이터만 fetch
합니다.
POST요청은 어떻게 핸들링할 수 있는 지도 알아봅시다.
export const POST = async (req: NextRequest) => {
const session = await auth();
if (!session) {
return new Response('Authentication Error', { status: 401 });
}
const request = await req.json();
return client
.create({
_type: 'bucket',
author: session.user?.email,
bucket_name: request['bucket-name'],
target_amount: request['target-amount'],
spend_book: request['spend-book'],
saving_book: request['saving-book'],
day_of_week: request['day-of-week'],
savings_amount: request['savings-amount'],
my_saving_product: request['my-saving-product']
})
.then(() => new Response('success', { status: 200 }));
};
이전 GET요청에서 보았던 client.fetch
메서드는 쿼리문을 파라미터값으로 받아 데이터베이스에서 원하는 데이터를 받아오는데 사용되었다면, client.create
메서드는 이름에서도 알 수 있듯이 지정한 document
에 field값을 작성해주면 그 값을 토대로 새로운 데이터를 추가하는데 사용됩니다.
이렇게 Next.js에서 Route Handlers와 Sanity를 조합하면 빠르게 풀스택 애플리케이션을 구축할 수 있어서 사이드 프로젝트에 아주 적합하다고 생각합니다.