πŸ•ŠοΈν”„λ‘œκ·Έλž˜λ¨ΈμŠ€ λ°λΈŒμ½”μŠ€/TIL

[λ°λΈŒμ½”μŠ€] HTTP μƒνƒœμ½”λ“œλ₯Ό ν™œμš©ν•œ μ˜ˆμ™Έμ²˜λ¦¬ 고도화(+ν•Έλ“€λŸ¬, λ¦¬μŠ€λ„ˆ, λ°°μ—΄ λ©”μ„œλ“œ)

ν‚ν‚μž‰ 2025. 4. 16. 19:45

πŸš—ν•Έλ“€λŸ¬μ™€ λ¦¬μŠ€λ„ˆ

ν•Έλ“€λŸ¬

μš”μ²­μ— μ˜ν•΄ ν˜ΈμΆœλ˜λŠ” λ©”μ„œλ“œ

HTTP μš”μ²­μ΄ λ‚ μ•„μ˜€λ©΄ μžλ™μœΌλ‘œ ν˜ΈμΆœλ˜λŠ” ν•¨μˆ˜λ₯Ό μ˜λ―Έν•œλ‹€. Node.jsμ—μ„œλŠ” 콜백 ν•¨μˆ˜ ν˜•νƒœλ‘œ app.HTTPMethod(path, ν•Έλ“€λŸ¬) ꡬ쑰둜 μ‚¬μš©ν•œλ‹€.

 

예λ₯Ό λ“€μ–΄, Expressμ—μ„œ GET μš”μ²­μ„ μ²˜λ¦¬ν•˜λŠ” ν•Έλ“€λŸ¬λŠ” λ‹€μŒκ³Ό 같이 μž‘μ„±ν•  수 μžˆλ‹€.

app.get('/youtubers', function(req, res) {
    //μ—¬κΈ°μ„œ function(req, res) {} 뢀뢄이 ν•Έλ“€λŸ¬!!
    res.json(youtubers);
});

 

ν•Έλ“€λŸ¬μ˜ μ—­ν• κ³Ό ꡬ쑰

ν•Έλ“€λŸ¬ ν•¨μˆ˜λŠ” 일반적으둜 req, res 두 개의 μ€‘μš”ν•œ λ§€κ°œλ³€μˆ˜λ₯Ό λ°›λŠ”λ‹€.

req(request): ν΄λΌμ΄μ–ΈνŠΈμ˜ μš”μ²­ 정보λ₯Ό λ‹΄κ³  μžˆλŠ” 객체

  • req.params: URL κ²½λ‘œμ—μ„œ μΆ”μΆœν•œ λ§€κ°œλ³€μˆ˜
  • req.query: URL 쿼리 λ¬Έμžμ—΄μ—μ„œ μΆ”μΆœν•œ 객체
  • req.body: μš”μ²­ λ³Έλ¬Έ 데이터
  • req.headers : HTTP 헀더 정보
  • req.cookies: μΏ ν‚€ 정보

res(response): ν΄λΌμ΄μ–ΈνŠΈμ—κ²Œ 보낼 응닡을 κ΅¬μ„±ν•˜λŠ” 객체

  • res.send() : λ‹€μ–‘ν•œ μœ ν˜•μ˜ 응닡 전솑
  • res.json() : JSON ν˜•μ‹μ˜ 응닡 전솑
  • res.status() : HTTP μƒνƒœ μ½”λ“œ μ„€μ •
  • res.redirect() : λ‹€λ₯Έ URL둜 λ¦¬λ‹€μ΄λ ‰νŠΈ
  • res.render() : ν…œν”Œλ¦Ώ 엔진을 μ‚¬μš©ν•˜μ—¬ λ·° λ Œλ”λ§

μ„ νƒμ μœΌλ‘œ, μ„Έ 번째 λ§€κ°œλ³€μˆ˜μΈ nextλ₯Ό 받을 μˆ˜λ„ μžˆλ‹€. μ΄λŠ” λ‹€μŒ 미듀웨어 ν•¨μˆ˜λ‘œ μ œμ–΄λ₯Ό λ„˜κΈ°λŠ” 데 μ‚¬μš©λœλ‹€.

