NextJS: API Routes

hwisaac·2023년 3월 12일
2

Next.js

목록 보기
19/29

소개

예시:

API 라우트는 Next.js로 API를 구축할 수 있는 솔루션을 제공합니다.

폴더 pages/api 내에 있는 모든 파일은 /api/*로 매핑되어 페이지 대신 API 엔드포인트로 처리됩니다. 이것들은 서버 사이드에서만 번들링되어 클라이언트 사이드 번들 크기를 증가시키지 않습니다.

예를 들어, 다음 API 라우트 pages/api/user.js는 상태 코드가 200JSON 응답을 반환합니다:

export default function handler(req, res) {
  res.status(200).json({ name: 'John Doe' });
}

참고: API 라우트는 next.config.js의 pageExtensions 구성에 영향을 받을 수 있습니다.

API 라우트가 작동하려면 기본(default)으로 함수를 내보내야 합니다. 이 함수는 다음 매개변수를 받습니다.

  • req: http.IncomingMessage의 인스턴스와 몇 가지 미들웨어들
  • res: http.ServerResponse의 인스턴스와 몇 가지 헬퍼 함수들

API 라우트에서 다른 HTTP 메소드를 처리하려면, 다음과 같이 요청 핸들러 내에서 req.method를 사용할 수 있습니다:

export default function handler(req, res) {
  if (req.method === 'POST') {
    // POST 요청 처리
  } else {
    // 다른 모든 HTTP 메소드 처리
  }
}

API 엔드포인트를 가져오려면, 이 섹션 시작 부분의 어떤 예시를 살펴보세요.

사용 사례

새 프로젝트의 경우, API 라우트를 사용하여 전체 API를 구축할 수 있습니다. 기존의 API가 있는 경우, 호출을 API 라우트를 통해 전달할 필요가 없습니다. API 라우트의 일부 다른 사용 사례는 다음과 같습니다.

주의 사항

  • API Routes는 기본적으로 same-origin만 허용하는 CORS 헤더를 지정하지 않습니다. CORS 동작을 사용자 정의하려면 요청 핸들러를 CORS 요청 헬퍼로 래핑해야 합니다.
  • API Routesnext export와 함께 사용할 수 없습니다.

동적 API 라우트(Dynamic API Routes)

예시: Basic API Routes(https://github.com/vercel/next.js/tree/canary/examples/api-routes)

API 라우트는 동적 라우트를 지원하며 페이지에서 사용하는 파일 네이밍 규칙과 동일합니다.

예를 들어, 다음과 같은 코드를 가진 API 라우트 pages/api/post/[pid].js가 있습니다.

export default function handler(req, res) {
  const { pid } = req.query;
  res.end(`Post: ${pid}`);
}

이제 /api/post/abc에 대한 요청은 Post: abc 텍스트로 응답합니다.

인덱스 라우트와 동적 API 라우트

RESTful 패턴에서 아래와 같은 라우트를 설정하는 것이 매우 일반적입니다.

  • GET api/posts - 페이지 목록을 가져옵니다. 아마도 페이지 별로 나뉘어 있습니다.

  • GET api/posts/12345 - id가 12345인 페이지를 가져옵니다.

이를 다음과 같이 모델링할 수 있습니다.

  • 옵션 1:
    • /api/posts.js
    • /api/posts/[postId].js
  • 옵션 2:
    • /api/posts/index.js
    • /api/posts/[postId].js

두 가지 모두 동일합니다. 동적 라우트(캐치 올 라우트를 포함)에는 정의되지 않은 상태가 없으므로 옵션 3은 /api/posts/[postId].js를 사용하는 것은 유효하지 않습니다. 어떤 상황에서도 GET api/posts/api/posts/[postId].js와 일치하지 않습니다.

캐치 올 API 라우트

API 라우트는 괄호 안에 세 개의 점 (...)을 추가하여 모든 경로를 캐치할 수 있습니다. 예를 들어:

pages/api/post/[...slug].js/api/post/a에 대해 일치하지만, /api/post/a/b, /api/post/a/b/c 등도 일치합니다.

참고: [...param]과 같은 slug 외의 이름도 사용할 수 있습니다.

일치하는 매개변수는 쿼리 매개변수(slug)로 페이지로 전송되며 항상 배열입니다. 따라서 경로 /api/post/a에는 다음과 같은 쿼리 객체가 있습니다.

{ "slug": ["a"] }

/api/post/a/b 및 일치하는 모든 경로의 경우 다음과 같이 새 매개변수가 배열에 추가됩니다.

{ "slug": ["a", "b"] }

pages/api/post/[...slug].js에 대한 API 라우트는 다음과 같습니다.

export default function handler(req, res) {
  const { slug } = req.query;
  res.end(`Post: ${slug.join(', ')}`);
}

이제 /api/post/a/b/c 에 대한 요청은 다음과 같은 응답을 반환합니다: Post: a, b, c.

선택적 캐치 올 API 라우트

파라미터를 더블 대괄호 안에 포함하여 캐치 올 라우트를 선택적으로 만들 수 있습니다([[...slug]]).

예를 들어, pages/api/post/[[...slug]].js/api/post, /api/post/a, /api/post/a/b 등과 일치합니다.

캐치 올 라우트와 선택적 캐치 올 라우트의 주요 차이점은 선택적인 경우, 파라미터가 없는 경로도 일치한다는 것입니다(위의 예에서 /api/post 경로도 일치합니다).

쿼리 객체는 다음과 같습니다:

{ } // GET `/api/post` (빈 객체)
{ "slug": ["a"] } // `GET /api/post/a` (요소가 하나인 배열)
{ "slug": ["a", "b"] } // `GET /api/post/a/b` (다중 요소 배열)

주의 사항

  • 미리 정의된 API 라우트는 동적 API 라우트보다 우선하고, 동적 API 라우트는 캐치 올 API 라우트보다 우선합니다. 다음 예제를 살펴보세요:
    • pages/api/post/create.js - /api/post/create에 일치합니다.
    • pages/api/post/[pid].js - /api/post/1, /api/post/abc 등에 일치합니다. 하지만 /api/post/create에는 일치하지 않습니다.
    • pages/api/post/[...slug].js - /api/post/1/2, /api/post/a/b/c 등에 일치합니다. 하지만 /api/post/create, /api/post/abc에는 일치하지 않습니다.

Request Helpers

예시:

API 라우트는 들어오는 요청(req)을 구문 분석하는 내장된 요청 도우미를 제공합니다.

  • req.cookies - 요청으로 전송된 쿠키를 포함하는 객체입니다. 기본값은 {}
  • req.query - 쿼리 문자열을 포함하는 객체입니다. 기본값은 {}
  • req.body - 콘텐츠 유형(content-type)으로 구문 분석된 본문을 포함하는 객체 또는 본문이 전송되지 않은 경우 null입니다.

사용자 정의 구성

API Route마다 기본 구성을 변경할 수 있는 구성 객체를 내보낼 수 있습니다. 기본 구성은 다음과 같습니다.

export const config = {
  api: {
    bodyParser: {
      sizeLimit: '1mb',
    },
  },
};

api 객체에는 API Routes에서 사용 가능한 모든 구성 옵션이 포함됩니다.

bodyParser는 자동으로 활성화됩니다. Stream 또는 raw-body로 본문을 소비하려면 이를 false로 설정할 수 있습니다.

자동 bodyParsing을 비활성화하는 경우 webhook 요청의 원시 본문을 확인할 수 있도록 할 수 있습니다(예: GitHub에서).

export const config = {
  api: {
    bodyParser: false,
  },
};

bodyParser.sizeLimit은 파싱된 본문의 최대 크기입니다. bytes에서 지원하는 어떤 형식이든 사용할 수 있습니다.

export const config = {
  api: {
    bodyParser: {
      sizeLimit: '500kb',
    },
  },
};

externalResolver는이 라우트가 express 또는 connect와 같은 외부 resolver에 의해 처리되고 있음을 명시적으로 알리는 플래그입니다. 이 옵션을 활성화하면 미해결된 요청에 대한 경고가 비활성화됩니다.

export const config = {
  api: {
    externalResolver: true,
  },
};

responseLimitAPI Routes의 응답 본문이 4MB를 초과할 때 경고가 발생합니다.

서버리스 환경에서 Next.js를 사용하지 않으며 CDN 또는 전용 미디어 호스트를 사용하지 않을 경우 이 한도를 false로 설정할 수 있습니다.

export const config = {
  api: {
    responseLimit: false,
  },
};

responseLimitbytes에서 지원하는 바이트 수 또는 어떤 문자열 형식('500kb' 또는 '3mb'와 같은)이든 사용할 수 있으며, 경고가 표시되기 전에 최대 응답 크기가 됩니다. 기본값은 4MB입니다. (위 참조)

TypeScript로 req/res 객체 확장하기

더 나은 타입 안정성을 위해서는 reqres 객체를 확장하지 않는 것이 권장됩니다. 대신, 이러한 객체들을 다루기 위해 함수를 사용하세요:

// utils/cookies.ts

import { serialize, CookieSerializeOptions } from 'cookie';
import { NextApiResponse } from 'next';

/**
 * This sets `cookie` using the `res` object
 */

