Skip to main content
Summary

화면 대신 데이터를 보내는 서버

지난 장에서는 사용자가 문의 폼에 입력한 값을 서버가 받았습니다. 브라우저는 HTML 화면을 보여 주었고, 서버는 제출된 값을 읽어 응답했습니다.

이번 장에서는 화면을 조금 내려놓고 데이터만 주고받는 서버를 만들어 보겠습니다. 웹사이트의 상품 목록, 모바일 앱의 예약 내역, 관리자 페이지의 문의 목록처럼 화면 뒤에서 오가는 정보는 보통 JSON 형태로 움직입니다.

JSON은 물건을 상자에 정리해 라벨을 붙여 둔 것과 비슷합니다. 사람도 읽을 수 있고, 컴퓨터도 규칙에 맞게 읽을 수 있습니다. 정확한 이름은 JavaScript Object Notation입니다. 어렵게 느껴질 수 있지만, 처음에는 "컴퓨터끼리 주고받기 좋은 정리된 표기법" 정도로 이해하면 충분합니다.

예를 들어 상품 하나는 이렇게 표현할 수 있습니다.

{
  "id": 1,
  "name": "노트북 파우치",
  "price": 18000
}

중괄호 {} 안에 이름표와 값이 들어 있습니다. "name"이라는 이름표에는 "노트북 파우치"라는 값이 들어 있고, "price"에는 18000이라는 숫자가 들어 있습니다. 이런 묶음을 객체라고 부릅니다.

상품이 여러 개라면 대괄호 [] 안에 나란히 담습니다.

[
  {
    "id": 1,
    "name": "노트북 파우치",
    "price": 18000
  },
  {
    "id": 2,
    "name": "무선 마우스",
    "price": 25000
  }
]

이처럼 여러 값을 순서대로 담은 형태를 배열이라고 합니다.

JSON API를 만들어 보기AI 생성 이미지

우리가 만들 API

이번 실습에서는 작은 상품 목록 API를 만듭니다. API는 서버에 마련된 데이터 창구입니다. 손님이 식당 직원에게 "메뉴판 주세요"라고 말하듯, 웹사이트나 앱은 API에 "상품 목록 주세요", "새 상품을 추가해 주세요"라고 요청합니다.

이번 장에서 만들 기능은 두 가지입니다.

  • GET /api/products: 상품 목록을 JSON으로 보내기

  • POST /api/products: 새 상품을 받아 목록에 추가하기

여기서 /api는 "이 주소는 화면보다 데이터를 주고받는 용도입니다"라고 알려 주는 이름표처럼 사용합니다. 반드시 /api를 써야 하는 것은 아니지만, 실제 프로젝트에서도 자주 쓰이는 구분입니다.

app.js에 JSON 준비하기

무엇을 만들고 있는지부터 정리하겠습니다. 우리는 Express 서버에 상품 데이터를 잠깐 담아 두고, 요청에 따라 JSON으로 돌려주는 API를 만들고 있습니다.

열 파일은 프로젝트 폴더의 app.js입니다. 이전에 사용하던 express-practice 폴더를 VS Code로 열고, app.js를 엽니다.

먼저 서버가 JSON 요청 본문을 읽을 수 있도록 미들웨어를 등록해야 합니다. 지난 장에서 폼 데이터를 읽기 위해 express.urlencoded()를 사용했습니다. JSON 요청에는 express.json()이 필요합니다.

const app = express(); 아래쪽, 그리고 POST 라우트보다 위쪽에 다음 줄을 추가합니다.

app.use(express.json());

이 한 줄은 서버 입구에 "JSON으로 온 요청서를 읽을 수 있게 풀어 주세요"라고 부탁하는 도우미입니다. 이 줄이 없으면 POST 요청으로 JSON을 보내도 req.body를 제대로 읽을 수 없습니다.

