반응형
2. API 계층 분석: adminApi.js
이 파일은 백엔드 서버와 통신하는 모든 API 요청을 중앙에서 관리하는 역할을 합니다. axios 라이브러리를 사용하여 HTTP 요청을 생성하고, 기능별로 모듈화하여 내보냅니다.
2.1. 코드 전문 (주석 포함)
// axios 라이브러리를 가져옵니다. HTTP 통신을 쉽게 할 수 있게 도와주는 도구입니다.
import axios from "axios";
// API 요청의 기본 URL을 상수로 정의합니다. 이렇게 하면 서버 주소가 변경될 때 여기만 수정하면 됩니다.
const BASE_URL = "http://localhost:8080/saykorean/admin";
// JSON 데이터를 전송하기 위한 axios 인스턴스(객체)를 생성합니다.
// 대부분의 텍스트 기반 데이터는 이 인스턴스를 통해 전송됩니다.
const api = axios.create({
baseURL: BASE_URL, // 기본 URL 설정
headers: {
"Content-Type": "application/json", // 보내는 데이터의 형식이 JSON임을 명시
},
});
// 파일(이미지, 음성)을 전송하기 위한 별도의 axios 인스턴스를 생성합니다.
// 파일 데이터는 'multipart/form-data' 형식으로 보내야 합니다.
const apiFormData = axios.create({
baseURL: BASE_URL,
headers: {
"Content-Type": "multipart/form-data", // 보내는 데이터에 파일이 포함되어 있음을 명시
},
});
// [1] 장르(Genre) 관련 API 함수들을 객체로 묶어서 관리합니다.
export const genreApi = {
// 1) 모든 장르 목록을 조회하는 함수 (GET 요청)
getAll: () => api.get("/study/genre"),
// 2) 새로운 장르를 생성하는 함수 (POST 요청). genreDto 객체를 JSON 형태로 서버에 전송합니다.
create: (genreDto) => api.post("/study/genre", genreDto),
// 3) 특정 장르를 삭제하는 함수 (DELETE 요청). URL 파라미터로 genreNo를 전달합니다.
delete: (genreNo) => api.delete(`/study/genre?genreNo=${genreNo}`),
};
// [2] 교육 주제/해설(Study) 관련 API 함수들을 객체로 묶습니다.
export const studyApi = {
// 1) 모든 교육 목록 조회
getAll: () => api.get("/study"),
// 2) 특정 교육 상세 조회
getIndi: (studyNo) => api.get(`/study/indi?studyNo=${studyNo}`),
// 3) 새 교육 생성
create: (studyDto) => api.post("/study", studyDto),
// 4) 기존 교육 수정 (PUT 요청). studyDto 객체를 JSON 형태로 전송합니다.
update: (studyDto) => api.put("/study", studyDto),
// 5) 특정 교육 삭제
delete: (studyNo) => api.delete(`/study?studyNo=${studyNo}`),
};
// [3] 예문(Exam) 관련 API 함수들을 객체로 묶습니다.
export const examApi = {
// 1) 모든 예문 목록 조회
getAll: () => api.get("/study/exam"),
// 2) 특정 예문 상세 조회
getIndi: (examNo) => api.get(`/study/exam/indi?examNo=${examNo}`),
// 3) 새 예문 생성 (파일 포함 가능성이 있으므로 FormData 사용)
create: (examDto) => {
// 파일과 텍스트를 함께 보내기 위해 FormData 객체를 생성합니다.
const formData = new FormData();
// 3-1) examDto 객체의 모든 키(key)를 순회하며 FormData에 추가합니다.
Object.keys(examDto).forEach(key => {
// 'imageFile'이 아니고, 값이 null이나 undefined가 아닌 경우에만 추가합니다.
if (key !== "imageFile" && examDto[key] != null && examDto[key] !== undefined) {
formData.append(key, examDto[key]);
}
});
// 3-2) 이미지 파일이 있으면 'imageFile'이라는 이름으로 FormData에 추가합니다.
if (examDto.imageFile) {
formData.append("imageFile", examDto.imageFile);
}
// 3-3) 최종적으로 만들어진 FormData를 apiFormData 인스턴스를 통해 서버에 POST 요청으로 보냅니다.
return apiFormData.post("/study/exam", formData);
},
// 4) 기존 예문 수정
update: (examDto) => {
const formData = new FormData();
// 4-1) 텍스트 데이터 변경
Object.keys(examDto).forEach(key => {
// 'newImageFile'(새로 업로드할 파일)이 아닌 데이터만 추가
if (key !== "newImageFile" && examDto[key] != null && examDto[key] !== undefined) {
formData.append(key, examDto[key]);
}
});
// 4-2) 새로 변경할 이미지 파일이 있으면 'newImageFile' 이름으로 추가
if (examDto.newImageFile) {
formData.append('newImageFile', examDto.newImageFile);
}
// 4-3) 만들어진 FormData를 PUT 요청으로 보냅니다.
return apiFormData.put('/study/exam', formData);
},
// 5) 특정 예문 삭제
delete: (examNo) => api.delete(`/study/exam?examNo=${examNo}`),
};
// [4] 음성(Audio) 관련 API 함수들을 객체로 묶습니다.
export const audioApi = {
// 1) 모든 음성 목록 조회
getAll: () => api.get('/audio'),
// 2) 특정 음성 상세 조회
getIndi: (audioNo) => api.get(`/audio/indi?audioNo=${audioNo}`),
// 3) 새 음성 생성 (파일이므로 FormData 사용)
create: (audioDto) => {
const formData = new FormData();
// 3-1) 텍스트 데이터(lang, examNo 등) 추가
Object.keys(audioDto).forEach(key => {
if (key !== "audioFile" && audioDto[key] !== null && audioDto[key] !== undefined) {
formData.append(key, audioDto[key]);
}
});
// 3-2) 음성 파일이 있으면 'audioFile' 이름으로 추가
if (audioDto.audioFile) {
formData.append('audioFile', audioDto.audioFile);
}
// 3-3) 만들어진 FormData를 POST 요청으로 보냅니다.
return apiFormData.post('/audio', formData);
},
// 4) 기존 음성 수정
update: (audioDto) => {
const formData = new FormData();
// 4-1) 텍스트 데이터 추가
Object.keys(audioDto).forEach(key => {
if (key !== 'newAudioFile' && audioDto[key] !== null && audioDto[key] !== undefined) {
formData.append(key, audioDto[key]);
}
});
// 4-2) 새로 변경할 음성 파일이 있으면 'newAudioFile' 이름으로 추가
if (audioDto.newAudioFile) {
formData.append('newAudioFile', audioDto.newAudioFile);
}
// 4-3) 만들어진 FormData를 PUT 요청으로 보냅니다.
return apiFormData.put('/audio', formData);
},
// 5) 특정 음성 삭제
delete: (audioNo) => api.delete(`/audio?audioNo=${audioNo}`),
};
// 기본 axios 인스턴스도 내보냅니다. (현재 코드에서는 사용되지 않음)
export default api;
2.2. 핵심 분석
- 두 개의 Axios 인스턴스: 왜
api와apiFormData두 개를 만들었을까요?- 서버는 요청 헤더의
Content-Type을 보고 데이터를 어떻게 해석할지 결정합니다. - 일반적인 텍스트 데이터는
application/json형식으로 보내는 것이 표준적이고 효율적입니다. - 하지만 이미지나 오디오 파일은 텍스트가 아니므로,
multipart/form-data라는 다른 형식으로 감싸서 보내야 합니다. - 따라서, 데이터의 종류에 따라 적절한 인스턴스를 사용하기 위해 두 개를 만들어 둔 것입니다.
- 서버는 요청 헤더의
- API 모듈화:
genreApi,studyApi등으로 API를 기능별로 묶었습니다.- 이렇게 하면 코드가 훨씬 깔끔해지고, 어떤 컴포넌트에서든
import { studyApi } from '../api/adminApi'와 같이 필요한 API만 쉽게 가져다 쓸 수 있습니다. - 각 함수는
getAll,create,delete등 역할이 명확한 이름을 가지고 있어 가독성이 좋습니다.
- 이렇게 하면 코드가 훨씬 깔끔해지고, 어떤 컴포넌트에서든
FormData동적 생성:examApi.create와audioApi.create를 보면FormData객체를 사용하는 것을 볼 수 있습니다.FormData는 HTML 폼(form) 데이터를 쉽게 만들고 전송하게 해주는 Web API입니다.Object.keys(examDto).forEach(...)루프를 통해examDto객체에 있는 모든 텍스트 데이터를formData.append(key, value)형태로 추가합니다.- 그 다음, 파일 데이터(
imageFile또는audioFile)를 별도로 추가합니다. - 이 과정을 통해 텍스트와 파일을 하나의 요청에 담아 서버로 보낼 수 있습니다.
반응형
'프로젝트 > 웹' 카테고리의 다른 글
| [React] 기타 컴포넌트 : 한국어 교육 웹 서비스 '재밌는한국어' 프로젝트 (1) | 2025.10.16 |
|---|---|
| [React] 수정 컴포넌트 : 한국어 교육 웹 서비스 '재밌는한국어' 프로젝트 (0) | 2025.10.16 |
| [React] 등록 컴포넌트 : 한국어 교육 웹 서비스 '재밌는한국어' 프로젝트 (0) | 2025.10.16 |
| [React] 전체 목록 컴포넌트 : 한국어 교육 웹 서비스 '재밌는한국어' 프로젝트 (0) | 2025.10.16 |
| [React] 관리자단 슬라이스 : 한국어 교육 웹 서비스 '재밌는한국어' 프로젝트 (0) | 2025.10.16 |
| [React] 프로젝트 구조 : 한국어 교육 웹 서비스 '재밌는한국어' 프로젝트 (0) | 2025.10.16 |