2026. 3. 11. 16:00ㆍ스파르타코딩클럽 Spring4기 내일배움캠프
어제 하던 과제를 이어서 진행하였습니다.
단계 1-4. API 명세서 만들어보기
이번 단계의 목적
- 연동한 API를 문서로 정리하며 API 명세서가 왜 필요한지 직접 느껴보기
맛집 목록 전체 조회 API 명세서
domain: places
url: dd6e73e4-a664-4b36-99ca-a6e84eb72f58.mock.pstmn.io/places
method: GET
API 설명
데이터베이스에 저장된 모든 맛집 목록을 조회하는 API 입니다.
요청(Request)
a. Parameter & Querystring
none
| 이름 | 데이터타입 | 설명 |
| none | none | none |
b. request headers
'Content-Type': 'application/json'
| 이름 | 데이터타입 | 설명 |
| Content-Type | String | 서버에게 요청하고 싶은 데이터의 형식을 적어주세요. |
| application/json | String | json 형식의 데이터를 요청하기 위한 형식입니다. |
c. request body
none
| 이름 | 데이터타입 | 설명 |
| none | none | none |
응답(respons)
a. response header
'Content-Type': 'application/json'
| 이름 | 데이터타입 | 설명 |
| Content-Type | String | request headers에서 요청한 데이터의 형식으로 반환합니다. |
| application/json | String | request headers에서 요청한 json 형식의 데이터를 반환하는 형식입니다. |
b. response body
성공응답: 200 OK
[
{
"id": 1,
"name": "명동교자 본점",
"address": "서울 중구 명동10길 29",
"call": "02-776-5348",
"category": "한식",
"rating": 5
},{
"id": 3,
"name": "광주 오리탕",
"address": "광주광역시 북구 동문로 123",
"call": "062-123-4567",
"category": "한식",
"rating": 10
}
]
| 이름 | 데이터타입 | 설명 |
| id | Integer | 요청 받는 아이템의 고유 ID입니다. |
| name | String | 요청 받은 아이템의 이름입니다. |
| address | String | 요청 받은 아이템의 주소입니다. |
| call | String | 요청 받은 아이템의 전화번호입니다. |
| category | String | 요청 받은 아이템의 카테고리입니다. |
| rating | Integer | 요청 받은 아이템의 평점입니다. |
API 명세서가 왜 필요한가?
프론트엔드와 백엔드 간의 개발에 있어서 충돌이 안생기게 하고 동시에 개발을 진행할 수 있도록 하고,
너무 많은 API들이 생기면 관리가 어려워질 수 있으니 설명서를 적어두는 것이다.
많은 프론트개발자들에게 API 사용(서버 소통 방법)에 대한 설명을 하기 위해서 필요하다.
단계 2-1. CRUD 를 직접 구현해보기
이번 단계의 목적
- Postman Mock 서버와 달리 실제로 데이터가 존재하는 서버를 경험해보기
- CRUD 가 무엇인지 학습하기
- 등록, 조회, 수정, 삭제 (CRUD) 4가지 기능이 각각 어떤 HTML Method와 대응되는지 직접 구현하며 체득하기
- API 호출 코드를 손으로 작성하면서 프론트엔드가 서버와 어떻게 통신하는지 흐름을 이해합니다.
a. mockapi.io 세팅

mockapi.io에서 프로젝트를 생성하여 URL을 발급 받았습니다.

다음에는 resource를 추가하여 맛집 등록에 사용할 데이터들의 형식을 정하였습니다.

