Sanity와 Route Handlers 사용하기

현성·2024년 9월 7일
0

Sanity 소개

SanityNext.jsRoute 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
      };
    }
  }
};

저는 카드에 대한 데이터를 설계하기 위해 위와 같은 코드를 작성하였습니다.
그래서 titlename을 각각 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 스키마 공식문서에 있습니다.

Sanity + Route Handlers

// 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를 조합하면 빠르게 풀스택 애플리케이션을 구축할 수 있어서 사이드 프로젝트에 아주 적합하다고 생각합니다.

profile
👈🏻 매일 꾸준히 성장하는 개발자 !

0개의 댓글