// 이 파일이 하는 일: "Express로 CRUD API 만들기" 코스 — 3강에서 설계한 블로그 API 명세를
// 진짜 돌아가는 서버로 옮기는 여정. 요청이 서버를 통과하는 원리에서 출발해, 프로젝트 셋업,
// POST/GET/PATCH/DELETE 구현, 미들웨어 추출, API 테스트 습관, 라우터·계층 분리까지
// 8개 섹션으로 안내하는 정적 학습 페이지.
// 학습 포인트: API 호출이 없는 '내용 고정' 페이지다. 스타일은 styles/global.css의
// 가이드 전용 클래스(step-card, code-block, tip 등)를 재사용하고 색은 var(--*)만 쓴다(하드코딩 금지).
// 예제 코드는 JSX 중괄호/백틱 충돌을 피하려고 파일 상단의 백틱 문자열 상수로 정의한다.

import { Link } from 'react-router-dom';

// ── 예제 코드 상수들 ──

const CODE_JOURNEY = `브라우저/Postman                      우리 Express 서버
────────────────                    ─────────────────────────────
GET /posts/3  ──────────▶  ① 미들웨어들 (검문소: 로거, body 파서...)
                           ② 라우터    (주소·메서드 보고 담당자 배정)
                           ③ 핸들러    (실제 일: 배열에서 3번 글 찾기)
              ◀──────────  ④ 응답      (상태 코드 200 + JSON 본문)

핵심: 요청 하나는 반드시 이 한 줄의 여정을 지나요.
"어느 단계에서 멈췄지?"를 짚을 수 있으면 디버깅의 절반은 끝난 겁니다.
포트(3000)는 한 컴퓨터 안의 '문 번호', localhost는 '내 컴퓨터 자신'.
그러니 http://localhost:3000 은 "내 컴퓨터의 3000번 문을 두드린다"는 뜻이에요.`;

const CODE_SETUP = `# 빈 폴더에서 — 프로젝트의 뼈대 세우기
mkdir blog-api && cd blog-api
npm init -y            # package.json 생성 (프로젝트 신분증)
npm install express    # 서버 프레임워크 설치

# server.js — 최소한의 서버. 이게 전부입니다.
const express = require('express');
const app = express();

// 메모리 배열 = 이번 실습의 '임시 데이터베이스'
// (서버를 재시작하면 사라져요 — 다음 강좌에서 진짜 DB로 옮깁니다)
let posts = [
  { id: 1, title: '첫 글', content: '안녕하세요', author: 'mirim' },
  { id: 2, title: '둘째 글', content: 'Express 재밌다', author: 'mirim' },
];

// GET /posts — 목록 응답. res.json()이 배열을 JSON으로 바꿔 보낸다
app.get('/posts', (req, res) => {
  res.json(posts);
});

app.listen(3000, () => {
  console.log('서버가 3000번 포트에서 듣는 중');
});`;

const CODE_NODEMON = `# 매번 Ctrl+C → node server.js 를 반복하지 않기
npm install -D nodemon      # -D: 개발할 때만 쓰는 도구라는 표시

# package.json 의 scripts 에 한 줄
"scripts": {
  "dev": "nodemon server.js"
}

# 이제 npm run dev 로 켜 두면, 파일을 저장할 때마다
# nodemon이 알아서 서버를 재시작해 줍니다.
# 코드 수정 → 저장 → 바로 테스트, 이 리듬이 개발 속도를 만들어요.`;

const CODE_POST = `// ★ 이 한 줄이 없으면 req.body는 undefined —
//   "body가 안 읽혀요"의 90%가 이 줄을 빼먹은 경우입니다.
app.use(express.json());

// POST /posts — 새 글 생성
app.post('/posts', (req, res) => {
  const title = req.body.title;
  const content = req.body.content;

  // 필수 필드 검증: 클라이언트가 뭘 보낼지는 절대 믿지 않는다
  if (!title || !content) {
    return res.status(400).json({
      error: 'title과 content는 필수입니다',
    });
  }

  const newPost = {
    id: posts.length ? Math.max(...posts.map((p) => p.id)) + 1 : 1,
    title: title,
    content: content,
    author: req.body.author || '익명',
  };
  posts.push(newPost);

  // 201 Created + 방금 만들어진 리소스를 그대로 돌려준다.
  // 클라이언트는 서버가 붙여 준 id를 이 응답으로 알게 돼요.
  res.status(201).json(newPost);
});`;