만약 지난 장의 폼 실습 코드가 남아 있다면 이런 모양이 될 수 있습니다.

app.use(express.json());
app.use(express.urlencoded({ extended: true }));

두 줄을 함께 둬도 괜찮습니다. 첫 줄은 JSON 요청을 읽고, 둘째 줄은 HTML 폼 제출을 읽습니다. 중요한 점은 요청 본문을 읽는 미들웨어가 그 값을 사용하는 라우트보다 먼저 나와야 한다는 것입니다.

상품 목록을 준비하기

다음으로 상품 데이터를 담을 임시 목록을 만듭니다. 이 데이터는 서버가 켜져 있는 동안만 기억됩니다. 서버를 끄고 다시 켜면 처음 상태로 돌아갑니다. 실제 저장은 뒤에서 다시 다룹니다.

app.use(...) 아래, 라우트들보다 위에 다음 코드를 추가합니다.

let products = [
  { id: 1, name: '노트북 파우치', price: 18000 },
  { id: 2, name: '무선 마우스', price: 25000 }
];

여기서는 JSON과 거의 비슷한 모양의 JavaScript 배열을 사용합니다. 따옴표 모양이 앞의 JSON 예시와 조금 달라도 괜찮습니다. 서버 안에서는 JavaScript 값으로 다루고, 응답으로 보낼 때 Express가 JSON 형태로 정리해 줍니다.

GET /api/products 만들기

이제 상품 목록을 읽는 API를 만듭니다. 무엇을 만들고 있는지 다시 확인해 보겠습니다. 브라우저나 앱이 /api/products로 요청하면, 서버가 상품 목록을 JSON으로 응답하게 만들 것입니다.

같은 app.js 파일에서 404 처리 코드가 있다면 그보다 위에 다음 라우트를 추가합니다.

app.get('/api/products', (req, res) => {
  res.json(products);
});

첫 줄의 app.get('/api/products', ...)는 "GET 방식으로 /api/products 주소에 요청이 오면 이 함수를 실행한다"는 뜻입니다.

안쪽의 res.json(products)는 products 배열을 JSON 응답으로 보내라는 뜻입니다. 지난 장에서 화면이나 문장을 보낼 때는 res.send()를 썼습니다. 데이터 API에서는 res.json()을 더 자주 사용합니다.

이제 터미널에서 서버를 실행합니다. 이미 서버가 켜져 있다면 Ctrl + C로 끈 뒤 다시 실행합니다.

node app.js

성공하면 터미널에는 이전에 작성해 둔 실행 메시지가 보입니다. 예를 들어 다음과 비슷합니다.

서버가 실행 중입니다.

브라우저 주소창에 다음 주소를 입력합니다.

http://localhost:3000/api/products

성공하면 화면에 상품 목록이 JSON으로 보입니다. 브라우저마다 줄바꿈 모양은 다를 수 있지만, 대략 이런 데이터가 보이면 됩니다.

[
  { "id": 1, "name": "노트북 파우치", "price": 18000 },
  { "id": 2, "name": "무선 마우스", "price": 25000 }
]

이 요청은 주소창으로 확인할 수 있습니다. 왜냐하면 주소창에 입력하는 요청은 기본적으로 GET 요청이기 때문입니다.

POST /api/products 만들기

이번에는 새 상품을 추가하는 API를 만듭니다. 사용자가 관리자 화면에서 상품 이름과 가격을 입력했다고 상상해 보겠습니다. 그 화면은 서버에 JSON을 보내고, 서버는 그 값을 받아 상품 목록에 추가합니다.

열 파일은 그대로 app.js입니다. 방금 만든 GET /api/products 라우트 아래에 POST 라우트를 추가합니다.

먼저 바깥 모양부터 봅니다.

app.post('/api/products', (req, res) => {

});

이 코드는 "POST 방식으로 /api/products에 요청이 오면 새 상품 추가 작업을 하겠다"는 뜻입니다.

