가수면

API 백엔드 로직 본문

React/Next.js

API 백엔드 로직

니비앙 2023. 6. 6. 06:04

내부 파일 사용할 때

서버에서만 사용할 수 있음. 클라이언트 컴포넌트에서 사용 불가

import path from "path";
import { promises as fs } from "fs";

export type Food = {
  id: string;
  name: string;
  price: number;
};

/** */
export async function getFoods(): Promise<Food[]> {
  const filePath = path.join(process.cwd(), "data", "food.json"); // data/food.json 경로 순서대로 스트링 넣어주면 됨
  const data = await fs.readFile(filePath, "utf-8");
  return JSON.parse(data);
}

또는

export async function getFoods(): Promise<Food[]> {
  const filePath = path.join(process.cwd(), "data", "food.json");
  return readFile(filePath, "utf-8")
    .then<Food[]>(JSON.parse) // (posts) => JSON.parse(posts)를 줄인 것
    .then((foods) => foods.sort());
}
/** */

export async function getFood(id: string): Promise<Food | undefined> {
  const foods = await getFoods();
  return foods.find((food) => food.id === id);
}

fetch 같은 경우는 next.js가 중복되는 요청을 알아서 한번으로 최적화 시켜주는데, 위처럼 fetch를 안 쓰는 로직에 대해선 next.js가 최적화시켜주지 않는다.

이 경우 'cache'를 사용하여 수동으로 최적화를 시켜줄 수 있음

export async function getAllPosts(): Promise<Post[]> {
  const filePath = path.join(process.cwd(), 'data', 'posts.json');
  return readFile(filePath, 'utf-8')
    .then<Post[]>(JSON.parse)
    .then((posts) => posts.sort((a, b) => (a.date > b.date ? -1 : 1)));
}

export const getAllPosts = cache(async () => {
  const filePath = path.join(process.cwd(), 'data', 'posts.json');
  return readFile(filePath, 'utf-8')
    .then<Post[]>(JSON.parse)
    .then((posts) => posts.sort((a, b) => (a.date > b.date ? -1 : 1)));
}

만능은 아니고, 두 가지 조건 하에서만 중복 호출되지 않는다.

1. 렌더링 사이클 안에서만 적용

예를 들어 빌드 시 해당 페이지의 여러 컴포넌트들에서 요청이 이루어질 경우 한번만 요청이 된다.

하지만 다른 페이지에서의 요청들은 새로운 렌더링 사이클로 인식해 중복 요청으로 간주하지 않고 재요청이 이루어진다.

2. 동일한 인자에 대해서만

호출할 때 넘겨주는 인자가 같은 경우에만 중복 요청하지 않는다.

서버 로직

Next.js는 서버 코드를 작성할 수 있는 기능을 제공한다.

컨벤션은 app파일 내 api 폴더를 생성하고 route 파일을 만들면 해당 경로가 요청 url이 된다

예시) app/api/todo/route.ts      =>  fetch("/api/todo")

주의할 점은 일반적으로는 상대 경로로도 배포 후 실사용까지 가능하지만 동적 경로 세그먼트의 경우는 url에 상대 경로 대신 절대 경로를 지정해줘야 한다.

 

서버 로직의 경우 다음의 방식을 따른다.

예전 방식

/** /pages/api/foods.ts 혹은 /pages/api/foods/index.ts */

import { Food, getFoods } from "@/service/foods";
import { NextApiRequest, NextApiResponse } from "next";

export default async function handler(req: NextApiRequest, res: NextApiResponse<Food[]>) {
  if (req.method === "GET") {
    const foods = await getFoods();
    return res.status(200).json(foods);
  }
  res.status(200);
}

/api/foods 입력하면 확인 가능

최신 방식

리턴으로 new Response나 NextResponse 아무거나 해도 됨. NextResponse 의 경우 new Response를 확장해 조금 더 다양한 기능들을 제공함.

new Response(Body?, ResponseInit?)

ResponseInit {

header?: 헤더,

status?: 0,

statusText?: ""

}

기본 예시)

import { getFoods } from "@/service/foods";
import { NextResponse } from "next/server";

export default async function GET(request: Request) {
  const foods = await getFoods();
  return NextResponse.json({ foods });
}

심화 예시 2)

바디로 받아오는 request가 없어 request를 사용하지 않더라도 쿼리로 받아오는 인수가 있다면 request를 반드시 적어줘야 한다!

export async function DELETE(request: NextRequest, { params: { id } }: paramsId) {
  try {
    const collection = await connectToMongo();
    await collection.deleteOne({ _id: new ObjectId(id) });
    return new Response(JSON.stringify({ message: "삭제 성공!" }));
  } catch (error) {
    console.error("투두 DELETE :", error);
    return new Response(JSON.stringify({ message: "투두 삭제 중에 오류가 발생했습니다." }), { status: 500 });
  }
}

export async function PATCH(request: NextRequest, { params: { id } }: paramsId) {
  try {
    const isDone = await request.json();
    const collection = await connectToMongo();
    await collection.updateOne({ _id: new ObjectId(id) }, { $set: { isDone: !isDone } });
    const todos = await collection.find({}).toArray();
    return new Response(JSON.stringify({ todos }));
  } catch (error) {
    console.error("투두 PATCH :", error);
    return new Response(JSON.stringify({ message: "투두 업데이트 중에 오류가 발생했습니다." }), { status: 500 });
  }
}

 

심화 예시 3)

export async function POST(req: Request) {
  const body = await req.json();
  if (!bodySchema.isValidSync(body)) {
    return new Response(JSON.stringify({ message: '메일 전송에 실패함!' }), {
      status: 400,
    });
  }
  return sendEmail(body) //
    .then(
      () =>
        new Response(JSON.stringify({ message: '메일을 성공적으로 보냈음' }), {
          status: 200,
        })
    )
    .catch((error) => {
      console.error(error);
      return new Response(JSON.stringify({ message: '메일 전송에 실패함!' }), {
        status: 500,
      });
    });
}

Revalidating Static Data

fetch('https://...', { next: { revalidate: 10 } })

// page나 레이아웃에 전역 설정할 수 있음
export const revalidate = false
// false | 'force-cache' | 0 | number

캐시된 데이터를 일정 시간 간격으로 재검증 (리액트 쿼리의 stale인듯)

yup

스키마 유효성 검증 라이브러리

npm i yup

import * as yup from 'yup';

const bodySchema = yup.object().shape({		// 골격 만들기
  from: yup.string().email().required(),	// 이메일 형태, 필수
  subject: yup.string().required(),
  message: yup.string().required(),
});

export async function POST(req: Request) {
  const body = await req.json();
  if (!bodySchema.isValidSync(body)) {		// bodySchema와 일치하는지 검사
    return new Response(JSON.stringify({ message: '메일 전송에 실패함!' }), {
      status: 400,
    });
  }
  return sendEmail(body) //
    .then(
      () =>
        new Response(JSON.stringify({ message: '메일을 성공적으로 보냈음' }), {
          status: 200,
        })
    )
    .catch((error) => {
      console.error(error);
      return new Response(JSON.stringify({ message: '메일 전송에 실패함!' }), {
        status: 500,
      });
    });
}

'React > Next.js' 카테고리의 다른 글

Next.js 심화  (0) 2023.06.20
라우팅 심화  (0) 2023.06.12
리액트에서 사용했던 기능들  (0) 2023.06.07
SSR, SSG, ISR, CSR 방법  (0) 2023.06.06
Next.js 기본  (0) 2023.06.02
Comments