const CODE_GET_DETAIL = `// GET /posts/:id — 경로 파라미터로 하나 꺼내기
app.get('/posts/:id', (req, res) => {
  // 주의: req.params.id 는 언제나 '문자열'. 숫자 비교 전에 변환!
  const id = Number(req.params.id);
  const post = posts.find((p) => p.id === id);

  if (!post) {
    // 없는 id 요청에 200 + null 을 주면 클라이언트가 혼란에 빠져요.
    // "그런 리소스 없음"은 반드시 404로 말합니다.
    return res.status(404).json({ error: '글을 찾을 수 없습니다' });
  }
  res.json(post);
});

// GET /posts?q=검색어&page=1&limit=10 — 쿼리스트링으로 검색·페이지네이션
app.get('/posts', (req, res) => {
  let result = posts;

  // 검색: q가 있으면 제목에 포함된 글만
  if (req.query.q) {
    result = result.filter((p) => p.title.includes(req.query.q));
  }

  // 페이지네이션: page·limit도 문자열로 오니 숫자로 변환
  const page = Number(req.query.page) || 1;
  const limit = Number(req.query.limit) || 10;
  const start = (page - 1) * limit;
  res.json(result.slice(start, start + limit));
});`;

const CODE_PATCH_DELETE = `// PATCH /posts/:id — 부분 수정: '온 필드만' 반영한다
app.patch('/posts/:id', (req, res) => {
  const id = Number(req.params.id);
  const post = posts.find((p) => p.id === id);
  if (!post) {
    return res.status(404).json({ error: '글을 찾을 수 없습니다' });
  }

  // undefined 체크가 핵심 — title만 보냈으면 content는 건드리지 않는다.
  // 이 조건 없이 통째로 덮어쓰면 안 보낸 필드가 undefined로 지워져요.
  if (req.body.title !== undefined) post.title = req.body.title;
  if (req.body.content !== undefined) post.content = req.body.content;

  res.json(post);   // 수정된 최종 모습을 돌려준다
});

// DELETE /posts/:id — 삭제
app.delete('/posts/:id', (req, res) => {
  const exists = posts.some((p) => p.id === Number(req.params.id));
  if (!exists) {
    // 방침 결정 지점: 이미 없는 걸 지우라고 하면?
    // 우리는 "없다"를 정직하게 404로 알리는 쪽을 택합니다.
    return res.status(404).json({ error: '이미 없는 글입니다' });
  }
  posts = posts.filter((p) => p.id !== Number(req.params.id));

  // 204 No Content: "잘 지웠고, 돌려줄 본문은 없다"
  // json()이 아니라 end()인 이유 — 204엔 본문을 실으면 안 되니까요.
  res.status(204).end();
});`;

const CODE_LOGGER = `// 요청 로거 미들웨어 — 모든 요청이 지나는 첫 검문소
function logger(req, res, next) {
  console.log(new Date().toISOString(), req.method, req.url);
  next();   // ★ 다음 검문소로 통과시킨다. 이걸 빼먹으면?
            //   요청이 여기 갇혀서 브라우저가 영원히 빙글빙글 돌아요.
}

// 등록 '순서'가 곧 실행 순서 — 라우트보다 위에 있어야 모든 요청을 본다
app.use(logger);
app.use(express.json());
// ... 이 아래에 라우트들

// 실행해 보면 매 요청마다 이렇게 찍힙니다:
// 2026-07-20T02:10:31.000Z GET /posts
// 2026-07-20T02:10:45.000Z POST /posts`;

