1. Express

  • Express ๋ž€

  • URL ๊ตฌ์กฐ

  • REST API

  • HTTP Method(CRUD)

๊ฐ•์˜ ์ •๋ฆฌ

Express

ํ”„๋ก ํŠธ์—”๋“œ ๊ฐœ๋ฐœ์ž๋ผ๊ณ  ํ•ด๋„ BFF(Backend for Frontend) ํ˜น์€ NextJS๋“ฑ ๋ฐฑ์—”๋“œ์— ๋Œ€ํ•œ ์ง€์‹์ด ํ•„์š”ํ•˜๋‹ค. ํ…Œ์ŠคํŠธ ์ฝ”๋“œ์—์„œ Mocking์„ ํ•  ๋•Œ๋„ ํ•„์š”ํ•˜๋‹ค.

Express๋Š” Node.js ๊ธฐ๋ฐ˜์˜ ์„œ๋ฒ„ ํ”„๋ ˆ์ž„์›Œํฌ. ๊ต‰์žฅํžˆ ์—ญ์‚ฌ๊ฐ€ ์˜ค๋ž˜๋œ ํ”„๋ ˆ์ž„์›Œํฌ๋‹ค. NestJS, Apollo์—์„œ๋„ Express๋ฅผ ์ง€์›ํ•œ๋‹ค.

๊ฐ„๋‹จํ•œ ์„œ๋ฒ„ ์•ฑ npm ํŒจํ‚ค์ง€ ์„ธํŒ…

๊ณต์‹๋ฌธ์„œ๋Š” JS ๊ธฐ๋ฐ˜์œผ๋กœ ๋‚˜์™€์žˆ๊ธฐ ๋•Œ๋ฌธ์— TS ์ง€์›์„ ์œ„ํ•ด ์ถ”๊ฐ€์ ์ธ ์„ค์น˜๋ฅผ ํ•ด์ค„ ๊ฒƒ.

ํ”„๋กœ์ ํŠธ ํด๋” ๊ตฌ์„ฑ

mkdir express-demo-app
cd express-demo-app

.gitignore ํŒŒ์ผ์€ ํ•ญ์ƒ ์ž‘์„ฑํ•ด์ฃผ๊ธฐ

touch .gitignore
echo "/node_modules/" > .gitignore

ํŒจํ‚ค์ง€ ์ดˆ๊ธฐํ™”

npm init -y

TypeScript

npm i -D typescript
npx tsc --init

Eslint

npm i -D eslint
npx eslint --init

# import/export syntax ์„ ํƒ
# Node ํ™˜๊ฒฝ ์„ ํƒ
# Airbnb ์„ ํƒ

ts-node

npm i -D ts-node

Express

npm i express
npm i -D @types/express

localhost:3000์„ ์ฃผ์†Œ์ฐฝ์— ์ž…๋ ฅํ–ˆ์„ ๋•Œ ์™„์ „ํ•œ ํ˜•ํƒœ๋Š” http://localhost:3000/ ์ด๋‹ค. localhost๋Š” 127.0.0.1์ด๋‹ค. ์ž๊ธฐ ์ž์‹ ์„ ๊ฐ€๋ฆฌํ‚ค๋Š” ์ฃผ์†Œ, ๋ฃจํ”„๋ฐฑ ์ฃผ์†Œ๋ผ๊ณ ๋„ ๋ถˆ๋ฆฐ๋‹ค.

Hello World ์˜ˆ์ œ

app.ts ํŒŒ์ผ ์ž‘์„ฑ

res.send์™€ res.json์˜ ์ฐจ์ด์ 

