1. Quick Start πŸ‘©β€πŸ’»

1. Install Netlify CLI

κΈ°λ³Έ κ°œλ…μ€ Vercel Serverless Functions λ₯Ό μ°Έκ³ ν•˜λ„λ‘ ν•˜κ³  λ°”λ‘œ ν•¨μˆ˜λ₯Ό λ§Œλ“€μ–΄λ³΄μž.

npm i -D netlify-cli

그리고 TypeScript ν™˜κ²½μΌ 경우 λ‹€μŒμ„ μΆ”κ°€λ‘œ μ„€μΉ˜ν•œλ‹€(Get started with functions 을 μ°Έκ³ ).

npm i @netlify/functions

Vercel 이 root κ²½λ‘œμ— apiλΌλŠ” 디렉토리λ₯Ό μƒμ„±ν•˜κ³  ν•˜μœ„ κ²½λ‘œμ— js λ˜λŠ” ts νŒŒμΌμ„ λ§Œλ“€μ—ˆλ“―μ΄ Netlify μ—­μ‹œ root κ²½λ‘œμ— netlify/functionsλΌλŠ” 디렉토리λ₯Ό μƒμ„±ν•˜κ³  ν•˜μœ„ κ²½λ‘œμ— mjs, mts νŒŒμΌμ„ λ§Œλ“œλŠ” κ²ƒμœΌλ‘œ μ‹œμž‘ν•œλ‹€ (이 뢀뢄은 Module format 에 μ„€λͺ…λ˜μ–΄ μžˆμœΌλ‹ˆ μ°Έκ³ ν•˜λ„λ‘ ν•œλ‹€).

μ΄λ•Œ 파일λͺ…μ˜ κ·œμΉ™μ΄ μ‘΄μž¬ν•˜λŠ”λ° λ‹€μŒκ³Ό κ°™λ‹€.

  • subdirectory κ°€ 없을 경우: netlify/functions/hello.mts
  • subdirectory κ°€ μžˆμ„ 경우: netlify/functions/hello/hello.mts λ˜λŠ” netlify/functions/hello/index.mts

subdirectory κ°€ μžˆμ„ 경우 파일λͺ…은 λ°˜λ“œμ‹œ subdirectory 와 λ™μΌν•˜κ±°λ‚˜ index둜 μž‘μ„±λ˜μ–΄μ•Όν•œλ‹€.

  • package.json
{
  "scripts": {
    "netlify": "netlify dev"
  }
}

Vercel κ³Ό 달리 netlify.json νŒŒμΌμ„ λ§Œλ“€μ§€ μ•Šμ•„λ„ npm run netlifyλ₯Ό μ‹€ν–‰ν•˜λ©΄ μžλ™μœΌλ‘œ λ¦¬μ•‘νŠΈλ₯Ό λ„μ›Œμ€€λ‹€.

Netlify 에도 config.json 파일이 μ‘΄μž¬ν•œλ‹€.

  • macOS: Library/Preferences/netlify/config.json
  • Linux: .config/netlify/config.json
  • Windows: AppData\Roaming\netlify\Config\config.json

에 μ €μž₯되며 netlify init λ˜λŠ” netlify login λͺ…λ Ήμ–΄λ₯Ό ν†΅ν•œ 둜그인 정보λ₯Ό μ €μž₯ν•˜λŠ” 데 μ‚¬μš©λœλ‹€.

2. Create API Functions

Vercel κ³Ό λ™μΌν•˜κ²Œ /api/user와 /api/user/uuid005435λ₯Ό μƒ˜ν”Œλ‘œ λ§Œλ“€μ–΄λ³΄λ„λ‘ ν•˜μž. Netlify λŠ” Prefix 둜 /apiκ°€ μ•„λ‹Œ /.netlify/functionsλ₯Ό μ‚¬μš©ν•˜λ©°, Vercel κ³Ό 달리 μ›Ήμ•±κ³Ό λ‹€λ₯Έ ν¬νŠΈμ—μ„œ μ‹€ν–‰λœλ‹€λŠ” 것에 μœ μ˜ν•΄μ•Όν•œλ‹€.

  • /netlify/functions/user.mts