app.get('/youtubers', function(req, res, next) {
  //쀑간 처리 둜직
  next(); //λ‹€μŒ λ―Έλ“€μ›¨μ–΄λ‘œ μ œμ–΄ 이동
});

 

λ¦¬μŠ€λ„ˆ

νŠΉμ • μ΄λ²€νŠΈκ°€ λ°œμƒν–ˆμ„ λ•Œ μ‹€ν–‰λ˜λŠ” ν•¨μˆ˜

λ¦¬μŠ€λ„ˆλŠ” Node.js의 이벀트 기반 μ•„ν‚€ν…μ²˜μ—μ„œ 핡심적인 역할을 ν•œλ‹€. Expressμ—μ„œλŠ” 주둜 μ„œλ²„ κ΄€λ ¨ μ΄λ²€νŠΈμ™€ μ²˜λ¦¬μ— λ¦¬μŠ€λ„ˆκ°€ μ‚¬μš©λœλ‹€.

 

μ„œλ²„ λ¦¬μŠ€λ„ˆ

μ„œλ²„ μ‹œμž‘ μ‹œ ν˜ΈμΆœλ˜λŠ” κ°€μž₯ 기본적인 λ¦¬μŠ€λ„ˆ

const server = app.listen(3000, () => {
  console.log('μ„œλ²„κ°€ 3000번 ν¬νŠΈμ—μ„œ μ‹€ν–‰ μ€‘μž…λ‹ˆλ‹€');
});

ν™”μ‚΄ν‘œ ν•¨μˆ˜κ°€ λ¦¬μŠ€λ„ˆμ΄λ‹€.

 

이벀트 이미터와 λ¦¬μŠ€ν„°

Node.jsλŠ” EventEmitter 클래슀λ₯Ό 톡해 이벀트 기반 ν”„λ‘œκ·Έλž˜λ°μ„ μ§€μ›ν•œλ‹€. Express μ• ν”Œλ¦¬μΌ€μ΄μ…˜κ³Ό HTTP μ„œλ²„λŠ” EventEmitter을 μƒμ†λ°›λŠ”λ‹€.

// HTTP μ„œλ²„ 이벀트 λ¦¬μŠ€λ„ˆ
server.on('error', (error) => {
  if (error.syscall !== 'listen') throw error;

  console.error(`μ„œλ²„ 였λ₯˜ λ°œμƒ: ${error}`);
  process.exit(1);
});

server.on('listening', () => {
  const addr = server.address();
  console.log(`μ„œλ²„κ°€ ${addr.port}번 ν¬νŠΈμ—μ„œ λ¦¬μŠ€λ‹ μ€‘μž…λ‹ˆλ‹€.`);
});

 

ν•Έλ“€λŸ¬μ™€ λ¦¬μŠ€λ„ˆμ˜ 차이

  • ν•Έλ“€λŸ¬: HTTP μš”μ²­μ„ μ²˜λ¦¬ν•˜λŠ” ν•¨μˆ˜(req, resλ₯Ό λ§€κ°œλ³€μˆ˜λ‘œ λ°›μŒ)
  • λ¦¬μŠ€λ„ˆ: νŠΉμ • 이벀트 λ°œμƒ μ‹œ μ‹€ν–‰λ˜λŠ” ν•¨μˆ˜(이벀트 κ΄€λ ¨ 데이터λ₯Ό λ§€κ°œλ³€μˆ˜λ‘œ λ°›μŒ)
⭐ ν•Έλ“€λŸ¬λŠ” λ¦¬μŠ€λ„ˆμ˜ 특수 ν˜•νƒœλ‘œ λ³Ό 수 μžˆλ‹€. HTTP μš”μ²­μ΄λΌλŠ” μ΄λ²€νŠΈμ— λŒ€ν•œ λ¦¬μŠ€λ„ˆκ°€ λ°”λ‘œ ν•Έλ“€λŸ¬!