res.send๋Š” argument๋ฅผ ํŒ๋‹จํ•˜์—ฌ Content-Type์„ ์žก์•„์ค€๋‹ค. argument๊ฐ€ object์ด๋ฉด ๋‚ด๋ถ€์—์„œ๋Š” res.json์„ ํ˜ธ์ถœํ•œ๋‹ค. res.json์€ Content-Type์ด json์ด ์•„๋‹ ๊ฒฝ์šฐ application/json์œผ๋กœ ์„ธํŒ…ํ•˜๊ณ  res.send๋ฅผ ํ˜ธ์ถœํ•œ๋‹ค. Content-Type์ด ๋‹ค๋ฅธ์ ์ด๋ผ ์ƒ๊ฐ๋˜๊ณ  json ํ˜•์‹์„ ๋ฐ˜ํ™˜ํ•ด์ฃผ๋Š” ๊ฒฝ์šฐ๋ฅผ ๋ช…์‹œ์ ์œผ๋กœ ํ‘œํ˜„ํ• ๋•Œ๋Š” res.json๋„ ์ข‹์€ ๊ฒƒ ๊ฐ™์œผ๋‚˜, argument๋ฅผ ๋ณด๊ณ ์„œ ์ถฉ๋ถ„ํžˆ ํŒ๋‹จํ•  ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ ํฐ ์˜๋ฏธ๋Š” ์—†๋Š” ๊ฒƒ ๊ฐ™๋‹ค.

// app.ts

// TypeScript์ด๊ธฐ ๋•Œ๋ฌธ์— ESModule(import, export)์„ ์‚ฌ์šฉํ•œ๋‹ค.
import express from 'express';

// ์„œ๋ฒ„์˜ ํฌํŠธ ๋ฒˆํ˜ธ๋Š” ํ™˜๊ฒฝ๋ณ€์ˆ˜๋กœ ๋งŽ์ด ๊ด€๋ฆฌํ•˜๋Š” ๊ฒƒ ๊ฐ™๋‹ค.
const port = 3000;

const app = express();

app.get('/', (req, res) => {
  res.send('Hello, world!');
});

app.listen(port, () => {
  console.log(`Server running at http://localhost:${port}`);
});

์‹คํ–‰์€ ts-node๋ฅผ ์ด์šฉํ•ด์„œ ํ•  ์ˆ˜ ์žˆ์œผ๋‚˜, ์ฝ”๋“œ๊ฐ€ ์ˆ˜์ •๋˜์—ˆ์„ ๋•Œ๋งˆ๋‹ค ์„œ๋ฒ„๋ฅผ ์žฌ์‹œ์ž‘ํ•ด์ฃผ๊ธฐ ์œ„ํ•ด nodemon์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒŒ ๊ฐœ๋ฐœ ํ™˜๊ฒฝ์—์„œ๋Š” ํŽธํ•˜๋‹ค. local์—์„œ๋Š” global๋กœ ์„ค์น˜ํ•ด์„œ ์‚ฌ์šฉํ•˜๋Š” ๊ฒŒ ํŽธํ•œ ๊ฒƒ ๊ฐ™๋‹ค.

npm i -g nodemon
nodemon app.ts

REST API

Roy Fielding - โ€œArchitectural Styles and the Design of Network-based Software Architecturesโ€ (2000)

Richardson Maturity Model

๋Œ€๊ฐœ๋Š” "ํ•„๋”ฉ ์ œ์•ฝ ์กฐ๊ฑด" 4๊ฐ€์ง€๋ฅผ ๋ชจ๋‘ ๋งŒ์กฑํ•˜์ง€ ์•Š๊ณ , Resource์™€ HTTP Verb๋งŒ ๋„์ž…ํ•˜๋Š” ์ˆ˜์ค€์œผ๋กœ ์‚ฌ์šฉํ•œ๋‹ค.

  • /write-post ์™€ ๊ฐ™์ด ์ž‘์„ฑํ•˜์ง€ ์•Š๊ณ 

  • /posts ์ฒ˜๋Ÿผ ์ž‘์„ฑํ•œ๋‹ค. (CRUD)

CRUD์— ๋Œ€ํ•ด HTTP Method๋ฅผ ๋Œ€์ž…ํ•œ๋‹ค. Read๋Š” Collection(๋ณต์ˆ˜)๊ณผ Item(Element)(๋‹จ์ˆ˜)๋กœ ๋‚˜๋‰œ๋‹ค.