이후 places의 저장된 데이터 개수가 표시되었습니다.
b. 등록 (CREATE)
- 입력 폼에서 값을 받아 POST 요청을 보내는 JS를 작성합니다.
async function addRestaurant() {
const name = document.getElementById('resName').value;
const category = document.getElementById('resCategory').value;
const address = document.getElementById('resAddress').value;
const call = document.getElementById('resCall').value;
const rating = document.getElementById('resRating').value;
let placeData = {
name,
category,
address,
call,
rating: Number(rating)
};
if (!name || !category) {
alert("식당 이름과 카테고리를 입력해주세요!");
return;
}
try {
const response = await fetch(API_URL, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(placeData)
});
} catch (error) {
console.error('데이터 등록 중 오류 발생:', error);
}
// 직접 등록은 맨 위에 보이도록 prepend 사용
renderCard(name, address, call, category, rating);
// 입력창 초기화
document.getElementById('resName').value = '';
document.getElementById('resCategory').value = '';
document.getElementById('resAddress').value = '';
document.getElementById('resCall').value = '';
document.getElementById('resRating').value = '';
}
fetch 함수와 async/await의 사용하여 비동기 방식으로 구현을 하였습니다.
비동기 방식이란 요청을 받은 뒤 먼저 작업이 끝난 순으로 실행되는 방식이라고 합니다.
나중에 여러가지 요청이 생기게 된다면 한번에 요청을 보내고 먼저 작업이 끝나는 것이 먼저 화면에 표시되거나 하는 방식으로 쓰일 것같습니다.
- 등록 성공 후 목록이 자동으로 갱신되는지 확인합니다.

내용을 입력한 후 등록하기 버튼을 눌러서 POST 요청을 보냈습니다.

성공적으로 201 Created 상태 코드를 받았습니다.
- 확인 포인트: mockapi.io 대시보드에 데이터가 실제로 쌓이는지 눈으로 확인합니다.


mockapi.io에서 places를 확인해본 결과 제대로 데이터가 저장된 것을 볼 수 있었습니다.
c. 조회(READ)
- GET 요청으로 전체 목록을 불러와 화면에 렌더링합니다.
async function fetchRestaurants() {
try {
const response = await fetch(API_URL, {
method : 'GET',
headers : {
'Content-Type': 'application/json'
}
});
const data = await response.json();
data.forEach(item => {
renderCard(item.name, item.address, item.call, item.category, item.rating);
});
} catch (error) {
console.error('데이터 렌더링 중 오류 발생:', error);
}
}
GET 요청을 보내서 서버에 존재하는 모든 데이터를 받아와서 renderCard함수의 매개변수로 넣어 주었습니다.
function renderCard(name, address, call, category, rating) {
const list = document.getElementById('restaurantList');
// 새로운 카드 엘리먼트 생성
const card = document.createElement('div');
card.className = 'restaurant-card';
// HTML 구조 삽입 (index.html의 기존 양식 활용)
card.innerHTML = `
<span class="category">${category}</span>
<h3>${name}</h3>
<p> ${address || '주소가 없습니다.'}</p>
<p> ${call || '번호가 없습니다.'}</p>
<p> ${rating || '점수가 없습니다.'}</p>
`;
// 목록의 맨 뒤에 추가 (서버 데이터 순서대로)
list.appendChild(card);
}
서버에서 받아온 데이터를 넣어 HTML에 새로운 구조를 삽입하였습니다.
받아온 데이터가 제대로 나오는지는 밑에 확인 포인트에서 확인해보았습니다.
- 확인 포인트: 1단계에서 등록한 데이터가 목록에 나오는지 확인합니다.

위와 같이 CREATE에서 만들었던 데이터가 잘 불러와졌습니다.
d. 삭제(DELETE)
- 각 카드에 삭제 버튼을 추가합니다.
card.innerHTML = `
<div class="card-buttons">
<button class="delete-btn" onclick="deleteRestaurants()">삭제️</button>
</div>
<span class="category">${category}</span>
<h3>${name}</h3>
<p> ${address || '주소가 없습니다.'}</p>
<p> ${call || '번호가 없습니다.'}</p>
<p> ${rating || '점수가 없습니다.'}</p>
`;
각 카드에 삭제 버튼을 추가하기 위해서 renderCard 함수 구현에서 버튼을 새로 추가하였습니다.
버튼 태그 안에 있는 onclick="deleteRestaurants()"는 삭제 버튼을 눌렀을때 DELETE 요청을 보내는 함수를 실행한다는 뜻입니다.
.restaurant-card{ // 원래 있던 코드에서 부분 추가
position: relative;
padding-top: 30px;
...
}
.delete-btn{
position: absolute;
top: 10px;
right: 10px;
background-color: #ff4d4d;
color: white;
border: none;
border-radius: 4px;
padding: 4px 8px;
cursor: pointer;
font-size: 12px;
transition: background 0.2s;
}
.delete-btn:hover{
background-color: #ff1a1a;
}
CSS를 통해서 삭제 버튼의 위치를 카드의 오른쪽 위로 바꾸어 주었습니다.