export const setCookie = (
  res: NextApiResponse,
  name: string,
  value: unknown,
  options: CookieSerializeOptions = {}
) => {
  const stringValue =
    typeof value === 'object' ? 'j:' + JSON.stringify(value) : String(value);

  if (typeof options.maxAge === 'number') {
    options.expires = new Date(Date.now() + options.maxAge * 1000);
  }

  res.setHeader('Set-Cookie', serialize(name, stringValue, options));
};

// pages/api/cookies.ts

import { NextApiRequest, NextApiResponse } from 'next';
import { setCookie } from '../../utils/cookies';

const handler = (req: NextApiRequest, res: NextApiResponse) => {
  // Calling our pure function using the `res` object, it will add the `set-cookie` header
  // Add the `set-cookie` header on the main domain and expire after 30 days
  setCookie(res, 'Next.js', 'api-middleware!', { path: '/', maxAge: 2592000 });
  // Return the `set-cookie` header so we can display it in the browser and show that it works!
  res.end(res.getHeader('Set-Cookie'));
};

export default handler;

만약, 이러한 객체들이 확장되는 것을 피할 수 없다면, 추가 속성을 포함하는 자신만의 타입을 만들어야 합니다:

// pages/api/foo.ts

import { NextApiRequest, NextApiResponse } from 'next';
import { withFoo } from 'external-lib-foo';

