왕초보의 간단한 API 서버 구현을 통한 GET, POST 개념 이해하기

👩‍💻실습 순서

  1. GET/hello 실습하기

  2. GET/foods 실습하기

  3. POST/foods 실습하기

  4. 내가 생각한 방식으로 GET, POST 구상하기 <<< 다음 사례글에서

  5. 내가 생각한 방식으로 GET, POST 구현하기

🛠️사용 도구

Cursor(모델은 주로 Grok 4.5)

💦진행 과정

먼저 API 서버를 갓 배우는 초보자의 눈높이에 맞춰 설명하면서 구현해달라고 했습니다.


엔드포인트의 개념

엔드포인트

역할

GET /hello

서버가 살아있는지 확인하는 인사

GET /나만의-무언가

저장된 데이터 조회

POST /나만의-무언가

새 데이터 보내기/저장

추천 언어 : Node.js (JavaScript/TypeScript) + Express

이유

- 웹/API 예제가 가장 많음

- AI 도구와 코드 예시가 풍부함

- 프론트(JS)랑 문법이 비슷해서 나중에 확장하기 쉬움

- 설치-실행이 비교적 단순함

시작 전에 사용자가 결정해야 할 것

1\. 언어/프레임워크  

   예: Node.js + Express 또는 Python + FastAPI

2\. 나만의 엔드포인트 주제  

   예: /memo, /todo, /mood, /quote  

   → 경로 이름과 "무엇을 주고받는지"를 정해야 함

3\. 데이터 형태 (JSON)  

   예:

   ```json

   { "id": 1, "text": "오늘 공부하기", "done": false }

   ```

4\. 저장 방식  

   초보 실습이면 메모리(배열)로 충분  

   (서버 재시작하면 데이터 사라짐 --- 정상)

5\. 포트  

   보통 3000 또는 8000

6\. 테스트 방법  

   브라우저(GET만), 또는 Postman / Thunder Client / curl


포트 3000과 8000의 차이가 궁금해서 물어봤습니다.


포트 3000 vs 8000

기능 차이는 없습니다. 둘 다 "서버가 문 열고 기다리는 번호"일 뿐이에요.

3000

8000

의미

그냥 번호

그냥 번호

주요 사용처

Node/React 예제에서 자주 씀

Python(Django/FastAPI) 예제에서 자주 씀

선택기준

비어 있으면 아무거나 OK

비어 있으면 아무거나 OK

주의할 점만 있으면 됩니다.

- 이미 다른 프로그램이 그 포트를 쓰면 충돌남

- URL은 http://localhost:선택한포트 형태


그리고 터미널에 pnpm start로 실행하고 3000으로 들어가면 확인해볼 수 있다고 해서 주소창에 http://localhost:3000이라고 입력했습니다.