- DELETE /places/:id 요청을 보냅니다
async function deleteRestaurant(id){
if(!confirm("정말 삭제하시겠습니까?")) return;
try{
const response = await fetch(`${API_URL}/${id}`, {
method : 'DELETE'
});
if (response.ok){
alert("삭제되었습니다.");
fetchRestaurants(); // 목록 새로고침
}
} catch(error){
console.error('데이터 삭제 중 오류 발생:', error);
}
}
confirm을 이용하여 확인 메시지를 띄워 확인을 받았을 경우에만 실행됩니다.
DELETE는 삭제할 아이템의 ID가 필요하여 URL과 같이 적어 주었습니다.
headers와 body는 필요없으니 없습니다.
이후 삭제가 완료되었으면 전체 조회 GET을 이용하여서 삭제된 데이터를 제외한 카드들을 다시 표시해주었습니다.
- 확인 포인트: 삭제 후 목록을 다시 불러왔을 때 해당 항목이 사라졌는지 확인합니다.

먼저 mockapi.io에서 더미 데이터를 25개 만들어주었습니다.

name 2 가게 카드를 삭제 해보겠습니다.


name 2 가게가 정상적으로 삭제 되었고, 200 OK 상태 코드도 받았습니다.

mockapi.io 에서도 places에 저장된 데이터가 하나 줄어든걸 확인하였습니다.

교자 맛집 다음으로 name 3의 정보가 오는 것을 볼 수 있었습니다.
e. 수정 (UPDATE)
- 수정 버튼 클릭 시 기존 데이터를 폼에 불러옵니다.
<div class="card-buttons">
<button class="update-btn" onclick="prepareEdit(
'${item.id}', '${item.name}', '${item.category}',
'${item.address}', '${item.call}', ${item.rating})">수정</button>
<button class="delete-btn" onclick="deleteRestaurant(${item.id})">삭제️</button>
</div>
수정 버튼을 클릭하면 onclick="prepareEdit" 함수에 클릭한 카드에 있는 맛집 정보들을 전달 하도록 하였습니다.
.update-btn{
position: absolute;
top: 10px;
right: 55px;
background-color: #83f19b;
color: white;
border: none;
border-radius: 4px;
padding: 4px 8px;
cursor: pointer;
font-size: 12px;
transition: background 0.2s;
}
.update-btn:hover{
background-color: #0dd43a;
}
삭제 버튼과 동일하게 수정 버튼을 만들어주었습니다.

아래는 수정 버튼을 누르면 실행되는 prepareEdit 함수 부분입니다.
let editId = null; // 수정 모드 여부를 판단하는 변수
.....
function prepareEdit(id, name, category, address, call, rating){
resetForm();
// 입력창에 기존 값 채우기
document.getElementById('resName').value = name;
document.getElementById('resCategory').value = category;
document.getElementById('resAddress').value = address;
document.getElementById('resCall').value = call;
document.getElementById('resRating').value = rating;
editId = id;
const submitBtn = document.querySelector('.form-section button');
submitBtn.innerText = '수정 완료';
submitBtn.style.backgroundColor = "#ff9800";
// 화면 상단 폼으로 스크롤 이동
window.scrollTo({ top: 0, behavior: 'smooth'});
}
수정 버튼 클릭으로 받은 정보들을 HTML내의 ID를 가져와 해당하는 입력 구간에 넣어 주었습니다.