type NextApiRequestWithFoo = NextApiRequest & {
  foo: (bar: string) => void;
};

const handler = (req: NextApiRequestWithFoo, res: NextApiResponse) => {
  req.foo('bar'); // we can now use `req.foo` without type errors
  res.end('ok');
};

export default withFoo(handler);

기억해야 할 점은, 코드가 withFoo()export에서 제거해도 여전히 컴파일 된다는 것입니다. 따라서 이 방법은 안전하지 않습니다.

Response Helper

서버 응답 객체 (res로 약어로 줄여집니다)는 Express.js와 비슷한 도우미 메서드 세트를 포함하고 있습니다. 이는 개발자 경험을 향상시키고 새 API 엔드포인트를 더 빠르게 만드는 데 도움이 됩니다.

포함된 도우미는 다음과 같습니다:

  • res.status(code) - 상태 코드를 설정하는 함수입니다. code는 유효한 HTTP 상태 코드여야 합니다.
  • res.json(body) - JSON 응답을 보냅니다. body는 직렬화 가능한 객체여야 합니다.
  • res.send(body) - HTTP 응답을 보냅니다. body는 문자열, 객체 또는 버퍼일 수 있습니다.
  • res.redirect([status,] path) - 지정된 경로 또는 URL로 리디렉션합니다. status는 유효한 HTTP 상태 코드여야 합니다. 지정되지 않으면, status는 "307" "임시 리디렉션(Temporary redirect)"으로 기본값이 설정됩니다.
  • res.revalidate(urlPath) - getStaticProps를 사용하여 페이지를 필요에 따라 다시 유효성 검사합니다. urlPath는 문자열이어야 합니다.

응답 상태 코드 설정

클라이언트로 응답을 보낼 때, 응답의 상태 코드를 설정할 수 있습니다.

다음 예제는 응답의 상태 코드를 200 (정상)으로 설정하고 JSON 응답으로 message 속성을 반환합니다.

export default function handler(req, res) {
  res.status(200).json({ message: 'Hello from Next.js!' });
}

JSON 응답 보내기

클라이언트로 응답을 보낼 때, 직렬화 가능한 객체를 포함하는 JSON 응답을 보낼 수 있습니다. 실제 애플리케이션에서는 요청된 엔드포인트 결과에 따라 요청 상태를 클라이언트에 알릴 수 있습니다.

다음 예제는 비동기 작업의 결과와 함께 상태 코드 200 (정상)을 가진 JSON 응답을 보냅니다. try-catch 블록으로 처리하며, 적절한 상태 코드와 오류 메시지를 클라이언트에 반환합니다.

export default async function handler(req, res) {
  try {
    const result = await someAsyncOperation();
    res.status(200).json({ result });
  } catch (err) {
    res.status(500).json({ error: 'failed to load data' });
  }
}

HTTP 응답 보내기

HTTP 응답을 보내는 것은 JSON 응답을 보낼 때와 같이 작동합니다. 유일한 차이점은 응답 본문이 문자열, 객체 또는 버퍼일 수 있다는 것입니다.

다음 예제는 상태 코드가 200 (OK) 이고 async 작업의 결과를 가진 HTTP 응답을 보냅니다.

export default async function handler(req, res) {
  try {
    const result = await someAsyncOperation();
    res.status(200).send({ result });
  } catch (err) {
    res.status(500).send({ error: 'failed to fetch data' });
  }
}

특정 경로 또는 URL로 리디렉션
양식을 예로 들면, 양식이 제출되면 클라이언트를 지정된 경로 또는 URL로 리디렉션 할 수 있습니다.