**Cannot GET /**이라고 나오더라고요.

당황해서 왜 그런지 물어봤어요.


원인은 단순해요. `http://localhost:3000/`에는 원래 엔드포인트가 없었어요.  

서버는 살아 있는데, / 경로만 정의가 안 되어 있어서 Cannot GET /이 뜬 겁니다.


😮깨달은 것 1

아,, url에 지정한 경로도 같이 넣어줘야 실행이 되는구나....

예전에 바이브코딩 할 때 로컬로 테스트를 하면 localhost:8000 같이 포트까지만 입력하면 접속이 잘됐어서 뒷통수가 얼얼한 경험이었습니다. 생각해보면 웹사이트 메인 주소에서 /다음으로 이런저런 경로가 뜨잖아요? 그게 다 그냥 갖다 붙인 게 아니라는 걸 느꼈습니다.

그렇게 hello와 foods에 대해 조회한 후 POST로 넘어가기 위해 POST는 어떻게 해야하냐고 물어봤습니다.


브라우저 주소창으로는 POST를 할 수 없어요. GET만 됩니다.

POST는 “데이터를 보내면서” 요청해야 해서, 아래 중 하나로 테스트하면 됩니다.

## 1) 가장 쉬운 방법: Thunder Client / Postman

VS Code/Cursor 확장 Thunder Client 또는 앱 Postman을 쓰면 됩니다.

1. Method를 POST로 선택

2. URL: http://localhost:3000/foods

3. Body → JSON 선택

4. 아래처럼 입력:

json

{

  "name": "초밥",

  "note": "주말에"

}

5. Send 클릭

그다음 브라우저에서 http://localhost:3000/foods를 열면 목록에 추가된 걸 볼 수 있어요.

## 2) 터미널로 하는 방법 (PowerShell)

서버가 켜진 상태에서 새 터미널을 열고:

powershell

Invoke-RestMethod -Method POST -Uri http://localhost:3000/foods -ContentType "application/json" -Body '{"name":"초밥","note":"주말에"}'

터미널로 하는 방법을 선택하고 하라는대로 입력했습니다.

텍스트 상자가 있는 웹페이지의 스크린샷

'엇 왜 ?가 나오지? 조회하면 정상적으로 뜨려나??' <<< ?? 로 조회됨

ai의 답변은 간단했습니다.


PowerShell에서 -Body '{"name":"초밥"...}'처럼 바로 넣으면, 한글이 **깨진 채(또는 ?로)** 서버에 전달됩니다.

서버는 받은 그대로 저장해서 ???가 된 거예요.

## 해결: UTF-8로 보내기

이 명령어로 다시 해보세요.

powershell

$body = @{ name = "초밥"; note = "주말에" } | ConvertTo-Json

Invoke-RestMethod -Method POST -Uri http://localhost:3000/foods -ContentType "application/json; charset=utf-8" -Body ([System.Text.Encoding]::UTF8.GetBytes($body))

???는 그대로 남아 있을 수 있어요.

서버를 한 번 재시작하면 메모리 데이터가 초기화되고, 위 명령으로 다시 추가하면 한글로 정상 저장됩니다.

Thunder Client/Postman을 쓰면 이런 인코딩 문제를 거의 안 겪습니다.


😮깨달은 것 2 : 아 터미널에 한글은 막 입력하지 말고 한번 코딩을 해줘야 하는구나. 난 UTF-8없이는 살 수 없겠는걸.

다음으로 바이브코딩으로 생성된 index.js의 코드를 차근차근 설명해달라고 부탁했습니다. 내용이 길어서 제가 새로 안 것들 위주로 뽑아보았습니다.


const express = require("express");

const app = express();
const PORT = process.env.PORT || 3000;
  • require("express"): Express 라이브러리를 가져옵니다.

  • app: 실제 서버 앱입니다. “이 주소로 오면 이렇게 응답해”를 여기에 등록합니다.

  • 서버는 포트로 접속을 받습니다. (문 번호라고 생각하면 됩니다.)

  • process.env.PORT가 있으면 그걸 쓰고, 없으면 3000을 씁니다.

  • 배포 환경(예: Render)에서는 보통 PORT를 환경변수로 줍니다.

// 먹고 싶은 음식을 메모리에 저장 (서버 재시작하면 초기화됨)
const foods = [
  { id: 1, name: "김치찌개", note: "매콤하게" },
  { id: 2, name: "돈까스", note: "저녁에" },
];
let nextId = 3;
  • foods: 음식 목록 배열. DB가 아니라 서버 메모리에만 있습니다.

  • 서버를 끄거나 재시작하면 처음 상태로 돌아갑니다.

  • nextId: 다음에 붙일 id 번호입니다. (지금은 1, 2가 있으니 다음은 3)

// GET / — 사용 가능한 주소 안내
app.get("/", (req, res) => {
  res.json({
    message: "API 서버가 실행 중입니다.",
    endpoints: [
      { method: "GET", path: "/hello" },
      { method: "GET", path: "/foods" },
      { method: "POST", path: "/foods" },
    ],
  });
});

// 1) GET /hello — 서버 인사
app.get("/hello", (req, res) => {
  res.json({ message: "hello" });
});


// 3) POST /foods — 먹고 싶은 음식 추가
// body 예시: { "name": "초밥", "note": "주말에" }
app.post("/foods", (req, res) => {
  const { name, note } = req.body;

  if (!name || typeof name !== "string") {
    return res.status(400).json({
      error: "name은 필수이고 문자열이어야 합니다.",
    });
  }

  const food = {
    id: nextId++,
    name: name.trim(),
    note: typeof note === "string" ? note.trim() : "",
  };

  foods.push(food);
  res.status(201).json({ food });
});

라우트 : app.get("/주소", 함수) / app.post("/주소", 함수) 형태입니다.

  • GET: 조회 (데이터 가져오기)

  • POST: 생성 (새 데이터 보내기)

  • req: 요청 정보

  • res: 응답을 보내는 객체

POST의 흐름:

  1. req.body에서 name, note를 꺼냅니다.

  2. name이 없거나 문자열이 아니면 400 에러를 보냅니다.

  3. 새 음식 객체를 만듭니다.

    • id: nextId를 쓰고 1 증가 (nextId++)

    • name: 앞뒤 공백 제거

    • note: 문자열이면 trim, 아니면 빈 문자열

  4. foods 배열에 넣고

  5. 201(Created)과 함께 방금 만든 음식을 돌려줍니다.


😮깨달은 것 3 : 아 req=request(요청), res=response(응답)!!!

함수가 뭔가 요청>>>응답의 반복으로 되어있는 것 같네.

여기까지 하나의 서버로 세가지 엔드포인트를 구현하는 실습을 마쳤습니다.

다른 분들은 예쁜 ui 적용해서 마법을 부리시는데 저는 보시다시피 기본을 따라가는 것도 꽤나 일이네요;;

다음에는 공공데이터포털의 공개 데이터를 이용해서 도로명주소를 입력하면 주변의 상가를 조회(GET)하고 북마크, 메모를 남기는(POST) 것을 실습해보려고 합니다.

1
1개의 답글
지피터스 뉴스레터

이번 주 AI, 딱 쓸 것만

매주 월요일 오후,
읽고 바로 써먹을 AI 소식만