๊ธฐ๋ณธ ๋ฆฌ์†Œ์Šค URL: /products

  1. Read (Collection) -> GET /products => ์ƒํ’ˆ ๋ชฉ๋ก ํ™•์ธ

  2. Read (Item) -> GET /products/{id} => ํŠน์ • ์ƒํ’ˆ ์ •๋ณด ํ™•์ธ

  3. Create (Collection Pattern ํ™œ์šฉ) -> POST /products => ์ƒํ’ˆ ์ถ”๊ฐ€ (JSON ์ •๋ณด ํ•จ๊ป˜ ์ „๋‹ฌ)

  4. Update (Item) -> PUT ๋˜๋Š” PATCH /products/{id} => ํŠน์ • ์ƒํ’ˆ ์ •๋ณด ๋ณ€๊ฒฝ (JSON ์ •๋ณด ํ•จ๊ป˜ ์ „๋‹ฌ)

  5. Delete (Item) -> DELETE /products/{id} => ํŠน์ • ์ƒํ’ˆ ์‚ญ์ œ

  • GET์€ request body์— ๊ฐ’์„ ๋„ฃ์–ด์ค„ ์ˆ˜ ์—†๋‹ค.

  • PUT์ด ๋จผ์ € ๋‚˜์™”๊ธฐ ๋•Œ๋ฌธ์— ๊ธฐ์กด์—๋Š” PUT์„ ๋งŽ์ด ์‚ฌ์šฉํ–ˆ์ง€๋งŒ ์ด์ œ PATCH๋„ ๋งŽ์ด ์‚ฌ์šฉํ•œ๋‹ค.

  • PUT์€ overwrite, ๋ฎ์–ด์“ฐ๋Š” ๋А๋‚Œ. ์—†์œผ๋ฉด ์ถ”๊ฐ€ํ•˜๊ณ , ์žˆ์œผ๋ฉด ๋ฎ์–ด์“ด๋‹ค.

  • PATCH๋Š” ์ผ๋ถ€๋งŒ ์ˆ˜์ •ํ•˜๋Š” ์—…๋ฐ์ดํŠธ๋ฅผ ํ•˜๋Š” ๋А๋‚Œ.

  • DELETE๋Š” DB์˜ row๋ฅผ hard deleteํ•˜์ง€ ์•Š๊ณ  soft delete๋ฅผ ํ•  ๋•Œ๋„ DELETE Method๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค.

  • ์‹ค์ œ๋กœ๋Š” row์˜ ํŠน์ • ์ปฌ๋Ÿผ๊ฐ’์„ ๋ณ€๊ฒฝํ•˜๋Š” ๊ฒƒ์ด์ง€๋งŒ, ๋ฆฌ์†Œ์Šค ์ž…์žฅ์—์„œ ๋ณผ ๋•Œ๋Š” ์‚ญ์ œ๋˜๋Š” ๊ฒƒ์ด๊ธฐ ๋•Œ๋ฌธ์— DELETE Method๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค.

Thinking in React ์˜ˆ์ œ

app.get('/products', (req, res) => {
  const products = [
    {
      category: 'Fruits',
      price: '$1',
      stocked: true,
      name: 'Apple',
    },
    {
      category: 'Fruits',
      price: '$1',
      stocked: true,
      name: 'Dragonfruit',
    },
    {
      category: 'Fruits',
      price: '$2',
      stocked: false,
      name: 'Passionfruit',
    },
    {
      category: 'Vegetables',
      price: '$2',
      stocked: true,
      name: 'Spinach',
    },
    {
      category: 'Vegetables',
      price: '$4',
      stocked: false,
      name: 'Pumpkin',
    },
    {
      category: 'Vegetables',
      price: '$1',
      stocked: true,
      name: 'Peas',
    },
  ];

  res.send({ products });
});

BFF๋ž€?