교자 맛집의 수정버튼을 클릭하였을때 이런식으로 맛집 등록 버튼이 수정 완료 버튼으로 바뀌며 내용이 폼으로 불러와집니다.
- 변경한 내용을 PUT /places/:id 로 전송합니다.
async function addRestaurant() {
if(editId) return updateRestaurant();
....
}
.....
async function updateRestaurant(){
const name = document.getElementById('resName').value;
const category = document.getElementById('resCategory').value;
const address = document.getElementById('resAddress').value;
const call = document.getElementById('resCall').value;
const rating = document.getElementById('resRating').value;
if (!name || !category) {
return alert("식당 이름과 카테고리를 입력해주세요!");
}
const placeData = {
name,
category,
address,
call,
rating: Number(rating)
};
try{
let response;
response = await fetch(`${API_URL}/${editId}`, {
method: 'PUT',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify(placeData)
});
if (response.ok) {
alert("수정되었습니다.");
resetForm();
fetchRestaurants();
}
}catch(error) {
console.error('데이터 수정 중 오류 발생:', error);
alert('수정에 실패했습니다.');
}
}
editId 는 null로 초기화하여 만들어두었고 수정 버튼을 눌렀을때 해당 아이템의 id를 받아서 변경됩니다.
editId가 null이 아니라면 addRestaurant 함수가 아닌 updateRestaurant를 실행하게 하였습니다. 이렇게 한 이유는 html에서 버튼에 onclick="addRestaurant"를 넣어 놓아서 하였습니다. 따로 버튼의 속성을 건드리는 방법도 생각해보았지만 일단 이런 식으로 하였습니다.
name, category, address, call, rating의 데이터들을 서버에 JSON.stringfy를 이용하여 json 형식으로 변경하여 수정 요청을 하였습니다.
이 과정이 addRestaurant 함수에서도 동일하게 존재하는데 따로 함수를 구현하여 깔끔하게 처리하는 것도 좋겠다고 생각하여서 함수로 객체를 반환하게 하였습니다.
function getDatas(){
const name = document.getElementById('resName').value;
const category = document.getElementById('resCategory').value;
const address = document.getElementById('resAddress').value;
const call = document.getElementById('resCall').value;
const rating = document.getElementById('resRating').value;
if (!name || !category) {
return alert("식당 이름과 카테고리를 입력해주세요!");
}
return {
name,
category,
address,
call,
rating: Number(rating)
};
}
....
async function addRestaurant() {
if(editId) return updateRestaurant();
const placeData = getDatas();
...
}
....
async function updateRestaurant() {
const placeData = getDatas();
...
}
이후 수정 완료를 눌렀다면 resetForm 함수를 실행하여 폼에 채웠던 수정 내용을 지우고 fetchRestaurants 함수로 변경된 내용을 웹사이트에 있는 카드에 적용되도록 하였습니다.
function resetForm() {
document.getElementById('resName').value = '';
document.getElementById('resCategory').value = '';
document.getElementById('resAddress').value = '';
document.getElementById('resCall').value = '';
document.getElementById('resRating').value = '';
editId = null;
const submitBtn = document.querySelector('.form-section button');
submitBtn.innerText = "등록하기";
submitBtn.style.backgroundColor = ""; // 원래 색상으로
}
editId를 null로 초기화 해주었습니다.
입력 폼을 빈칸으로 초기화 해주고 버튼을 다시 등록하기 버튼으로 변경해주는 코드입니다.
- 확인 포인트: 수정된 내용이 목록에 즉시 반영되는지 확인합니다.

이와 같이 수정을 하여 완료 해보도록 하겠습니다.

수정완료 알림이 나왔습니다.

카드에 변경한 내용이 잘 적용되었으며, 200 OK 상태 코드도 받았습니다.
백엔드 서버가 왜 필요한가?
클라이언트에게 들어온 요청에 대한 응답을 반환하기 위해서 필요하다.'스파르타코딩클럽 Spring4기 내일배움캠프' 카테고리의 다른 글
| (내일배움캠프)260316 TIL (0) | 2026.03.16 |
|---|---|
| (내일배움캠프)260312 TIL (0) | 2026.03.12 |
| (내일배움캠프)260310 TIL (0) | 2026.03.10 |