import { Context } from "@netlify/functions";

const ALLOWED_METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE"];

const RESPONSE_INIT = {
  headers: {
    "Content-Type": "application/json; charset=utf-8",
  },
  status: 200,
};

export default function handler(request: Request, context: Context) {
  const method = ALLOWED_METHODS.find((method) => method === request.method);
  if (method === undefined) return;
  return user[method](request, context);
}

const user: Record<string, Function> = {
  GET: getUser,
  POST: postUser,
  PUT: putUser,
  PATCH: patchUser,
  DELETE: deleteUser,
};

function getUser(request: Request, context: Context): Response {
  return new Response(
    JSON.stringify({ name: "Hogwarts", age: 32, favorite: ["Movie", "Music", "Book", "Beer"] }),
    RESPONSE_INIT,
  );
}

function postUser(request: Request, context: Context): Response {
  return new Response(JSON.stringify({}), RESPONSE_INIT);
}

function putUser(request: Request, context: Context): Response {
  return new Response(JSON.stringify({}), RESPONSE_INIT);
}

function patchUser(request: Request, context: Context): Response {
  return new Response(JSON.stringify({}), RESPONSE_INIT);
}

function deleteUser(request: Request, context: Context): Response {
  return new Response(JSON.stringify({}), RESPONSE_INIT);
}

npm run netlifyλͺ…령을 μž…λ ₯ν•˜λ©΄ μœ„μ— μž‘μ„±ν•œ Netlify λͺ…λ Ήμ–΄κ°€ μ‹€ν–‰λœλ‹€.

Netlify Server

3000번 ν¬νŠΈμ—μ„œ λ¦¬μ•‘νŠΈκ°€ μ‹€ν–‰λ˜κ³ , Netlify λŠ” 8888번 ν¬νŠΈμ—μ„œ μ‹€ν–‰λ˜μ–΄ μ„œλ‘œ λ‹€λ₯Έ ν¬νŠΈμ—μ„œ μž‘λ™ν•˜λŠ” 것을 μ•Œ 수 μžˆλ‹€.

http://localhost:8888/.netlify/functions/user 에 μš”μ²­μ„ 보내면 결과둜 Status Code 200 κ³Ό λ‹€μŒ JSON 데이터λ₯Ό 얻을 수 μžˆλ‹€.

{
  "name": "Hogwarts",
  "age": 32,
  "favorite": [ "Movie", "Music", "Book", "Beer" ]
}

3. Dynamic Routes

Vercel κ³Ό λ§ˆμ°¬κ°€μ§€λ‘œ 동적 λΌμš°νŒ…μ΄ κ°€λŠ₯ν•˜λ‹€. ν•˜μ§€λ§Œ Vercel 이 Next.js 와 λ§ˆμ°¬κ°€μ§€λ‘œ 디렉토리 ꡬ쑰와 [urlParam].ts와 같은 λ°©μ‹μœΌλ‘œ νŒŒμΌμ„ 생성해 κ΅¬λΆ„ν•˜λŠ” 것과 달리 디렉토리 ꡬ쑰λ₯Ό μ‚¬μš©ν•œ 동적 λΌμš°νŒ…μ€ λΆˆκ°€λŠ₯ν•˜λ‹€. λŒ€μ‹  Netlify κ°€ μ œκ³΅ν•˜λŠ” Configλ₯Ό μ„€μ •ν•΄ λΌμš°νŒ…μ„ μ‹œν‚¬ 수 μžˆλ‹€.

import { Config, Context } from "@netlify/functions";

const ALLOWED_METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE"];

const RESPONSE_INIT = {
  headers: {
    "Content-Type": "application/json; charset=utf-8",
  },
  status: 200,
};

export default function handler(request: Request, context: Context) {
  const method = ALLOWED_METHODS.find((method) => method === request.method);
  if (method === undefined) return;
  return index[method](request, context);
}

const index: Record<string, Function> = {
  GET: getUser,
  POST: postUser,
  PUT: putUser,
  PATCH: patchUser,
  DELETE: deleteUser,
};

