프론트엔드 개발 시 백엔드 API를 대체하기 위해 자주 사용되는 json-server에서, 관계형 데이터베이스를 구축하면서 겪은 에러와 이를 바탕으로 알게된 사용방법에 대해 설명해보겠다.
📄 개발 시나리오
사용 방법에 대해 설명하기에 앞서 앞으로 나오는 예제에 대해 간단히 설명하겠다.
예시들의 내용은 간단한 블로그 시스템을 구축하는 프로젝트로, 아래와 같은 기능이 있다.
- 사용자는 회원가입하고 로그인할 수 있다.
- 사용자는 글(포스트)을 작성, 수정, 삭제할 수 있다.
- 사용자는 다른 사용자의 글에 댓글을 달 수 있다.
이 예시를 기준으로 관계형 데이터베이스를 구축하고 사용하는 방법을 알아보자!
🕶️ 사용 방법
1. 설치
json-server를 사용하기 위해 일단 패키지를 설치하자.
$ npm install json-server
$ yarn add json-server
$ pnpm install json-server
2. db.json
db.json 파일은 json-server의 핵심으로, 가상 데이터베이스 역할을 한다. 이 파일을 확장자에 맞게 JSON 형식으로 데이터를 정의하면 json-server가 이를 기반으로 REST API를 자동으로 생성해준다.
db.json 문법 및 구조
db.json 파일은 표준 JSON 형식을 따른다:
- 최상위 객체의 각 키는 리소스(테이블) 이름이 된다
- 각 리소스는 객체 배열 형태여야 한다
- 각 객체는 고유한
id
필드를 가져야 한다 (자동 생성 가능)
{
"users": [
{ "id": 1, "name": "홍길동", "avatar": "image-url" },
{ "id": 2, "name": "김철수", "avatar": "image-url" }
],
"posts": [
{ "id": 1, "title": "json-server 시작하기", "userId": 1 },
{ "id": 2, "title": "관계형 데이터베이스 구축하기", "userId": 1 },
{ "id": 3, "title": "React와 함께 사용하기", "userId": 2 }
],
"comments": [
{ "id": 1, "body": "좋은 글이네요!", "postId": 1, "userId": 2 },
{ "id": 2, "body": "매우 유용한 정보입니다.", "postId": 1, "userId": 1 },
{ "id": 3, "body": "감사합니다.", "postId": 2, "userId": 2 }
]
}
테이블(리소스) 명명 규칙
- 복수형 사용: 테이블 이름은 반드시 복수형으로 지정해야 한다. 예를 들어
user
가 아닌users
,post
가 아닌posts
로 명명해야 한다. 이는 REST API 관례를 따르는 것이며, json-server의 관계 기능이 올바르게 작동하기 위해 중요하다. - 외래 키 명명 규칙: 외래 키는 참조하는 테이블의 단수형에 'Id'를 붙인 형태로 지정해야 한다. 예를 들어
users
테이블을 참조하는 외래 키는userId
여야 한다. - 일관성 유지: 대소문자를 포함한 명명 규칙을 일관되게 유지해야 관계형 쿼리가 제대로 작동한다. json-server는 대소문자를 구분한다.
이러한 명명 규칙을 따르지 않으면 _embed
나 _expand
같은 관계 쿼리 파라미터가 작동하지 않을 수 있으니 주의해야 한다.
3. 손쉬운 사용을 위해 package.json 수정
npm json-server db.json --port 4000
을 통해서 json-server를 실행시킬 수 있다.
하지만 매번 위 명령어를 입력하기는 번거러움으로, 스크립트에 json-server 실행 명령어를 추가하자!
"scripts": {
"start": "react-scripts start",
"server": "json-server --watch db.json --port 4000",
"dev": "concurrently \"npm run start\" \"npm run server\""
}
앞으로는 npm dev
를 통해 json-server 와 프론트 프로젝트를 실행할 수 있다.
4. API 호출 방식
Create (POST)
post 메서드를 통해 새로운 포스터를 생성할 수 있다.
// 새 포스트 생성
fetch('http://localhost:4000/posts', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
title: '새로운 포스트',
userId: 1
})
})
.then(response => response.json())
.then(data => console.log('생성된 데이터:', data));
Read (GET)
get 메서드를 통해 포스터 목록 또는 특정 id 포스트, 필터링된 포스터 목록을 불러올 수 있다.
// 모든 포스트 가져오기
fetch('http://localhost:4000/posts')
.then(response => response.json())
.then(data => console.log('모든 포스트:', data));
// 특정 ID의 포스트 가져오기
fetch('http://localhost:4000/posts/1')
.then(response => response.json())
.then(data => console.log('포스트 1:', data));
// 필터링
fetch('http://localhost:4000/posts?userId=1')
.then(response => response.json())
.then(data => console.log('사용자 1의 포스트:', data));
Update (PUT/PATCH)
put 과 patch 메서드를 통해 특정 id의 포스트를 업데이트 할 수 있다.
- PUT은 리소스 전체를 대체한다. 포함되지 않은 필드는 기본값 또는 null로 초기화된다.
- PATCH는 지정된 필드만 업데이트한다. 포함되지 않은 필드는 기존 값을 유지한다.
// PUT: 전체 리소스 업데이트
fetch('http://localhost:4000/posts/1', {
method: 'PUT',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
title: '수정된 제목',
userId: 1
})
})
.then(response => response.json())
.then(data => console.log('업데이트된 데이터:', data));
// PATCH: 부분 업데이트
fetch('http://localhost:4000/posts/1', {
method: 'PATCH',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
title: '부분 수정된 제목'
})
})
.then(response => response.json())
.then(data => console.log('부분 업데이트된 데이터:', data));
Delete (DELETE)
delete 메서드를 통해 특정 id의 포스터를 삭제할 수 있다.
// 포스트 삭제
fetch('http://localhost:4000/posts/1', {
method: 'DELETE'
})
.then(response => {
if (response.ok) {
console.log('삭제 성공');
}
});
5. 관계형 테이블 작성방식
json-server에서 관계형 데이터베이스를 구현하는 방법은 다음과 같다:
{
"users": [
{ "id": 1, "name": "홍길동" }
],
"posts": [
{ "id": 1, "title": "첫 번째 글", "userId": 1 },
{ "id": 2, "title": "두 번째 글", "userId": 1 }
]
}
관계 데이터 조회하기
외래 키를 사용해 관련 데이터를 조회할 수 있다:
// 특정 사용자의 포스트 조회
fetch('http://localhost:4000/users/1/posts')
.then(response => response.json())
.then(data => console.log('사용자 1의 포스트:', data));
// 특정 포스트와 댓글 함께 조회 (_embed)
fetch('http://localhost:4000/posts/1?_embed=comments')
.then(response => response.json())
.then(data => console.log('포스트와 댓글:', data));
// 특정 포스트와 작성자 정보 함께 조회 (_expand)
fetch('http://localhost:4000/posts/1?_expand=user')
.then(response => response.json())
.then(data => console.log('포스트와 작성자:', data));
주요 관계 쿼리 파라미터
_embed
: 하위 리소스 포함 (1:N 관계에서 "1" 쪽에서 사용)_expand
: 상위 리소스 포함 (1:N 관계에서 "N" 쪽에서 사용)
참조 무결성과 연쇄 삭제
json-server는 기본적으로 참조 무결성을 유지하기 위한 연쇄 삭제(cascade delete) 기능을 제공한다.
즉, 부모 레코드가 삭제되면 그것을 참조하는 모든 자식 레코드도 함께 삭제된다.
예를 들어:
- 사용자(users)가 삭제되면 해당 사용자가 작성한 모든 포스트(posts)도 함께 삭제된다.
- 포스트(posts)가 삭제되면 해당 포스트에 달린 모든 댓글(comments)도 함께 삭제된다.
이 동작은 관계형 데이터베이스의 참조 무결성을 유지하는 데 도움이 되지만, 주의해야 할 점도 있다:
- 예상치 못한 데이터 손실: 부모 레코드를 삭제할 때 자식 레코드도 함께 삭제되므로, 중요한 데이터가 의도치 않게 삭제될 수 있다.
- 삭제 요청 예시:
// 사용자 1을 삭제하면 해당 사용자의 모든 포스트와 댓글이 함께 삭제됨
fetch('http://localhost:4000/users/1', {
method: 'DELETE'
})
.then(response => {
if (response.ok) {
console.log('사용자와 관련 데이터가 모두 삭제됨');
}
});
🔖 에러 해결 팁
- 관계형 쿼리가 작동하지 않을 때:
- 외래 키 이름이 올바른지 확인 (예:
userId
는users
테이블을 참조) - 단수형과 복수형 구분 (
_expand=user
는users
테이블을 참조)
- 외래 키 이름이 올바른지 확인 (예:
- 데이터 불일치 오류:
- 존재하지 않는 ID를 참조하는 경우 json-server는 오류를 발생시키지 않음
- 백엔드 구현 전에 클라이언트 측에서 참조 무결성 검사 로직 구현 고려
⚠️ 내가 겪은 에러 사례
프로젝트 배경
내 프로젝트는 MBTI 테스트 애플리케이션이었다. 인증(Authentication) 관련 기능은 스파르타 코딩클럽에서 제공하는 API를 사용했기 때문에 직접 구현할 필요가 없었다.
데이터베이스 구조는 다음과 같이 설계했다:
- users 테이블: id, nickname, avatar 정보 저장
- results 테이블: id, type, userId 정보 저장 (테스트 결과)
🚨 첫 번째 에러: 연쇄 삭제 문제
초기에는 스파르타 코딩클럽에서 제공하는 로그인/회원가입 API를 통하여 user의 정보를 받아올 수 있어, users 테이블을 비워둔 채로 results 테이블만 먼저 구현했다:
{
"user": [],
"result": [
{ "id": 1, "type": "INFP", "userId": 1 },
{ "id": 2, "type": "ENTP", "userId": 1 },
{ "id": 3, "type": "ISFJ", "userId": 2 },
],
}
이 상태에서 특정 ID의 결과(result)를 삭제하려고 했는데, 예상과 달리 모든 결과가 한꺼번에 삭제되는 문제가 발생했다.
원인을 찾아보니 json-server의 GitHub 이슈(issue #13)에서 답을 찾을 수 있었다.
json-server는 참조 무결성을 유지하기 위해, 참조하는 엔티티(여기서는 userId)가 존재하지 않으면 해당 데이터를 자동으로 삭제하는 것이었다.
임시 해결책으로, users 테이블과 API 연동이 완료되기 전까지는 외래 키 이름을 userId 대신 userIdentity와 같은 다른 이름으로 변경하여 참조 관계를 끊었다.
🚨 두 번째 에러: 테이블 명명 규칙
users 테이블 API 연동 후, 테스트 결과와 함께 사용자 정보를 함께 불러오기 위해 _expand 쿼리 파라미터를 사용하려 했으나 예상대로 작동하지 않았다.
처음에는 문제가 없었는데 갑자기 데이터가 제대로 불러와지지 않는 현상이 발생했다.
Stack Overflow의 관련 질문을 통해 원인을 파악할 수 있었다.
json-server에서는 테이블 이름을 반드시 복수형으로 지정해야 관계 기능이 올바르게 작동한다는 것이었다.
테이블 이름을 단수형에서 복수형으로 수정(user → users, result → results)하니 문제가 해결되었다.
참고 자료
'💻 개발 > toy project' 카테고리의 다른 글
Next.js 프로젝트에서 styled-components 에서 tailwindCSS로 바꾼 이유 (0) | 2025.03.03 |
---|---|
Zustand와 Axios: React 컴포넌트 외부에서 상태 관리하는 방법 (0) | 2025.02.25 |
프로젝트 기록 2 - ERD 설정과 프로젝트 세팅 (0) | 2025.02.14 |
프로젝트 기록 1 - 프로젝트 기획 및 와이어 프레임 구현 (0) | 2025.02.13 |
React 프로젝트 이벤트 위임을 통한 리렌더링 최적화 (0) | 2025.02.10 |