πŸ”find() λ©”μ„œλ“œ

λ°°μ—΄μ—μ„œ νŠΉμ • 쑰건에 λ§žλŠ” μš”μ†Œλ₯Ό 찾을 λ•Œ, 일반적으둜 forEach 문을 μ‚¬μš©ν•˜μ—¬ λ‹€μŒκ³Ό 같이 μ½”λ“œλ₯Ό μž‘μ„±ν•  수 μžˆλ‹€.

fruits.forEach((fruit) => {
    if (fruit.id == id) {
        findFruit = fruit;
        }
    });

 

ν•˜μ§€λ§Œ, μžλ°”μŠ€ν¬λ¦½νŠΈμ˜ find() λ©”μ„œλ“œλ₯Ό μ‚¬μš©ν•˜λ©΄ 이 μ½”λ“œλ₯Ό ν•œ μ€„λ‘œ 쀄일 수 μžˆλ‹€.

var findFruit = fruits.find((fruit) => fruit.id == id);

⭐ find() λ©”μ„œλ“œλŠ” 쑰건을 λ§Œμ‘±ν•˜λŠ” 첫 번째 μš”μ†Œλ₯Ό λ°˜ν™˜ν•˜λ©°, λ§Œμ‘±ν•˜λŠ” μš”μ†Œκ°€ μ—†μœΌλ©΄ undefinedλ₯Ό λ°˜ν™˜ν•œλ‹€.

 

λ‹€λ₯Έ μœ μš©ν•œ λ©”μ„œλ“œλ“€

find() 외에도 λ°°μ—΄ μ‘°μž‘μ„ μœ„ν•œ μ—¬λŸ¬ μœ μš©ν•œ λ©”μ„œλ“œλ“€μ΄ μžˆλ‹€.

 

filter()

쑰건을 λ§Œμ‘±ν•˜λŠ” λͺ¨λ“  μš”μ†Œλ₯Ό λ°°μ—΄λ‘œ λ°˜ν™˜

const expensive = fruits.filter((fruit) => fruit.price > 1000);

 

map()

λ°°μ—΄μ˜ 각 μš”μ†Œλ₯Ό λ³€ν™˜ν•˜μ—¬ μƒˆ λ°°μ—΄ λ°˜ν™˜

const fruitNames = fruits.map((fruit) => fruit.name);

 

some()

ν•˜λ‚˜ μ΄μƒμ˜ μš”μ†Œκ°€ 쑰건을 λ§Œμ‘±ν•˜λŠ”μ§€ 확인(boolean λ°˜ν™˜)

const hasExpensive = fruits.some((fruit) => fruit.price > 1000);

 

every()

λͺ¨λ“  μš”μ†Œκ°€ 쑰건을 λ§Œμ‘±ν•˜λŠ”μ§€ 확인(boolean λ°˜ν™˜)

const allFresh = fruits.every((fruit) => fruit.fresh === true);

β¬†οΈμƒνƒœμ½”λ“œλ₯Ό ν™œμš©ν•˜μ—¬ μ˜ˆμ™Έμ²˜λ¦¬ κ³ λ„ν™”ν•˜κΈ°

μ΄μ „μ—λŠ” 응닡에 λŒ€ν•œ μƒνƒœ μ½”λ“œλ₯Ό κ³ λ €ν•˜μ§€ μ•Šμ€ 채 μ½”λ“œλ₯Ό μž‘μ„±ν•˜μ˜€λ‹€. ν•˜μ§€λ§Œ, 상황에 λ§žλŠ” μƒνƒœ μ½”λ“œλ₯Ό μ‚¬μš©ν•˜μ—¬ 더 λͺ…ν™•ν•œ 응닡을 μ œκ³΅ν•  수 μžˆκΈ°μ— REST APIλ₯Ό ꡬ좕할 땐, μ μ ˆν•œ HTTP μƒνƒœ μ½”λ“œλ₯Ό λ°˜ν™˜ν•˜λŠ” 것이 맀우 μ€‘μš”ν•˜λ‹€.