function getUser(request: Request, context: Context): Response {
  return new Response(
      JSON.stringify({ name: "Hogwarts", age: 32, favorite: ["Movie", "Music", "Book", "Beer"] }),
      RESPONSE_INIT,
  );
}

// ...

export const config: Config = {
  path: ["/user"],
};

와 같이 μ„€μ •ν•˜λ©΄ 이제 μš”μ²­ URL 은 http://localhost:8888/.netlify/functions/user κ°€ μ•„λ‹Œ
http://localhost:8888/user κ°€ λœλ‹€.


이제 Configλ₯Ό μ‚¬μš©ν•΄ 동적 λΌμš°νŒ…μ„ μ‹œμž‘ν•΄λ³΄μž.

Dynamic Routes

μœ„μ™€ 같이 디렉토리λ₯Ό κ΅¬λΆ„ν•˜μ§€ μ•Šκ³  μƒμ„±ν•œ μ΄μœ λŠ” λ‹€μŒκ³Ό κ°™λ‹€.

  • functions ν•˜μœ„ subdirectory 깊이λ₯Ό 1κΉŒμ§€λ§Œ μ°ΎλŠ”λ‹€.
  • subdirectory κ°€ μ‘΄μž¬ν•  경우, κ·Έ μ•ˆμ— μœ„μΉ˜ν•œ νŒŒμΌμ€ subdirectory 와 λ™μΌν•˜κ±°λ‚˜ index 인 파일만 μ°ΎλŠ”λ‹€.

λ”°λΌμ„œ 디렉토리 ꡬ쑰λ₯Ό ν™œμš©ν•œ λΌμš°νŒ… 뿐 μ•„λ‹ˆλΌ 파일 정리 μžμ²΄κ°€ λΆˆκ°€λŠ₯ν•˜κΈ° λ•Œλ¬Έμ— μœ„μ™€ 같이 - λ˜λŠ” _λ₯Ό μ‚¬μš©ν•΄ κ΅¬λΆ„ν•˜μ˜€λ‹€.

  • /netlify/functions/user-id.mts
import { Config, Context } from "@netlify/functions";

const ALLOWED_METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE"];

const RESPONSE_INIT = {
  headers: {
    "Content-Type": "application/json; charset=utf-8",
  },
  status: 200,
};

export default function handler(request: Request, context: Context) {
  const method = ALLOWED_METHODS.find((method) => method === request.method);
  if (method === undefined) return;
  return user[method](request, context);
}

const user: Record<string, Function> = {
  GET: getUser,
  POST: postUser,
  PUT: putUser,
  PATCH: patchUser,
  DELETE: deleteUser,
};

function getUser(request: Request, context: Context): Response {
  const { id } = context.params;
  return new Response(JSON.stringify({ message: `${id} μ‚¬μš©μž 정보에 λŒ€ν•œ μš”μ²­` }), RESPONSE_INIT);
}

// ...

export const config: Config = {
  path: ["/user/:id"],
};


이제 API μš”μ²­μ€ λ‹€μŒκ³Ό 같이 URL Parameters λ₯Ό ꡬ뢄할 수 있게 λœλ‹€.

  • GET /user/ μš”μ²­μ— λŒ€ν•œ 응닡
{
  "name": "Hogwarts",
  "age": 32,
  "favorite": [ "Movie", "Music", "Book", "Beer" ]
}
  • GET /api/user/uuid005435 μš”μ²­μ— λŒ€ν•œ 응닡
{
    "message": "uuid005435 μ‚¬μš©μž 정보에 λŒ€ν•œ μš”μ²­"
}




Reference

  1. λ°•μ˜μ›…, β€œν”„λ‘ νŠΈμ—”λ“œ μ›Ή 개발의 λͺ¨λ“  것 초격차 νŒ¨ν‚€μ§€ Online.” fastcampus.co.kr. last modified unknown, Fast Campus.
  2. β€œConfiguring Projects with vercel.json.” Vercel. Feb. 21, 2023, accessed May. 04, 2024, Vercel - Project Configuration.