이제 요청 본문에서 값을 꺼냅니다.

const { name, price } = req.body;

req.body는 클라이언트가 보낸 JSON 본문입니다. 예를 들어 클라이언트가 { "name": "키보드", "price": 39000 }을 보내면, 서버는 그 안에서 name과 price를 꺼내 사용할 수 있습니다.

값이 빠져 있을 때를 확인하는 코드도 넣겠습니다.

if (!name || price === undefined) {
  return res.status(400).json({ message: 'name과 price가 필요합니다.' });
}

400은 요청에 필요한 값이 부족하거나 형식이 맞지 않을 때 자주 쓰는 상태 코드입니다. 응답을 보낸 뒤에는 return으로 함수를 끝냅니다. 그래야 아래 코드가 이어서 실행되지 않습니다.

여기서 name은 빈 문자열이면 안 되므로 !name으로 확인했습니다. 반면 price는 숫자이기 때문에 price === undefined처럼 "아예 오지 않았는지"를 확인했습니다. 처음에는 조금 낯설 수 있지만, 값이 있는지 검사할 때 문자와 숫자를 똑같이 다루면 예상과 다른 결과가 나올 수 있다는 정도만 기억해 두면 됩니다.

다음으로 새 상품을 만듭니다.

const newProduct = {
  id: products.length + 1,
  name,
  price
};

id는 상품을 구분하는 번호입니다. 여기서는 학습을 위해 현재 상품 개수에 1을 더해 만들었습니다. 실제 서비스에서는 데이터베이스가 더 안전하게 번호를 관리하는 경우가 많습니다.

이제 목록에 새 상품을 추가합니다.

products.push(newProduct);

마지막으로 결과를 응답합니다.

res.status(201).json(newProduct);

201 Created는 "새로운 것이 만들어졌습니다"라는 뜻의 상태 코드입니다. 상품 추가, 게시글 작성, 예약 생성 같은 요청이 성공했을 때 잘 어울립니다.

완성된 POST 라우트는 다음과 같습니다.

app.post('/api/products', (req, res) => {
  const { name, price } = req.body;

  if (!name || price === undefined) {
    return res.status(400).json({ message: 'name과 price가 필요합니다.' });
  }

  const newProduct = {
    id: products.length + 1,
    name,
    price
  };

  products.push(newProduct);

  res.status(201).json(newProduct);
});

코드를 저장한 뒤 터미널에서 서버를 다시 실행합니다.

node app.js

여기서 중요한 점이 있습니다. POST 요청은 브라우저 주소창만으로 확인하기 어렵습니다. 주소창은 보통 GET 요청을 보내기 때문입니다. 새 상품을 보내려면 요청 방식, 주소, 본문 JSON을 직접 지정할 수 있는 도구가 필요합니다.

POST 요청을 도구로 보내기

이번 실습에서는 Postman이나 VS Code의 REST Client 같은 도구를 사용할 수 있습니다. 둘 중 하나만 사용하면 됩니다.

Postman을 사용한다면 새 요청을 만들고 다음처럼 설정합니다.

  • Method: POST

  • URL: http://localhost:3000/api/products

  • Body: raw

  • 형식: JSON

본문에는 다음 JSON을 입력합니다.

{
  "name": "기계식 키보드",
  "price": 79000
}

Send 버튼을 누릅니다.

성공하면 응답 상태가 201 Created로 보이고, 응답 본문에는 새로 추가된 상품이 보입니다.

{
  "id": 3,
  "name": "기계식 키보드",
  "price": 79000
}

그다음 브라우저에서 다시 다음 주소를 열어 봅니다.

http://localhost:3000/api/products

성공하면 상품 목록에 방금 추가한 기계식 키보드가 포함되어 있습니다.

만약 REST Client를 사용한다면 프로젝트 폴더에 test.http 파일을 만들고 다음 내용을 적을 수 있습니다.