app.post("/test", function (req, res) {
  const channelName = req.body.channelName;

  if (channelName) {
    db.set(id++, req.body);

    res.status(201).json({
      message: `${db.get(id - 1).channelName}λ‹˜μ˜ μœ νŠœλ²„ μƒν™œμ„ μ‘μ›ν•©λ‹ˆλ‹€.`,
    });
  } else {
    res.status(400).json({ message: "μš”μ²­κ°’μ„ μ œλŒ€λ‘œ λ³΄λ‚΄μ£Όμ„Έμš”" });
  }
});
  • 201: λ¦¬μ†ŒμŠ€ 생성 성곡(POST μš”μ²­μ΄ μ„±κ³΅μ μœΌλ‘œ 처리됨)
  • 400: 잘λͺ»λœ μš”μ²­(ν•„μš”ν•œ 데이터가 λˆ„λ½λ¨)
β­μƒνƒœ μ½”λ“œλ₯Ό ν™œμš©ν•œ μ—λŸ¬μ²˜λ¦¬λŠ” API의 가독성과 μ‚¬μš©μ„±μ„ 크게 ν–₯μƒν•œλ‹€! ν΄λΌμ΄μ–ΈνŠΈλŠ” μƒνƒœ μ½”λ“œλ§ŒμœΌλ‘œ μš”μ²­ 처리 κ²°κ³Όλ₯Ό λΉ λ₯΄κ²Œ νŒŒμ•…ν•  수 있고, μ μ ˆν•œ 후속 쑰치λ₯Ό μ·¨ν•  수 μžˆλ‹€.

πŸ‘₯λ―Έλ‹ˆ ν”„λ‘œμ νŠΈ

μ§€κΈˆκΉŒμ§€ 배운 λ‚΄μš©λ“€μ„ ν™œμš©ν•˜μ—¬, κ°„λ‹¨ν•œ νšŒμ›κ°€μž… APIλ₯Ό μ‹€μŠ΅ν•΄λ³΄μ•˜λ‹€.

 

κ΅¬ν˜„ κΈ°λŠ₯

νšŒμ›
-둜그인
-νšŒμ›κ°€μž…
-νšŒμ› 정보 쑰회
-νšŒμ› νƒˆν‡΄

채널
-채널 생성
-채널 μˆ˜μ •
-채널 μ‚­μ œ

νšŒμ›μ€ 계정 1개 λ‹Ή μ΅œλŒ€ 100개 채널을 κ°€μ§ˆ 수 μžˆλ‹€.

 

API 섀계

둜그인

method post
path /login
req body(userId, password)
res 200: ${name} λ‹˜ 둜그인 성곡.
400: 잘λͺ»λœ μš”μ²­μž…λ‹ˆλ‹€.

 

νšŒμ›κ°€μž…

method post
path /signup
req body(userId, password, name)
res 201: ${name} λ‹˜ νšŒμ›κ°€μž…μ„ ν™˜μ˜ν•©λ‹ˆλ‹€.
400: μš”μ²­κ°’μ„ μ œλŒ€λ‘œ λ³΄λ‚΄μ£Όμ„Έμš”

 

νšŒμ› 쑰회(κ°œλ³„)

method get
path /users/:id
req params(id)
res 200: {userId, name}
404: μ‘΄μž¬ν•˜μ§€ μ•ŠλŠ” νšŒμ›μž…λ‹ˆλ‹€.

 

νšŒμ› νƒˆν‡΄(κ°œλ³„)

method delete
path /users/:id
req params(id)
res 200: νšŒμ›νƒˆν‡΄κ°€ μ™„λ£Œλ˜μ—ˆμŠ΅λ‹ˆλ‹€.
404: μ‘΄μž¬ν•˜μ§€ μ•ŠλŠ” νšŒμ›μž…λ‹ˆλ‹€.

 

API κ΅¬ν˜„

νšŒμ›κ°€μž…