다음 예제는 양식이 성공적으로 제출되면 클라이언트를 / 경로로 리디렉션합니다.

export default async function handler(req, res) {
  const { name, message } = req.body;
  try {
    await handleFormInputAsync({ name, message });
    res.redirect(307, '/');
  } catch (err) {
    res.status(500).send({ error: 'failed to fetch data' });
  }
}

TypeScript 타입 추가하기

response 핸들러를 더욱 안전한 타입으로 만들 수 있습니다. NextApiRequestNextApiResponse 타입을 next에서 가져온 후, response 데이터에도 타입을 지정할 수 있습니다.

import type { NextApiRequest, NextApiResponse } from 'next';

type ResponseData = {
  message: string;
};

export default function handler(
  req: NextApiRequest,
  res: NextApiResponse<ResponseData>
) {
  res.status(200).json({ message: 'Hello from Next.js!' });
}

참고: NextApiRequestbody는 클라이언트가 임의의 페이로드를 포함시킬 수 있으므로 any 입니다. 사용하기 전에 실행 시간에 body의 유형/구조를 검증해야 합니다.

타입을 사용한 더 많은 예제는 TypeScript 문서를 확인하십시오.

실제 프로젝트 구조 내에서 예제를 보려면 예제 저장소를 확인할 수 있습니다.

Edge API Routes

Edge API Routes를 사용하면 Next.js를 사용하여 고성능 API를 구축할 수 있습니다. Edge Runtime을 사용하여 Node.js 기반 API Routes보다 빠릅니다. 이 성능 향상은 기본적으로 웹 API를 사용하므로 Native Node.js API에 액세스할 수 없는 제약 사항이 있습니다.

폴더 pages/api 내부의 모든 파일은 /api/*에 매핑되어 페이지 대신 API 엔드포인트로 처리됩니다. 이는 서버 사이드에서만 번들로 작동하며 클라이언트 사이드 번들 크기를 늘리지 않습니다.

예시

기본

export const config = {
  runtime: 'edge',
};

export default (req) => new Response('Hello world!');

JSON 응답

import type { NextRequest } from 'next/server';

export const config = {
  runtime: 'edge',
};

export default async function handler(req: NextRequest) {
  return new Response(
    JSON.stringify({
      name: 'Jim Halpert',
    }),
    {
      status: 200,
      headers: {
        'content-type': 'application/json',
      },
    }
  );
}

캐시 제어

import type { NextRequest } from 'next/server';

export const config = {
  runtime: 'edge',
};

export default async function handler(req: NextRequest) {
  return new Response(
    JSON.stringify({
      name: 'Jim Halpert',
    }),
    {
      status: 200,
      headers: {
        'content-type': 'application/json',
        'cache-control': 'public, s-maxage=1200, stale-while-revalidate=600',
      },
    }
  );
}

쿼리 매개변수

import type { NextRequest } from 'next/server';

export const config = {
  runtime: 'edge',
};

export default async function handler(req: NextRequest) {
  const { searchParams } = new URL(req.url);
  const email = searchParams.get('email');
  return new Response(email);
}

전달 헤더(Forwarding Header)

import { type NextRequest } from 'next/server';

export const config = {
  runtime: 'edge',
};

export default async function handler(req: NextRequest) {
  const authorization = req.cookies.get('authorization')?.value;
  return fetch('https://backend-api.com/api/protected', {
    method: req.method,
    headers: {
      authorization,
    },
    redirect: 'manual',
  });
}

Configuring Regions (for deploying)

배포 시 특정 지역에만 edge function을 제한하려면 다음과 같이 할 수 있습니다.

참고: 이 설정은 Next.js v12.3.2 이상에서 사용할 수 있습니다.

import { NextResponse } from 'next/server';

export const config = {
  regions: ['sfo1', 'iad1'], // 기본값은 'all'입니다.
};

export default async function handler(req: NextRequest) {
  const myData = await getNearbyData();
  return NextResponse.json(myData);
}

API Routes 간의 차이점

Edge API RoutesEdge Runtime을 사용하고, API RoutesNode.js Runtime을 사용합니다.

Edge API Routes는 서버에서 응답을 스트리밍하고 캐시된 파일 (예: HTML, CSS, JavaScript)에 액세스한 후 실행됩니다. 서버 측 스트리밍은 더 빠른 Time To First Byte (TTFB)로 성능을 향상시킬 수 있습니다.

참고: getServerSidePropsEdge Runtime을 함께 사용하면 응답 객체에 액세스할 수 없습니다. 응답 객체에 액세스해야하는 경우 runtime: 'nodejs'로 Node.js Runtime을 사용해야합니다.

Edge Runtime에서 지원되는 API 및 지원되지 않는 API를 확인하려면 다음 문서를 참조하십시오.

0개의 댓글