POST http://localhost:3000/api/products
Content-Type: application/json

{
  "name": "기계식 키보드",
  "price": 79000
}

요청 위에 표시되는 Send Request를 누르면 같은 결과를 확인할 수 있습니다.

오류가 나도 학습이 멈춘 것은 아닙니다. 먼저 서버가 켜져 있는지, 주소가 /api/products인지, JSON의 쉼표와 따옴표가 맞는지 확인해 보세요. 특히 JSON은 문자열에 큰따옴표 "를 사용해야 합니다. Postman을 사용한다면 Body 형식이 JSON으로 되어 있는지도 확인합니다. 서버가 JSON 본문을 읽으려면 요청 헤더에 Content-Type: application/json이 함께 들어가야 하기 때문입니다.

CRUD로 API의 기본 패턴 보기

상품 API는 더 많은 기능으로 확장할 수 있습니다. 서버 기능은 보통 CRUD라는 네 가지 동작으로 정리합니다.

CRUD는 어려운 약자가 아니라, 데이터를 다루는 기본 행동 네 가지입니다.

동작뜻자주 쓰는 HTTP 메서드예시 주소
Create새로 만들기POSTPOST /api/products
Read읽기GETGET /api/products
Update고치기PUT 또는 PATCHPATCH /api/products/3
Delete삭제하기DELETEDELETE /api/products/3

PUT과 PATCH는 둘 다 수정에 사용됩니다. 보통 PUT은 전체를 갈아끼우는 느낌이고, PATCH는 일부만 고치는 느낌으로 이해하면 됩니다. 처음에는 둘의 차이를 완벽히 외우기보다 "수정 요청에 쓰는 메서드"라고 잡아 두면 충분합니다.

이번 장에서는 GET과 POST만 직접 만들었습니다. 하지만 패턴은 같습니다. 주소와 메서드를 정하고, req에서 필요한 값을 읽고, res로 결과를 돌려줍니다.

주소창으로 되는 요청, 도구가 필요한 요청

마지막으로 확인 방법을 구분해 보겠습니다.

브라우저 주소창으로 확인하기 좋은 요청은 GET입니다. 예를 들어 상품 목록을 보는 요청은 주소창에 입력해도 됩니다.

http://localhost:3000/api/products

하지만 POST, PUT, PATCH, DELETE처럼 데이터를 보내거나 바꾸는 요청은 주소창만으로 확인하기 어렵습니다. 이때 Postman, REST Client 같은 도구가 필요합니다. 이런 도구는 요청 방식, 요청 본문, 헤더를 직접 정할 수 있게 해 줍니다.

API 서버를 만든다는 것은 결국 이런 창구를 차근차근 늘려 가는 일입니다. 어떤 주소에서 어떤 방식의 요청을 받을지 정하고, 들어온 데이터를 읽고, 알맞은 JSON 응답을 보내는 것입니다.

이번 장에서 만든 상품 API는 작지만 서버의 중요한 흐름을 담고 있습니다. 웹페이지 뒤에서 데이터가 오가는 방식도 이와 크게 다르지 않습니다. 이어지는 학습에서는 이렇게 만든 데이터를 어떻게 보관할지, 오류가 났을 때 어떻게 읽고 대응할지 같은 주제로 서버를 조금씩 더 단단하게 만들어 가겠습니다.

책 목록

비개발자를 위한 Node.js Express 서버 입문

전체 목차 보기

이 노트는 보도·비평·교육·연구 목적으로 작성되었습니다. 저작권 문의가 있으시면 에서 알려주세요.

틸노트는 사용자가 AI를 활용해 노트를 쉽게 작성하거나 편집할 수 있는 서비스입니다.

AI 스퀘어 단톡방에 함께해요

AI 활용과 틸노트 사용 팁을 나누는 오픈채팅방입니다. 입장 비밀번호는 tilnote입니다.

참여하기