์นด์นด์˜คํŽ˜์ด์ง€์—์„œ BFF ์ ์šฉ๊ธฐ ์นด์นด์˜คํŽ˜์ด์ง€์—์„œ ๊ฒช์—ˆ๋˜ ๋ฌธ์ œ

  • ์—ฌ๋Ÿฌ ํ”Œ๋žซํผ(Web, Android, iOS, ...)์„ ์ง€์›ํ•˜๊ฒŒ ๋˜๋ฉด์„œ ๊ฐ๊ฐ ํŠน์ • ๋ฐ์ดํ„ฐ๊ฐ€ ํ•„์š”ํ•œ ์ƒํ™ฉ

  • ์›ํ•˜๋Š” ๋ฐ์ดํ„ฐ ํ˜•ํƒœ์— ๋„๋‹ฌํ•˜๊ธฐ ์œ„ํ•ด ์—ฌ๋Ÿฌ API ํ˜ธ์ถœ์˜ ์‘๋‹ต์„ ์กฐ์ž‘, ํ˜ผํ•ฉ, ์ผ์น˜์‹œํ‚ค๋Š” ์ƒํ™ฉ

  • ์ด๋Ÿฐ ์ƒํ™ฉ๋“ค์ด ๊ฒน์ณ ํ”„๋ก ํŠธ์—”๋“œ์—์„œ ๋ณต์žกํ•œ ๊ณ„์‚ฐ์ด๋‚˜ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์„ ์ž‘์„ฑํ•˜๋Š” ์ƒํ™ฉ

ํ”„๋ก ํŠธ์—”๋“œ์—์„œ ๋ณต์žกํ•œ ๊ณ„์‚ฐ์„ ์ˆ˜ํ–‰ํ•˜๋Š” ๊ฒฝ์šฐ ๋ Œ๋”๋ง์ด ๋А๋ ค์งˆ ์ˆ˜ ์žˆ๋‹ค. UI ์Šค๋ ˆ๋“œ์—์„œ ๋ Œ๋”๋ง๊ณผ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง ์ˆ˜ํ–‰์ด ๊ฒฝํ•ฉ์„ ๋ฒŒ์ด๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค. ๋ Œ๋”๋ง์€ ์œ ์ € ๊ฒฝํ—˜๊ณผ ๋งค์šฐ ๋ฐ€์ ‘ํ•œ ๊ด€๋ จ์ด ์žˆ๋‹ค. ์ด๋ฅผ ๊ฐœ์„ ํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” ์–ด๋– ํ•œ ๋ฐฉ๋ฒ•์ด ์žˆ์„๊นŒ?

๋‹ค์–‘ํ•œ ํ”Œ๋žซํผ์„ ์ง€์›ํ•ด์•ผ ํ•˜๋Š” API๋Š” ํด๋ผ์ด์–ธํŠธ๋งˆ๋‹ค ์‚ฌ์šฉํ•˜์ง€ ์•Š๋Š” ๋ถˆํ•„์š”ํ•œ ๋ฐ์ดํ„ฐ๊ฐ€ ํฌํ•จ๋  ์ˆ˜ ์žˆ๋‹ค. ์ถ”๊ฐ€๋กœ ์ง์ ‘ API์— ์˜์กดํ•˜๋Š” ๊ฒฝ์šฐ์— ์•„๋ž˜์™€ ๊ฐ™์€ ์ด์Šˆ๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ๋‹ค.

  • MSA ํ™˜๊ฒฝ์—์„œ API ์—”๋“œํฌ์ธํŠธ๊ฐ€ ๋ถ„๋ฆฌ๋  ๋•Œ ํŒ”๋กœ์—… ์ด์Šˆ

  • CORS

  • API ์ž…์žฅ์—์„œ ์—ฌ๋Ÿฌ ํ”Œ๋žซํผ๊ณผ ์ŠคํŽ™์„ ๋งž์ถฐ์•ผ ํ•˜๋Š” ๋น„์šฉ

  • ํ”Œ๋žซํผ๋ณ„๋กœ ๋‹ค๋ฅธ ์ธ์ฆ ๋ฐฉ์‹์„ ํ†ตํ•ฉํ•˜๋ ค๋Š” ๋ฌด๋ฆฌํ•œ ์‹œ๋„

  • 'ํ™”๋ฉด์— ํ•„์š”ํ•œ ๋ฐ์ดํ„ฐ๋งŒ ๋ฐ›๋Š”' partial response๋ฅผ ํ•˜๊ธฐ ์–ด๋ ค์šด ์ด์Šˆ