const CODE_EXISTS_MW = `// '리소스 존재 확인' 미들웨어 — 세 핸들러에 반복되던 404 로직을 한 곳으로
function loadPost(req, res, next) {
  const post = posts.find((p) => p.id === Number(req.params.id));
  if (!post) {
    return res.status(404).json({ error: '글을 찾을 수 없습니다' });
  }
  req.post = post;   // 찾은 결과를 req에 실어 다음 단계로 전달
  next();
}

// 두 번째 인자로 끼워 넣으면, 핸들러가 실행되기 '전에' 먼저 돈다
app.get('/posts/:id', loadPost, (req, res) => {
  res.json(req.post);            // 이미 검증됐으니 바로 사용
});
app.patch('/posts/:id', loadPost, (req, res) => {
  if (req.body.title !== undefined) req.post.title = req.body.title;
  if (req.body.content !== undefined) req.post.content = req.body.content;
  res.json(req.post);
});

// find + 404 코드가 세 군데서 한 군데로 — 이게 미들웨어의 힘입니다.`;

const CODE_HTTP_FILE = `### api.http — VS Code 'REST Client' 확장으로 실행하는 요청 시나리오
### 각 요청 위의 "Send Request"를 누르면 결과가 옆에 뜹니다

### 1. 목록 조회
GET http://localhost:3000/posts

### 2. 글 생성 (성공 → 201 기대)
POST http://localhost:3000/posts
Content-Type: application/json

{ "title": "테스트 글", "content": "REST Client에서 보냄" }

### 3. 필수 필드 누락 (실패 → 400 기대)
POST http://localhost:3000/posts
Content-Type: application/json

{ "title": "content가 없는 글" }

### 4. 없는 글 조회 (실패 → 404 기대)
GET http://localhost:3000/posts/9999

### 5. 부분 수정 → 6. 삭제(204) → 7. 재삭제(404) ... 순서로 이어가기
### 포인트: '실패 케이스'까지 시나리오에 남겨 두는 게 진짜 테스트예요.`;

const CODE_ROUTER = `// routes/posts.js — 글 관련 주소를 몽땅 이 파일로
const express = require('express');
const router = express.Router();
const postService = require('../services/postService');

// 여기선 '/posts'를 다시 안 써요 — server.js에서 접두어를 붙여 주니까
router.get('/', (req, res) => {
  res.json(postService.findAll(req.query));
});
router.post('/', (req, res) => {
  const created = postService.create(req.body);   // 검증·생성은 서비스가
  res.status(201).json(created);                  // HTTP 응답은 라우터가
});

module.exports = router;

// ── server.js — 이제 조립만 담당 ──
const postsRouter = require('./routes/posts');
app.use('/posts', postsRouter);   // /posts 로 오는 건 전부 저 파일로

// 폴더 구조가 곧 팀의 지도:
// blog-api/
// ├─ server.js            ← 조립: 미들웨어·라우터 등록
// ├─ routes/posts.js      ← 입출구: 주소 ↔ HTTP 응답
// └─ services/postService.js ← 비즈니스 로직: 데이터를 다루는 실제 일`;

// ── 이 페이지 안에서만 쓰는 작은 부품들 ──
// 학습 포인트: 같은 모양이 반복되면 컴포넌트로 뽑는다. props만 바꿔 재사용.
function Section({ n, title, sub, children }) {
  return (
    <section className="step-card" id={`sec-${n}`}>
      <div className="step-head">
        <span className="step-num">{n}</span>
        <div>
          <h3>{title}</h3>
          {sub && <span className="step-sub">{sub}</span>}
        </div>
      </div>
      <div className="step-body">{children}</div>
    </section>
  );
}

function Code({ children }) {
  return <pre className="code-block"><code>{children}</code></pre>;
}

const SECTIONS = [
  { n: 1, label: '요청의 여정' },
  { n: 2, label: '셋업과 첫 GET' },
  { n: 3, label: 'POST: 생성' },
  { n: 4, label: 'GET 상세·목록' },
  { n: 5, label: 'PATCH·DELETE' },
  { n: 6, label: '미들웨어 추출' },
  { n: 7, label: '테스트 습관' },
  { n: 8, label: '라우터와 계층' },
];

