Mini Project 4 03
json-server와 db.json의 역할: 프론트엔드 데이터 레이어의 시작
json-server를 단순 가짜 서버라고만 보면 왜 frontend 미니프로젝트에서 충분히 중요한 역할을 했는지 보이지 않는다. 반대로 실제 backend와 같은 책임을 가진다고 봐도 헷갈린다.
근거 · 교안 json-server/db.json 데이터 흐름
1장: json-server, ‘가짜’가 아닌 ‘실습용’ REST Endpoint
솔라의 손가락이 노트북 화면의 한 문장에서 멈췄다. 4차 프론트엔드 미니프로젝트 회고 문서였다.
"4차에서는 Spring backend 대신 json-server가 db.json을 REST처럼 열어 주고, src/bookService.js가 http://localhost:3000/books로 GET/POST/PATCH/DELETE 요청을 보낸다."
솔라는 미간을 살짝 찌푸렸다. 이미 여러 번 본 문장이었지만, 볼 때마다 묘한 위화감이 들었다. 특히 ‘대신’이라는 단어가 마음에 걸렸다.
“언니.”
거실에서 책을 읽던 루나가 고개를 들었다.
“이것 좀 봐봐. 우리 4차 프로젝트 요약인데, 자꾸 ‘Spring 백엔드 대신 json-server’라고 하니까… 꼭 json-server가 진짜 백엔드를 완벽히 대체하는 것처럼 들리잖아. 근데 우리가 써봤을 땐 그냥 임시 서버 같은 느낌이었는데. 내가 잘못 이해하고 있는 건가?”
솔라의 목소리에는 답답함이 묻어났다. json-server를 단순한 가짜 서버로 치부하기엔, 프론트엔드 개발 과정에서 너무나 중요한 역할을 했던 기억이 선명했다. 하지만 그렇다고 실제 백엔드와 같은 무게를 지닌다고 생각하니, 그 또한 어색했다.
루나는 솔라의 노트북 화면을 잠시 들여다보더니, 말없이 고개를 끄덕였다. 그리고는 정의를 내려주는 대신 다른 제안을 했다.
“솔라, 그 프로젝트 폴더 아직 있지? 터미널 하나 열고, db.json 파일도 한번 열어볼래?”
솔라는 고개를 갸웃했지만, 이내 익숙하게 터미널과 코드 에디터를 열었다. 화면 한쪽에는 db.json 파일의 내용이, 다른 한쪽에는 텅 빈 터미널 창이 나타났다. db.json 안에는 books라는 키 아래 여러 개의 책 객체가 배열 형태로 담겨 있었다. 제목, 저자, 표지 이미지 경로 같은 익숙한 데이터였다. 그냥 평범한 텍스트 파일일 뿐이었다.
“자, 이제 그 터미널에 이렇게 입력해봐.”
루나가 나지막이 말했다.
npx json-server --watch db.json
솔라가 명령어를 입력하고 엔터 키를 누르자, 몇 줄의 로그가 터미널에 출력되며 서버가 실행되기 시작했다. 그리고 그 아래, 익숙한 주소가 나타났다.
Resources
http://localhost:3000/books
“이제 웹 브라우저에서 저 주소로 한번 들어가 봐.”
솔라는 새 브라우저 탭을 열고 http://localhost:3000/books를 주소창에 붙여 넣었다. 화면에 나타난 것은, 조금 전 코드 에디터에서 봤던 db.json 파일의 내용과 정확히 일치하는 데이터였다.
“어…?”
순간 솔라의 눈이 커졌다. 너무나 당연하게 해왔던 일이었지만, 루나의 질문을 품고 다시 마주하니 완전히 다른 의미로 다가왔다.
“이거… 그냥 내 컴퓨터에 있던 db.json 파일 내용을 그대로 보여주는 거잖아. 웹 주소로.”
“맞아.”
“그럼 json-server가 한 일은… 데이터베이스를 만들거나, 복잡한 로직을 처리한 게 아니네. 그냥 ‘파일’이었던 db.json을, 외부에서 HTTP 요청으로 접근할 수 있는 ‘창구’로 만들어 준 것뿐이구나.”
솔라는 스스로 말을 뱉으며 생각을 정리했다. ‘대신’이라는 단어에 갇혀 있던 시야가 한순간에 트이는 기분이었다. json-server는 Spring 백엔드의 모든 책임을 ‘대신’하는 대체품이 아니었다. 프론트엔드 개발자가 마치 실제 백엔드와 대화하듯, HTTP 요청을 보내고 응답을 받는 경험을 할 수 있도록 마련된 ‘실습 장치’에 가까웠다.
“아.” 솔라의 입에서 작은 탄성이 터져 나왔다. “그러니까 이건 ‘가짜 서버’라기보단, 프론트엔드가 데이터 통신이라는 대화법을 연습할 수 있게 해주는 ‘REST 엔드포인트 체험 도구’였던 거네. 백엔드가 아직 준비되지 않았어도, 우리는 이 덕분에 실제 API가 있는 것처럼 화면 개발을 끝마칠 수 있었던 거고.”
마침내 json-server의 정확한 위치를 찾은 듯, 솔라의 표정이 한결 편안해졌다. 더 이상 어중간한 존재가 아니었다. 프론트엔드 개발 흐름 속에서 꼭 필요한, 명확한 역할을 가진 도구였다.
루나는 조용히 미소 지으며 솔라의 깨달음을 지켜보고 있었다. 솔라가 다시 노트북 화면으로 시선을 돌렸다. 이제 db.json과 json-server의 관계는 명확해졌다. 하지만 그 순간, 머릿속에 새로운 질문이 꼬리를 물고 이어졌다.
“좋아. db.json 파일이 http://localhost:3000/books라는 주소를 통해 REST 엔드포인트가 된 건 알겠어. 그런데, 우리 프론트엔드 코드, 그러니까 src/bookService.js는 이 주소를 어떻게 알고 찾아가서 데이터를 조회하고, 또 새 책 정보를 보내서 db.json 파일을 수정하기까지 하는 거지? 주소만 안다고 저절로 되는 건 아닐 텐데.”
2장: bookService.js: 프론트엔드의 REST 대화법
솔라의 질문은 허공에 남지 않았다. 그 질문은 곧장 다음 행동으로 이어졌다. 솔라는 이전 챕터에서 열어두었던 프로젝트 폴더에서 src 디렉토리를 열고, 망설임 없이 bookService.js 파일을 클릭했다. 화면에 익숙한 자바스크립트 코드가 펼쳐졌다. 파일 상단에는 const BASE_URL = 'http://localhost:3000';라는 문장이 선언되어 있었다.
한쪽 화면에는 json-server가 실행 중인 터미널, 다른 한쪽에는 db.json 파일의 내용, 그리고 이제 정면에는 bookService.js 파일이 자리 잡았다. 마치 대화에 참여하는 세 명의 인물처럼, 프론트엔드 데이터 흐름의 주역들이 한눈에 들어왔다. 솔라는 이 파일이 http://localhost:3000/books 주소로 요청을 보낸다는 사실은 이미 알고 있었다. 하지만 ‘어떻게’ 그 일을 수행하는지는 막연하게만 느껴졌다. getBooks, createBook, updateBook… 함수 이름들은 직관적이었지만, 그 안에서 벌어지는 일이 마치 마법처럼 느껴졌다.
“주소만 안다고 저절로 되는 건 아닐 텐데…”
혼잣말처럼 중얼거린 질문이 귓가에 맴돌았다. 파일 내용을 스크롤하던 솔라의 시선이 createBook 함수에서 멈췄다.
다음 코드는 실제 파일을 그대로 옮긴 것이 아니라, 요청 흐름을 설명하기 위해 단순화한 예시다.
export const createBook = async (bookData) => {
const response = await fetch(`${BASE_URL}/books`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(bookData),
});
return response.json();
};
“흠… fetch를 쓰네. 주소는 ${BASE_URL}/books고… 어? 그런데 여기 method: 'POST' 라는 게 있네.”
솔라는 옆에 있던 루나를 돌아보며 말했다.
“언니, getBooks 함수는 그냥 주소만 가지고 fetch를 호출했는데, createBook은 뭔가 옵션이 더 붙어있어. 특히 이 method: 'POST'라는 거. 이게 핵심인가?”
루나는 대답 대신 솔라의 노트북 화면과 db.json 파일이 열려있는 에디터 창을 번갈아 가리켰다.
“솔라, 네가 지금 책방 주인이라고 상상해봐. 손님이 와서 그냥 책 한 권을 툭 던져주고 가면, 그게 새로 입고할 책인지, 아니면 기존에 있던 책 정보를 수정해달라는 건지 어떻게 알 수 있을까?”
“어… 뭘 원하는지 말을 해줘야 알 수 있겠지? ‘이 책 새로 들여놔 주세요’ 라거나, ‘이 책 가격이 바뀌었어요’ 라거나.”
“바로 그거야.”
루나의 말에 솔라는 다시 코드 화면을 뚫어지게 쳐다봤다. method: 'POST'. 그제야 이 한 줄이 가진 의미가 뚜렷하게 보이기 시작했다. 이것은 단순한 옵션이 아니었다. bookService.js가 json-server에게 보내는 명확한 ‘요청 사항’이었다.
“아! 그럼 getBooks는 그냥 주소로 찾아가서 ‘거기 있는 책 목록 좀 보여줘’ 하고 말하는 거고, 이 createBook은 ‘내가 지금 주는 이 책 데이터(bookData)를 새로 등록해줘’ 라고 말하는 거구나. 그 요청의 종류를 method로 구분하는 거고!”
POST는 ‘새로 등록해달라’는 말. GET은 ‘보여달라’는 말. 그렇다면 수정은? 솔라의 손가락이 빠르게 움직여 updateBook 함수를 찾아냈다. 예상대로였다.
export const updateBook = async (id, updates) => {
const response = await fetch(`${BASE_URL}/books/${id}`, {
method: 'PATCH',
// ...
});
// ...
};
“여긴 PATCH네! ‘이 id를 가진 책의 정보를 이걸로 고쳐줘’라는 뜻이구나. DELETE도 있겠지, 당연히.”
솔라는 스스로 깨달은 사실에 감탄하며 미소 지었다. bookService.js는 더 이상 마법 상자가 아니었다. 서버와 소통하기 위한 규칙과 어법을 담고 있는, 일종의 ‘대화 스크립트’였다. 프론트엔드가 백엔드에게 무엇을 원하는지 정확하게 전달하는 방법을 정의해 둔 것이다.
“그럼 정말 그런지 한번 볼까?”
루나의 제안에 솔라는 기다렸다는 듯 고개를 끄덕였다. 솔라는 브라우저의 개발자 도구 콘솔을 열고, createBook 함수를 직접 호출해보기로 했다. db.json에 없는 새로운 책 정보를 객체로 만들어 함수의 인자로 넘겨주었다.
createBook({ title: '솔라의 코딩 일기', author: '솔라' })
엔터 키를 누르는 순간, json-server가 실행되던 터미널 창에 POST /books 201 ... 로그가 한 줄 찍혔다. 동시에, 다른 쪽 화면에 열어두었던 db.json 파일의 내용이 스르륵 바뀌었다. 파일의 가장 마지막 줄에, 방금 솔라가 입력한 책 정보가 새로운 객체로 추가된 것이다.
// ... 기존 책 목록
{
"title": "솔라의 코딩 일기",
"author": "솔라",
"id": 7
}
]
“와…”
솔라의 입에서 탄성이 터져 나왔다. 프론트엔드 코드에서 보낸 ‘새로 등록해줘’라는 말이 json-server를 거쳐, 눈앞의 db.json이라는 텍스트 파일을 실제로 바꾸는 장면. 데이터의 흐름이 눈에 보이는 형태로 완벽하게 증명되는 순간이었다. 책 데이터의 ‘모양’이 코드를 통해 바뀌는 것을 직접 목격한 것이다.
“이제 알겠어. json-server는 REST 엔드포인트라는 창구를 열어주고, bookService.js는 그 창구에 대고 GET, POST, PATCH 같은 정확한 ‘HTTP 메서드’를 사용해서 말을 거는 거였어. 이 둘이 이렇게 짝을 이뤄서 프론트엔드의 데이터 흐름을 만들고 있었구나.”
모호했던 관계가 명확한 역할 분담으로 정리되자 속이 다 시원해졌다. 하지만 그 순간, 솔라의 머릿속에 새로운 의문이 피어올랐다.
“그런데 언니. 내가 방금 author 필드를 빼고 title만 넣어서 createBook을 실행했어도 json-server는 그냥 받아줬을까? 아니면 price에 숫자 대신 문자열을 넣었어도? json-server는 너무 착해서 우리가 주는 건 뭐든지 그냥 db.json 파일에 저장해주는 것 같은데… 실제 백엔드도 이렇게 모든 요청을 믿고 그대로 처리해줄까? 뭔가 중요한 책임이 빠져있는 느낌이야.”
3장: 중간 층: json-server의 책임과 실제 백엔드의 질문
이전 장에서 얻은 깨달음의 흥분이 채 가시지 않은 솔라의 손가락이 다시 키보드 위를 맴돌았다. 솔라는 잠시 생각에 잠긴 채 브라우저의 개발자 도구 콘솔 창을 바라봤다. 방금 전, createBook 함수로 새 책 정보를 성공적으로 db.json에 추가했던 그 창이었다. 모든 게 순조롭게 작동하는 것을 확인했지만, 마지막에 떠올랐던 그 찝찝한 질문이 머릿속을 떠나지 않았다. ‘json-server는 너무 착한 거 아닐까?’
솔라는 질문을 입으로 내뱉는 대신, 직접 행동으로 옮겨보기로 했다. 그녀는 위쪽 화살표 키를 눌러 이전에 입력했던 createBook({ title: '솔라의 코딩 일기', author: '솔라' }) 명령어를 다시 불러왔다. 그리고는 망설임 없이 author: '솔라' 부분을 지워버렸다. 저자 정보가 없는, 불완전한 책 데이터였다.
createBook({ title: '수상한 코딩 노트' })
엔터 키를 누르자, 이전과 똑같이 Promise가 반환되고 json-server 터미널에는 POST /books 201 로그가 찍혔다. 그리고 솔라의 눈을 사로잡은 것은 db.json 파일의 변화였다. 파일의 맨 아래, 저자 없이 제목만 덩그러니 있는 새 책 객체가 추가되었다.
// ...
{
"title": "수상한 코딩 노트",
"id": 8
}
]
“역시…”
솔라는 한 번 더 실험했다. 이번에는 db.json의 다른 책들에는 존재하지 않는, 완전히 새로운 필드를 추가해 보았다.
createBook({ title: '완벽한 코딩 가이드', author: '루나', rating: '★★★★★' })
결과는 예상과 같았다. json-server는 이 요청마저 군말 없이 받아들였고, db.json에는 rating이라는 낯선 필드를 가진 책이 새로 등록되었다.
솔라는 의자 등받이에 몸을 기댔다.
“언니, 이거 봐. json-server는 우리가 뭘 주든지 그냥 다 받아줘. 저자가 없어도, 약속되지 않은 rating 필드를 보내도, 아무런 불평 없이 db.json에 그냥 써버리네. 이건… 진짜 백엔드라고 할 수 없지 않아? 뭔가 중요한 책임이 빠져있는 느낌이야.”
루나는 가만히 듣고 있다가, 솔라의 화면 너머 가상의 서점을 가리키듯 말했다.
“책방 주인이 손님한테 책을 받았는데, 제목도 없고 저자도 없는 표지뿐인 책이야. 그래도 서가에 꽂아둘까? 아니면 어떤 손님이 와서 책 가격표에 숫자 대신 ‘아주 비쌈’이라고 손으로 써 붙여놓는다면, 그걸 그대로 계산기에 입력할까?”
“말도 안 돼. 당연히 ‘이 책은 규칙에 맞지 않아서 받을 수 없어요’라고 거절하거나, 가격표를 원래대로 되돌려 놓겠지.”
“맞아. 방금 솔라 네가 말한 그 ‘거절’하고 ‘원래대로 되돌리는’ 역할. 그게 바로 실제 백엔드가 짊어져야 할 중요한 책임이야.”
루나의 말에 솔라는 다시 db.json 파일을 바라봤다. 그저 모든 요청을 순순히 파일에 기록할 뿐인 json-server의 모습과, 규칙에 맞지 않는 것은 거절해야 하는 ‘책방 주인’의 모습이 겹쳐 보였다.
“그럼 실제 백엔드는 json-server가 하지 않았던 어떤 책임들을 지는 건데?”
“우리가 4차 프로젝트에서 json-server에게 한 번도 물어보지 않았던 질문들을 떠올려보면 돼.”
루나는 솔라가 직접 답을 찾도록 유도했다.
“예를 들어, ‘책 제목은 반드시 있어야 하는가?’ 이건 **서버 측 유효성 검사(Server-side validation)**에 대한 질문이지. 또 ‘책 가격이 음수가 될 수 있는가?‘처럼, 우리 서비스의 고유한 규칙, 즉 **도메인 규칙(Domain rule)**에 대한 질문도 있어. ‘누가 책을 삭제할 권한을 가지는가?’ 하는 인증/인가(Auth relation) 문제도 있고. 그리고 가장 근본적으로, ‘서버를 껐다 켜도 이 데이터가 정말 안전하게 보존되는가?’ 하는 영속성(Persistence) 문제도.”
루나가 던지는 질문들이 하나씩 쌓이자, json-server의 역할과 한계가 비로소 선명한 윤곽을 드러냈다. json-server는 이런 복잡하고 중요한 책임들을 모두 비워둔 채, 오직 프론트엔드가 REST라는 약속된 언어로 대화하는 연습을 할 수 있도록 도와주는 ‘통신 실습용 파트너’였던 것이다. 실제 데이터베이스도, 복잡한 비즈니스 규칙도, 보안 책임도 없는, 말 그대로 텅 빈 무대와 같았다.
“아…”
솔라의 입에서 긴 탄식이 흘러나왔다.
“이제야 알겠어. json-server는 4차 프론트엔드 개발에 필요한 REST 데이터 흐름을 ‘흉내 내’ 주었을 뿐, 진짜 백엔드의 핵심적인 책임을 맡은 게 아니었구나. 그래서 ‘중간 층’이라고 불리는 거였어. 프론트엔드 개발을 완성시키면서, 동시에 5차에서 우리가 진짜 백엔드를 만들 때 고민해야 할 이런 질문들을 고스란히 남겨두는 존재.”
모든 조각이 맞춰지는 순간이었다. json-server는 더 이상 ‘가짜 서버’도, ‘미니 백엔드’도 아니었다. 프론트엔드가 다음 단계로 나아가기 위해 반드시 거쳐야 할, 명확한 역할과 한계를 지닌 ‘다리’와 같은 존재였다.
솔라는 잠시 생각에 잠기더니, 새 텍스트 파일을 열었다. 그리고 망설임 없이 제목을 입력했다.
[ 5차 백엔드 개발 - 우리가 답해야 할 질문들 ]
그리고 조금 전 루나와 나누었던 대화를 떠올리며, 하나씩 질문을 구체적으로 적어 내려가기 시작했다.
-
데이터 모델 & 유효성 검사:
- 책을 등록할 때
title과author는 필수 항목인가? price필드에 숫자 외의 값이 들어오면 어떻게 처리할 것인가? (예: 400 Bad Request 에러 반환)id는 어떻게 생성하고 관리할 것인가? (클라이언트가 보내주는가? 서버가 자동으로 생성하는가?)
- 책을 등록할 때
-
도메인 규칙:
- 책 재고가 0일 때 대출을 시도하면 어떻게 되는가?
- 한 번 삭제된 책 정보는 복구할 수 있는가?
-
인증/인가:
- 로그인한 사용자만 책을 등록/수정/삭제할 수 있는가?
- 관리자 등급과 일반 사용자 등급이 나뉘는가?
-
영속성:
- 이 데이터는 어떤 데이터베이스(MySQL, PostgreSQL 등)에 저장될 것인가?
- 서버가 다운되어도 데이터가 안전하게 보관되는가?
노트북 화면에 채워지는 질문들을 바라보는 솔라의 눈빛은 이전과 달랐다. json-server라는 실습용 도구가 남겨준 숙제를 통해, 이제 막연했던 ‘백엔드 개발’이 무엇을 고민하고 책임져야 하는지에 대한 구체적이고 현실적인 시야를 갖게 된 것이다. 4차 프로젝트는 json-server 덕분에 완성되었고, 이제 그 한계 덕분에 5차 프로젝트를 시작할 준비가 되었다.