์ด์™€ ๊ฐ™์€ ๋ฌธ์ œ๋“ค์„ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด BFF๊ฐ€ ๋“ฑ์žฅํ–ˆ๋‹ค. ๋ง ๊ทธ๋Œ€๋กœ ํ”„๋ก ํŠธ์—”๋“œ๋ฅผ ์œ„ํ•œ ์ค‘๊ฐ„ ์„œ๋ฒ„๋ฅผ ๊ตฌํ˜„ํ•˜๋Š” ๊ฒƒ ํ•˜๋‚˜์˜ ํ”„๋ก ํŠธ์—”๋“œ์— ๋Œ€ํ•ด ํ•˜๋‚˜์˜ BFF๋ฅผ ๋‘์–ด์„œ ํ”„๋ก ํŠธ์—”๋“œ ์š”๊ตฌ์‚ฌํ•ญ์— ๋งž๊ฒŒ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ๋‹ค. ์—ฌ๋Ÿฌ ํ”Œ๋žซํผ์„ ์ง€์›ํ•˜์ง€ ์•Š์„ ๊ฒฝ์šฐ์—๋Š” BFF๊ฐ€ ์˜๋ฏธ ์—†์„ ์ˆ˜ ์žˆ๋‹ค.

์นด์นด์˜ค ํŽ˜์ด์ง€์—์„œ๋Š” iOS, Android, Web์„ ์ง€์›ํ•˜์ง€๋งŒ, Web์—์„œ๋งŒ BFF๋ฅผ ์ ์šฉํ•˜๊ณ  ์žˆ๋‹ค. BFF์—์„œ๋Š” ์ƒ์‚ฐ์„ฑ์„ ๋†’์ด๊ธฐ ์œ„ํ•ด ๋ฐ์ดํ„ฐ๋ฅผ ํ†ตํ•ฉํ•˜๋Š” ์ฒ˜๋ฆฌ๋ฅผ ๋‹ด๋‹นํ•œ๋‹ค. BFF๋ฅผ ์‚ฌ์šฉํ•จ์œผ๋กœ์จ ์•ž์—์„œ ๋งํ•œ API ์˜์กด์„ฑ ์ด์Šˆ๋ฅผ ์ฒ˜๋ฆฌํ•ด์ค„ ์ˆ˜ ์žˆ๋‹ค.

์นด์นด์˜ค ํŽ˜์ด์ง€์—์„œ๋Š” ํด๋ผ์ด์–ธํŠธ์—์„œ ๋ฐ›์•„์•ผํ•˜๋Š” ๋ฐ์ดํ„ฐ์˜ ํ˜•์‹์ด ์—ฌ๋Ÿฌ๊ฐ€์ง€์ธ ๊ฒฝ์šฐ๊ฐ€ ๋งŽ์•„์„œ BFF๋ฅผ ๋„์ž…ํ•˜๊ฒŒ ๋˜์—ˆ๋‹ค. ์นด์นด์˜ค ํŽ˜์ด์ง€์˜ BFF ๊ตฌ์กฐ๋Š” NextJS, apollo server, urql, redux๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ๋‹ค.

ts-node๋ž€?

JavaScript๋ฅผ ์›น ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ์•„๋‹Œ๊ณณ์—์„œ ์‹คํ–‰ํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” JavaScript Runtime์ธ Node.js๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค. TypeScript๋ฅผ ์‹คํ–‰ํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” ๋จผ์ € JavaScript ์ปดํŒŒ์ผํ•˜๋Š” ๊ณผ์ •์ด ํ•„์š”ํ•œ๋ฐ ์ด๋ฅผ ์ƒ๋žตํ•˜๊ณ  TypeScript๋กœ ์ž‘์„ฑ๋œ ํŒŒ์ผ์„ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ค€๋‹ค.

  • ts-node๋Š” Node.js๋ฅผ ์œ„ํ•œ TypeScript ์‹คํ–‰ ์—”์ง„, REPL์ด๋‹ค.

  • JIT ์ปดํŒŒ์ผ๋Ÿฌ๋ฅผ ์ด์šฉํ•˜์—ฌ TypeScript๋ฅผ JavaScript๋กœ ๋ณ€ํ™˜ํ•ด์ฃผ๊ณ , ํ”„๋ฆฌ์ปดํŒŒ์ผ์—†์ด ํƒ€์ž…์Šคํฌ๋ฆฝํŠธ๋ฅผ Node.js์—์„œ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ค€๋‹ค.

Last updated