export default function Lv2ExpressCrudPage() {
  return (
    <div>
      {/* 히어로: 이 코스가 어디서 출발해 어디까지 가는지 */}
      <div className="hero">
        <div className="eyebrow">Course · 중급 과정</div>
        <h1>🛠️ Express로<br />CRUD API 만들기</h1>
        <p>
          3강에서 우리는 블로그 API의 <strong>명세</strong>를 종이 위에 설계했어요. 이번엔 그
          설계도를 <strong>진짜 돌아가는 서버</strong>로 옮깁니다. 요청이 서버를 통과하는 원리부터
          라우터·미들웨어·계층 분리까지 — 생성(C)·조회(R)·수정(U)·삭제(D) 전 구간을 내 손으로
          구현하고, Postman 시나리오로 끝까지 검증해 봅시다.
        </p>
        <div className="chip-row">
          <span className="chip">예상 소요 75분</span>
          <span className="chip">실습: 블로그 API 전 구간 구현·검증</span>
          <span className="chip">선수 지식: JS 기초 · HTTP/REST(3강)</span>
        </div>
      </div>

      {/* 섹션 바로가기 */}
      <nav className="pill-nav">
        {SECTIONS.map((s) => (
          <a key={s.n} href={`#sec-${s.n}`}>
            <b>{s.n}</b>
            {s.label}
          </a>
        ))}
      </nav>

      <Section n={1} title="서버가 요청을 받는 원리" sub="요청 → 라우터 → 핸들러 → 응답의 여정">
        <p>
          코드를 치기 전에, 요청 하나가 서버 안에서 <strong>어떤 길을 걷는지</strong>부터
          그려 봅시다. 브라우저나 Postman이 <span className="icode">GET /posts/3</span>을 보내면,
          Express는 그 요청을 <strong>① 미들웨어 → ② 라우터 → ③ 핸들러 → ④ 응답</strong>의
          한 줄로 통과시켜요. 이 지도 한 장이 이번 코스 전체의 뼈대입니다.
        </p>
        <Code>{CODE_JOURNEY}</Code>
        <p>
          <strong>미들웨어(middleware)</strong>는 요청이 핸들러에 닿기 전에 거치는{' '}
          <strong>검문소</strong>예요. 공항 보안 검색대를 떠올리면 정확합니다 — 모든 승객(요청)이
          차례로 통과하면서 짐 검사(body 파싱), 기록(로깅), 신분 확인(인증) 같은 공통 절차를
          거치죠. 검문을 통과시키는 신호가 <span className="icode">next()</span>인데, 이건 섹션
          6에서 직접 만들어 보며 손에 익힙니다.
        </p>
        <p>
          <strong>포트와 localhost</strong>도 정확히 해 둘게요. 한 컴퓨터에는 여러 프로그램이 동시에
          네트워크를 쓰기 때문에, 프로그램마다 <strong>문 번호(포트)</strong>를 하나씩 배정받아요.
          우리 서버가 3000번 문 앞에서 기다리겠다고 선언하는 게{' '}
          <span className="icode">app.listen(3000)</span>이고,{' '}
          <span className="icode">localhost</span>는 "다른 컴퓨터가 아니라 <strong>내 컴퓨터
          자신</strong>"을 가리키는 특별한 이름입니다.
        </p>
        <div className="tip">
          <b>디버깅의 반은 지도에서</b> "응답이 안 와요"라는 문제는 대부분 이 여정의{' '}
          <strong>어느 한 단계</strong>에서 멈춘 거예요. 서버가 안 떠 있나(listen), 주소·메서드가
          안 맞나(라우터), 핸들러가 응답을 안 보냈나(res 누락), 미들웨어가 next()를 안 불렀나 —
          단계를 짚어 가며 좁히는 습관이 이 코스에서 가장 오래 남을 기술입니다.
        </div>
      </Section>

      <Section n={2} title="프로젝트 셋업과 첫 GET" sub="npm init부터 배열 응답까지, 최소 구성">
        <p>
          서버 하나 띄우는 데 필요한 건 생각보다 적어요. <span className="icode">npm init -y</span>
          로 프로젝트 신분증(<span className="icode">package.json</span>)을 만들고, Express를
          설치하고, 파일 하나에 열 줄 남짓 — 이게 전부입니다. 데이터베이스는 아직 없어요. 이번
          실습에선 <strong>메모리 배열</strong>이 임시 DB 역할을 합니다.
        </p>
        <Code>{CODE_SETUP}</Code>
        <p>
          <span className="icode">node server.js</span>로 켜고 브라우저에서{' '}
          <span className="icode">http://localhost:3000/posts</span>를 열면 글 두 개가 담긴 JSON
          배열이 보일 거예요. 축하합니다 — 방금 <strong>여러분의 첫 API 서버</strong>가 응답한
          겁니다. <span className="icode">res.json()</span>은 자바스크립트 배열·객체를 JSON
          문자열로 바꾸고, "이건 JSON이야"라는 안내(Content-Type 헤더)까지 붙여서 보내 줘요.
        </p>
        <p>
          그런데 코드를 고칠 때마다 서버를 껐다 켜는 건 금방 지칩니다.{' '}
          <strong>nodemon</strong>을 붙여 "저장하면 자동 재시작"되는 개발 흐름을 만들어 두세요.
        </p>
        <Code>{CODE_NODEMON}</Code>
        <div className="tip">
          <b>메모리 배열의 운명</b> <span className="icode">let posts = [...]</span>는 서버
          프로세스의 메모리에만 존재해요. 서버를 재시작하면(nodemon이 재시작할 때도!) 추가했던
          글이 <strong>전부 초기 상태로 돌아갑니다.</strong> 버그가 아니라 구조예요. "데이터가
          서버의 삶과 운명을 같이한다"는 이 불편함이, 다음 강좌에서 DB를 배우는 이유가 됩니다.
        </div>
      </Section>

      <Section n={3} title="POST: 생성 구현" sub="body 파싱, 201, 그리고 400으로 거절하기">
        <p>
          이제 클라이언트가 보낸 데이터로 <strong>새 글을 만들어</strong> 봅시다. POST 요청의
          데이터는 주소가 아니라 <strong>본문(body)</strong>에 실려 오는데, Express는 기본적으로
          body를 읽지 않아요. <span className="icode">app.use(express.json())</span>이라는
          검문소를 세워야 JSON 본문이 <span className="icode">req.body</span> 객체로 풀립니다.
        </p>
        <Code>{CODE_POST}</Code>
        <p>
          여기서 상태 코드 두 개가 일하고 있어요. 성공하면 <strong>201 Created</strong> —
          "만들어졌다"를 뜻하는 전용 코드로, 200보다 정확한 표현입니다. 이때{' '}
          <strong>생성된 리소스를 응답 본문에 담아</strong> 돌려주는 게 관례예요. 서버가 붙여 준{' '}
          <span className="icode">id</span>를 클라이언트가 알아야 다음 요청(상세 조회, 수정)을
          이어 갈 수 있으니까요.
        </p>
        <p>
          그리고 <strong>400 Bad Request</strong> — 필수 필드가 빠진 요청은 배열에 넣기 전에
          거절합니다. 클라이언트가 보낸 데이터는 <strong>절대 믿지 않는다</strong>가 서버 개발의
          기본 자세예요. 검증 없이 저장하면 <span className="icode">title: undefined</span>인
          유령 글이 쌓이고, 나중에 목록 화면이 깨지고 나서야 원인을 찾게 됩니다.
        </p>
        <div className="tip">
          <b>return을 빼먹으면</b> <span className="icode">res.status(400).json(...)</span> 앞의{' '}
          <span className="icode">return</span>이 없으면, 응답을 보낸 뒤에도 코드가 계속 흘러서
          글이 <strong>저장까지 돼 버립니다.</strong> 심하면 "응답을 두 번 보냈다"는{' '}
          <span className="icode">ERR_HTTP_HEADERS_SENT</span> 에러도 만나요. "응답을 보냈으면
          함수를 끝낸다" — return 습관으로 기억하세요.
        </div>
      </Section>

      <Section n={4} title="GET 상세·목록 완성" sub=":id로 하나 꺼내기, 없으면 404, 쿼리로 검색">
        <p>
          목록은 있으니 이제 <strong>하나만 콕 집어</strong> 꺼내 봅시다. 주소의{' '}
          <span className="icode">/posts/:id</span>에서 <span className="icode">:id</span>는{' '}
          <strong>경로 파라미터</strong> — "이 자리에 오는 값은 뭐든 받아서{' '}
          <span className="icode">req.params.id</span>에 담아 줘"라는 자리표시자예요.
        </p>
        <Code>{CODE_GET_DETAIL}</Code>
        <p>
          두 가지 함정이 숨어 있어요. 첫째, <strong>URL에서 온 값은 전부 문자열</strong>입니다.{' '}
          <span className="icode">'3' === 3</span>은 false라서, <span className="icode">Number()</span>
          변환 없이 비교하면 멀쩡한 글도 못 찾아요. 둘째, <strong>없는 id에는 반드시 404</strong>
          를 보냅니다. 200에 빈 응답을 주면 클라이언트는 "성공했는데 데이터가 이상하네?"라며
          엉뚱한 곳을 뒤지게 돼요. 상태 코드는 서버가 클라이언트에게 하는 <strong>정직한
          말</strong>입니다.
        </p>
        <p>
          목록 쪽엔 <strong>쿼리스트링</strong>을 붙였어요. <span className="icode">?q=Express&amp;page=1</span>
          처럼 주소 뒤에 붙는 옵션들은 <span className="icode">req.query</span>로 들어옵니다.
          경로 파라미터가 "<strong>어떤 리소스</strong>냐"를 정한다면, 쿼리스트링은 "그 목록을{' '}
          <strong>어떻게 보여 줄</strong> 거냐"(검색·정렬·페이지)를 정해요 — 3강에서 설계한 그
          구분이 코드로 그대로 내려온 겁니다.
        </p>
        <div className="tip">
          <b>페이지네이션은 배려다</b> 글이 10만 개인데 <span className="icode">GET /posts</span>가
          전부 돌려준다면? 응답은 느려지고 클라이언트는 메모리로 앓아요.{' '}
          <span className="icode">page</span>·<span className="icode">limit</span>으로 잘라 보내는
          건 성능 최적화이기 전에 <strong>API를 쓰는 상대에 대한 배려</strong>입니다. 실무 API
          목록 응답에 페이지네이션이 빠지는 일은 거의 없어요.
        </div>
      </Section>

      <Section n={5} title="PATCH와 DELETE" sub="온 필드만 반영하기, 삭제 성공은 204">
        <p>
          수정에는 두 가지 철학이 있어요. <strong>PUT</strong>은 "통째로 교체"(안 보낸 필드는
          사라짐), <strong>PATCH</strong>는 "<strong>보낸 필드만</strong> 고침". 블로그에서
          제목만 살짝 바꾸는 시나리오라면 PATCH가 자연스럽죠. 핵심은{' '}
          <span className="icode">undefined</span> 체크 — "이 필드가 요청에 실려 왔는가"를
          확인하고 온 것만 반영하는 로직입니다.
        </p>
        <Code>{CODE_PATCH_DELETE}</Code>
        <p>
          삭제 성공의 응답은 <strong>204 No Content</strong>입니다. "잘 처리했고, 돌려줄 내용은
          없다"는 뜻이에요. 이미 지워진 걸 다시 보여 줄 이유가 없으니 본문 없이 상태 코드만
          보내는 거죠. 그래서 <span className="icode">res.json()</span>이 아니라{' '}
          <span className="icode">res.status(204).end()</span>를 씁니다.
        </p>
        <p>
          그리고 설계자가 <strong>방침을 정해야 하는 순간</strong>이 하나 있어요 — 이미 없는
          리소스를 지우라는 요청이 오면? "없다"는 사실을 알리는 <strong>404</strong>파와, "지워진
          상태가 목표였으니 결과는 같다"며 <strong>204</strong>를 주는 파가 실무에도 둘 다
          있습니다. 우리는 클라이언트의 버그(잘못된 id 사용)를 일찍 드러내 주는 404를 택했어요.
          어느 쪽이든 <strong>일관되게 정하고 명세에 적어 두는 것</strong>이 정답입니다.
        </p>
        <div className="tip">
          <b>여기까지가 CRUD 전부</b> POST(Create) · GET(Read) · PATCH(Update) · DELETE(Delete) —
          3강 명세의 네 동사가 모두 코드가 됐어요. 나머지 섹션들은 이 코드를{' '}
          <strong>더 깔끔하게, 더 믿을 수 있게</strong> 만드는 이야기입니다.
        </div>
      </Section>

      <Section n={6} title="미들웨어로 공통 로직 빼기" sub="검문소를 직접 만들고 next()를 이해하기">
        <p>
          지금까지 <span className="icode">express.json()</span>이라는 남이 만든 검문소를
          썼다면, 이제 <strong>직접 만들어</strong> 봅시다. 미들웨어는 그냥{' '}
          <span className="icode">(req, res, next)</span> 세 개를 받는 함수예요. 첫 작품은 모든
          요청을 기록하는 <strong>로거</strong>입니다.
        </p>
        <Code>{CODE_LOGGER}</Code>
        <p>
          <span className="icode">next()</span>가 이 구조의 심장이에요. "내 검문은 끝났으니{' '}
          <strong>다음 순번에게 넘겨라</strong>"는 신호죠. 이걸 부르지 않으면 요청은 그 검문소에
          갇혀서 응답 없이 멈춥니다. 그리고 <strong>등록 순서가 곧 실행 순서</strong> —{' '}
          <span className="icode">app.use(logger)</span>를 라우트들보다 아래에 두면, 이미 핸들러가
          응답해 버린 뒤라 로거는 구경도 못 해요.
        </p>
        <p>
          두 번째 작품은 더 실전적이에요. 섹션 4~5에서 상세·수정·삭제 핸들러마다{' '}
          <span className="icode">find</span> + 404 코드가 <strong>세 번 복붙</strong>됐던 것,
          눈치챘나요? 그 반복을 미들웨어 하나로 추출합니다.
        </p>
        <Code>{CODE_EXISTS_MW}</Code>
        <div className="tip">
          <b>어디서 본 감각이죠?</b> "같은 코드가 반복되면 함수로 뽑는다"는 원칙이 서버에선
          "공통 전처리가 반복되면 <strong>미들웨어로 뽑는다</strong>"가 됩니다. 찾은 결과를{' '}
          <span className="icode">req.post</span>에 실어 다음 단계로 넘기는 패턴도 기억해 두세요 —
          나중에 배울 인증 미들웨어가 <span className="icode">req.user</span>에 로그인 정보를
          실어 주는 것과 똑같은 원리입니다.
        </div>
      </Section>

      <Section n={7} title="API 테스트 습관" sub="주소창의 한계, 그리고 시나리오를 저장하는 이유">
        <p>
          "브라우저 주소창에 치면 되지 않나요?" — 절반만 맞아요. 주소창에 URL을 입력하는 행위는{' '}
          <strong>언제나 GET 요청</strong>입니다. 메서드를 바꿀 수도, JSON 본문을 실을 수도,
          헤더를 만질 수도 없죠. 우리가 만든 POST·PATCH·DELETE는 주소창으로는{' '}
          <strong>테스트 자체가 불가능</strong>합니다. 그래서 API 전용 도구가 필요해요.
        </p>
        <p>
          <strong>Postman</strong>은 메서드·헤더·본문을 자유롭게 조립해 쏘는 대표 도구고, VS Code의{' '}
          <strong>REST Client</strong> 확장은 <span className="icode">.http</span> 파일에 요청을
          적어 두고 에디터 안에서 바로 실행하는 방식이에요. 어느 쪽이든 핵심은 같습니다 —{' '}
          <strong>요청을 일회용으로 쏘고 버리지 말고, 시나리오로 저장</strong>하세요.
        </p>
        <Code>{CODE_HTTP_FILE}</Code>
        <p>
          이렇게 저장해 두면 코드를 고칠 때마다 <strong>전 구간을 몇 초 만에 재검증</strong>할
          수 있어요. 특히 3번·4번 같은 <strong>실패 케이스</strong>(400·404가 제대로 나오는가)를
          시나리오에 남겨 두는 게 중요합니다 — 성공만 확인한 API는 절반만 검증된 API예요.{' '}
          <span className="icode">.http</span> 파일은 코드와 함께 저장소에 커밋되니, 팀 동료에게
          "이 API는 이렇게 쓰는 거예요"라는 살아 있는 문서가 되기도 하고요.
        </p>
        <div className="tip">
          <b>캡처해 기록하는 습관</b> 요청과 응답(상태 코드·본문)을 캡처해서 실습 노트에
          남기세요. "내가 뭘 보냈고 서버가 뭐라 답했나"를 기록하는 습관은 나중에 버그 리포트를
          쓸 때, 협업 중 "제 쪽에선 이렇게 나오는데요?"를 증명할 때 그대로 힘이 됩니다. 이번
          실습 과제의 제출물에도 이 캡처 기록이 포함돼요.
        </div>
      </Section>

      <Section n={8} title="라우터 분리와 계층" sub="한 파일 탈출 — 폴더 구조가 협업을 만든다">
        <p>
          지금 우리 <span className="icode">server.js</span>엔 서버 설정, 데이터, 라우트 8개가
          한 파일에 살아요. 혼자 할 땐 괜찮지만, 글·댓글·사용자로 기능이 늘고 동료가 합류하는
          순간 <strong>모두가 한 파일을 고치며 충돌</strong>하게 됩니다. Express가 준비해 둔
          해법이 <span className="icode">express.Router</span> — 라우트 묶음을 별도 파일로 떼어
          내는 도구예요.
        </p>
        <Code>{CODE_ROUTER}</Code>
        <p>
          한 걸음 더 나아가 <strong>계층 분리</strong>도 맛봤어요. <strong>라우터</strong>는
          입출구 — 주소를 받고 상태 코드로 답하는 HTTP 담당. <strong>서비스</strong>는 비즈니스
          로직 — 검증하고, 만들고, 찾는 실제 일 담당. 이렇게 나누면 나중에 메모리 배열을 DB로
          바꿀 때 <strong>서비스 파일만 고치면</strong> 되고, 라우터는 한 줄도 안 건드려요.
          역할이 나뉘어 있으면 교체도 국소적입니다.
        </p>
        <p>
          폴더 구조는 곧 <strong>팀의 지도</strong>예요. "댓글 API 담당해 주세요"라는 요청이{' '}
          <span className="icode">routes/comments.js</span>와{' '}
          <span className="icode">services/commentService.js</span> 두 파일로 명확히 번역되고,
          서로 다른 기능을 맡은 동료와 같은 파일에서 충돌할 일이 사라집니다. 잘 나눈 구조는
          코드 정리를 넘어 <strong>협업의 속도</strong>를 바꿔요.
        </p>
        <div className="tip">
          <b>과하게 나누지는 말 것</b> 파일 3개짜리 프로젝트에 계층 7개를 만드는 건 배보다
          배꼽이에요. 지금 단계에선 <strong>라우터(입출구) / 서비스(로직)</strong> 두 층이면
          충분합니다. 구조는 코드가 자라며 아플 때 한 층씩 늘리는 것 — 이번 실습에서도 이
          두 층까지만 나눠 봅니다.
        </div>
      </Section>

      <div className="step-card">
        <div className="step-body">
          <h3 style={{ marginBottom: 6 }}>🛠️ 여기까지 왔다면</h3>
          <p className="muted">
            종이 위의 명세가 <strong>돌아가는 서버</strong>가 됐어요. 요청의 여정을 그릴 수 있고,
            CRUD 네 동사를 상태 코드(200·201·204·400·404)와 함께 구현했고, 반복 로직을
            미들웨어로 추출했고, 실패 케이스까지 시나리오로 검증하는 습관과 라우터·서비스 계층까지
            손에 쥐었습니다. 그런데 찜찜함이 하나 남았죠 — <strong>서버를 재시작하면 글이 다
            사라진다</strong>는 것. 데이터에게 서버의 삶과 무관한 '집'을 지어 줄 차례예요. 다음{' '}
            <Link to="/learn/lv2-db-transactions"><strong>DB 연동과 트랜잭션: 데이터가 사는
            집</strong></Link> 강좌에서, 메모리 배열을 진짜 데이터베이스로 옮기고 "전부 성공
            아니면 전부 취소"라는 트랜잭션의 감각을 이어서 익혀 봅시다.
          </p>
        </div>
      </div>
    </div>
  );
}