app.post("/signup", function (req, res) {
  if (req.body !== undefined && req.body.userID) {
    db.set(id++, req.body);
    res.status(201).json({
      message: `${req.body.userID}λ‹˜ νšŒμ›κ°€μž…μ„ ν™˜μ˜ν•©λ‹ˆλ‹€.`,
    });
  } else {
    res.status(400).json({ message: "μš”μ²­κ°’μ„ μ œλŒ€λ‘œ λ³΄λ‚΄μ£Όμ„Έμš”" });
  }
});

 

νšŒμ› 쑰회(κ°œλ³„)

app.get("/users/:id", function (req, res) {
  let { id } = req.params;
  id = parseInt(id);

  const user = db.get(id);

  if (user == undefined) {
    res.status(404).json({ message: "μ‘΄μž¬ν•˜μ§€ μ•ŠλŠ” νšŒμ›μž…λ‹ˆλ‹€" });
  } else {
    res.status(200).json({ userId: user.userId, name: user.name });
  }
});

 

νšŒμ› μ‚­μ œ(κ°œλ³„)

app.delete("/users/:id", function (req, res) {
  let { id } = req.params;
  id = parseInt(id);

  const user = db.get(id);
  if (user == undefined) {
    res.status(404).json({ message: "μ‘΄μž¬ν•˜μ§€ μ•ŠλŠ” νšŒμ›μž…λ‹ˆλ‹€" });
  } else {
    db.delete(id);
    res.status(200).json({ message: "νšŒμ› νƒˆν‡΄κ°€ μ™„λ£Œλ˜μ—ˆμŠ΅λ‹ˆλ‹€" });
  }
});


μƒνƒœ μ½”λ“œλ₯Ό ν™œμš©ν•œ μ˜ˆμ™Έ μ²˜λ¦¬κ°€ 정말 μ€‘μš”ν•˜λ‹€λŠ” 것을 μ•Œ 수 μžˆμ—ˆλ‹€. μ μ ˆν•œ μƒνƒœ μ½”λ“œλ₯Ό ν•¨κ»˜ μ „μ†‘ν•˜λ‹ˆ API의 ν’ˆμ§ˆμ΄ 크게 ν–₯μƒλœ 것 처럼 λŠκ»΄μ‘Œλ‹€. 200, 201, 400, 404 μ½”λ“œλ§ŒμœΌλ‘œλ„ μš”μ²­μ˜ κ²°κ³Όλ₯Ό λΉ λ₯΄κ²Œ νŒλ‹¨ν•  수 μžˆλ‹€λŠ” 것을 μ•Œ 수 μžˆμ—ˆλ‹€.

 

이번 ν¬μŠ€νŠΈμ—μ„œ μ“°μ§€λŠ” μ•Šμ•˜μ§€λ§Œ, app.route()λ₯Ό ν™œμš©ν•˜μ—¬ λ™μΌν•œ μ—”λ“œν¬μΈνŠΈλ₯Ό κ°€μ§„ API듀을 λΆ„κΈ°ν•˜μ—¬ μ½”λ“œλ₯Ό 더 κΉ”λ”ν•˜κ²Œ μ§œλŠ” 방법을 잠깐 λ°°μ› λ‹€.μ•žμœΌλ‘œ 더 λ³΅μž‘ν•œ κΈ°λŠ₯을 κ°€μ§„ APIλ₯Ό κ°œλ°œν•˜κ²Œ 될 텐데, 정말 μœ μš©ν•˜κ²Œ μ“°μ΄κ² κ΅¬λ‚˜ μƒκ°ν–ˆλ‹€. .get().delete() 둜 μ½”λ“œκ°€ λ„ˆλ¬΄ κΈΈμ–΄μ Έμ„œ μ½”λ“œκ°€ 더 μ§§μ•„μ§€μ§„ μ•ŠλŠ” 것 κ°™μ§€λ§Œ..? μ•žμœΌλ‘œ 더 효율적인 처리 방식을 λ°°μš°μ§€ μ•Šμ„κΉŒ μ‹Άμ–΄ κΈ°λŒ€κ°€ λœλ‹€.