json-server
설치json-server
는 간단한 JSON 파일을 사용하여 RESTful API를 빠르게 구축할 수 있는 도구입니다. 프론트엔드 개발 시 실제 백엔드가 준비되지 않았더라도 API를 모킹(mocking)하여 개발을 진행할 수 있어 매우 유용합니다. 이번 섹션에서는 json-server
를 설치하고 설정하는 과정을 자세히 살펴보겠습니다.
json-server
설치터미널을 열고 다음 명령어를 실행하여 json-server
를 프로젝트에 설치합니다:
npm install json-server
설명:
npm install json-server
: npm
패키지 매니저를 사용하여 json-server
를 로컬 프로젝트에 설치합니다. json-server
는 개발 의존성(devDependency)으로 설치하는 것이 일반적이지만, 필요에 따라 전역(global)으로 설치할 수도 있습니다.Tip:
전역 설치를 원할 경우, 다음과 같이 -g
플래그를 사용할 수 있습니다:
npm install -g json-server
하지만 프로젝트별로 다른 버전을 관리하거나, 특정 프로젝트에만 json-server
를 사용하고자 할 때는 로컬 설치가 더 유용합니다.
package.json
scripts 추가package.json
파일의 scripts
섹션에 json-server
를 실행할 수 있는 스크립트를 추가하여 편리하게 API 서버를 시작할 수 있습니다. 이렇게 하면 터미널에서 간단한 명령어로 json-server
를 실행할 수 있어 개발 프로세스가 더욱 효율적이 됩니다.
package.json
수정package.json
파일을 열고 scripts
섹션에 다음 라인을 추가합니다:
{
...
"scripts": {
"dev": "vite",
"build": "vite build",
"lint": "eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0",
"lint:fix": "eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0 --fix",
"format": "prettier --write --cache .",
"preview": "vite preview",
"db": "json-server data/db.json -p 8000"
},
...
}
설명:
"db": "json-server data/db.json -p 8000"
:db
: 스크립트의 이름입니다. 터미널에서 npm run db
명령어로 실행할 수 있습니다.json-server data/db.json -p 8000
:json-server
: 설치한 json-server
를 실행합니다.data/db.json
: API의 데이터 소스로 사용할 JSON 파일의 경로입니다. 이 파일에 API가 제공할 데이터를 정의합니다.-p 8000
: json-server
가 실행될 포트를 지정합니다. 기본 포트는 3000
이지만, 여기서는 8000
번 포트를 사용하도록 설정했습니다.Tip:
data/db.json
)는 프로젝트 구조에 따라 조정할 수 있습니다. 폴더가 존재하지 않을 경우, 미리 생성해두는 것이 좋습니다.db.json
파일json-server
가 사용할 데이터베이스 파일인 db.json
을 생성하고 초기 데이터를 정의합니다. 이 파일은 JSON 형식으로 작성되며, API의 엔드포인트와 데이터를 설정하는 역할을 합니다.
db.json
파일 생성프로젝트 루트 디렉토리에 data
폴더를 생성한 후, 그 안에 db.json
파일을 생성합니다.
data/db.json
db.json
내용{
"canvases": [
{
"id": 1,
"title": "친환경 도시 농업 플랫폼",
"lastModified": "2023-06-15",
"category": "농업"
},
{
"id": 2,
"title": "AI 기반 건강 관리 앱",
"lastModified": "2023-06-10",
"category": "헬스케어"
},
{
"id": 3,
"title": "온디맨드 물류 서비스",
"lastModified": "2023-06-05",
"category": "물류"
},
{
"id": 4,
"title": "VR 가상 여행 서비스",
"lastModified": "2023-06-01",
"category": "여행"
}
]
}
설명:
canvases
:/canvases
를 통해 접근할 수 있는 데이터 배열입니다.id
, title
, lastModified
, category
필드를 포함합니다.id
: 각 린 캔버스의 고유 식별자입니다.title
: 린 캔버스의 제목을 나타냅니다.lastModified
: 린 캔버스가 마지막으로 수정된 날짜를 나타냅니다.category
: 린 캔버스가 속한 카테고리를 나타냅니다.Tip:
json-server
는 db.json
파일을 읽어 API 엔드포인트를 자동으로 생성합니다. 예를 들어, /canvases
엔드포인트를 통해 위의 데이터에 접근할 수 있습니다.db.json
에 추가적인 키-값 쌍을 정의하면 됩니다. 예를 들어, "users": [...]
, "posts": [...]
등.이제 json-server
를 통해 설정한 API에서 데이터를 가져와 프론트엔드 애플리케이션에 표시해보겠습니다. 이를 위해 Home.jsx
컴포넌트를 수정하여 서버로부터 데이터를 fetching(fetching)하고, 이를 목록으로 렌더링합니다.
src/pages/Home.jsx
수정import { useEffect, useState } from 'react';
import CanvasList from '../components/CanvasList';
import SearchBar from '../components/SearchBar';
import ViewToggle from '../components/ViewToggle';
function Home() {
const [searchText, setSearchText] = useState('');
const [isGridView, setIsGridView] = useState(true);
const [data, setData] = useState([]);
// 데이터 fetching 함수
async function fetchData() {
try {
const response = await fetch('http://localhost:8000/canvases');
if (!response.ok) {
throw new Error('네트워크 응답에 문제가 있습니다.');
}
const data = await response.json();
setData(data);
} catch (error) {
console.error('데이터를 가져오는 중 오류가 발생했습니다:', error);
}
}
// 컴포넌트 마운트 시 데이터 fetching
useEffect(() => {
fetchData();
}, []);
// 특정 아이템 삭제 핸들러
const handleDeleteItem = id => {
setData(data.filter(item => item.id !== id));
};
// 검색 텍스트에 따른 필터링된 데이터
const filteredData = data.filter(item =>
item.title.toLowerCase().includes(searchText.toLowerCase()),
);
return (
<div className="container mx-auto px-4 py-16">
{/* 검색 바와 보기 토글 */}
<div className="mb-6 flex flex-col sm:flex-row items-center justify-between">
<SearchBar searchText={searchText} setSearchText={setSearchText} />
<ViewToggle isGridView={isGridView} setIsGridView={setIsGridView} />
</div>
{/* 캔버스 목록 */}
<CanvasList
filteredData={filteredData}
isGridView={isGridView}
searchText={searchText}
onDeleteItem={handleDeleteItem}
/>
</div>
);
}
export default Home;
설명:
임포트 추가:
useEffect
, useState
: React 훅을 사용하여 컴포넌트의 상태와 라이프사이클을 관리합니다.CanvasList
: 캔버스 목록을 렌더링하는 컴포넌트입니다.SearchBar
: 사용자가 검색할 수 있는 검색 바 컴포넌트입니다.ViewToggle
: 그리드 보기와 리스트 보기를 전환할 수 있는 토글 컴포넌트입니다.상태 관리:
searchText
: 사용자가 입력한 검색어를 저장합니다.isGridView
: 현재 목록이 그리드 보기인지 리스트 보기인지를 저장합니다.data
: 서버로부터 가져온 전체 캔버스 데이터를 저장합니다.데이터 fetching (fetchData
함수):
fetch
: 브라우저의 Fetch API를 사용하여 http://localhost:8000/canvases
엔드포인트에서 데이터를 가져옵니다.data
상태를 업데이트합니다.useEffect
훅:
fetchData
함수를 호출하여 데이터를 가져옵니다.[]
)을 두 번째 인자로 전달하여 이펙트가 컴포넌트의 첫 렌더링 시에만 실행되도록 합니다.아이템 삭제 핸들러 (handleDeleteItem
):
id
를 가진 캔버스를 data
상태에서 제거합니다.CanvasList
또는 Note
컴포넌트에서 삭제 버튼을 클릭할 때 호출됩니다.필터링된 데이터 (filteredData
):
searchText
를 기준으로 data
를 필터링하여 검색어를 포함하는 캔버스만 표시합니다.toLowerCase()
를 사용합니다.렌더링 로직:
CanvasList
): 필터링된 데이터를 CanvasList
컴포넌트에 전달하여 실제 목록을 렌더링합니다. 또한, 보기 방식과 삭제 핸들러도 전달합니다.Tip:
CORS 문제 해결: json-server
는 기본적으로 CORS를 허용하지만, 프론트엔드 서버(Vite)와 백엔드 서버(json-server)가 다른 포트를 사용할 경우 CORS 정책에 의해 요청이 차단될 수 있습니다. 이 경우, json-server
실행 시 CORS 옵션을 설정하거나 프록시 설정을 통해 문제를 해결할 수 있습니다.
예를 들어, json-server
에 CORS를 허용하는 플래그를 추가할 수 있습니다:
json-server data/db.json -p 8000 --watch --cors
데이터 수정: json-server
는 GET, POST, PUT, PATCH, DELETE 등의 HTTP 메서드를 지원하므로, CRUD(Create, Read, Update, Delete) 작업을 쉽게 테스트할 수 있습니다.
결과:
Home
컴포넌트는 json-server
로부터 캔버스 데이터를 성공적으로 가져와 화면에 목록으로 표시합니다.이번 실습에서는 json-server
를 활용하여 간단한 RESTful API를 구축하고, 프론트엔드 애플리케이션에서 이를 연동하여 데이터를 fetching(fetching)하고 목록으로 표시하는 과정을 배웠습니다. 주요 학습 포인트는 다음과 같습니다:
json-server
설치 및 설정:
json-server
를 설치하고, package.json
에 스크립트를 추가하여 쉽게 실행할 수 있도록 했습니다.db.json
파일을 생성하여 API의 데이터 소스를 정의했습니다.API 데이터 fetching:
useEffect
와 useState
훅을 사용하여 컴포넌트가 마운트될 때 데이터를 가져오고 상태를 관리했습니다.동적 목록 렌더링 및 상호작용:
종합적으로, json-server
를 활용한 API 모킹은 프론트엔드 개발 시 빠르고 유연한 데이터 관리를 가능하게 하며, 실제 백엔드 개발이 완료되기 전에 프론트엔드 기능을 테스트하고 개발할 수 있는 유용한 도구입니다. 앞으로 더 복잡한 데이터 구조나 사용자 인증 등의 기능을 추가할 때도 json-server
를 기반으로 다양한 시도를 해볼 수 있을 것입니다.
프론트엔드 애플리케이션에서 백엔드 API와 통신할 때, fetch
API를 사용하는 대신 axios
라이브러리를 활용하면 코드의 가독성과 유지보수성이 크게 향상됩니다. 이번 섹션에서는 axios
를 설치하고, 이를 활용하여 API 요청을 리팩토링하고 모듈화하는 방법을 단계별로 살펴보겠습니다.
Axios는 Promise 기반의 HTTP 클라이언트로, 브라우저와 Node.js 환경에서 모두 사용할 수 있습니다. fetch
API와 비교했을 때, axios
는 다음과 같은 장점을 제공합니다:
axios
는 HTTP 응답이 실패했을 때 자동으로 에러를 발생시켜, 에러 처리가 간편합니다. 반면, fetch
는 응답 상태를 수동으로 확인해야 합니다.axios
를 사용하면 .then
체인 없이도 response.data
로 직접 데이터를 접근할 수 있어 코드가 더 깔끔해집니다.axios
는 요청 및 응답 데이터를 자동으로 JSON으로 변환해주어 추가적인 데이터 변환 과정이 필요 없습니다.이러한 장점들 덕분에 axios
는 네트워크 요청 관리가 더 쉽고, 코드의 가독성과 유지보수성이 향상됩니다.
프로젝트에 axios
를 설치하기 위해 다음 명령어를 실행합니다:
npm install axios
설명:
npm install axios
: axios
라이브러리를 프로젝트의 node_modules
에 설치합니다. 이후, 프로젝트 내에서 axios
를 임포트하여 사용할 수 있습니다.API 요청을 모듈화하면 코드의 재사용성이 높아지고, 네트워크 요청을 중앙에서 관리할 수 있어 유지보수가 용이해집니다. 아래와 같이 axios
를 활용하여 API 요청을 모듈화해보겠습니다.
src/api/http.js
import axios from 'axios';
/**
* Create an Axios instance with default configurations.
* @param {string} baseURL - The base URL for the API.
* @param {object} options - Additional Axios configurations.
* @returns {AxiosInstance} - Configured Axios instance.
*/
function create(baseURL, options = {}) {
const instance = axios.create({
baseURL,
...options,
});
// 요청 인터셉터 추가 (예: 인증 토큰 포함)
instance.interceptors.request.use(
config => {
// 예시: 로컬 스토리지에서 토큰 가져오기
const token = localStorage.getItem('token');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
},
error => Promise.reject(error)
);
// 응답 인터셉터 추가 (예: 응답 데이터 포맷 통일)
instance.interceptors.response.use(
response => response,
error => {
// 예시: 특정 에러 처리
if (error.response && error.response.status === 401) {
// 로그아웃 처리 또는 토큰 갱신 로직
}
return Promise.reject(error);
}
);
return instance;
}
export const canvases = create('http://localhost:8000/canvases/');
설명:
create
함수: 주어진 baseURL
과 추가 옵션을 사용하여 axios
인스턴스를 생성합니다. 이 함수는 재사용 가능한 axios
인스턴스를 생성하는데 사용됩니다.canvases
인스턴스: /canvases
엔드포인트를 기본 URL로 설정한 axios
인스턴스를 생성하여 내보냅니다. 이를 통해 다른 파일에서 canvases
인스턴스를 임포트하여 API 요청을 할 수 있습니다.src/api/canvas.js
import { canvases } from './http';
/**
* Get all canvases.
* @returns {Promise} - Promise 객체를 반환하며, 캔버스 데이터를 포함합니다.
*/
export function getCanvases() {
return canvases.get('/');
}
/**
* Delete a canvas by ID.
* @param {number} id - 삭제할 캔버스의 ID.
* @returns {Promise} - Promise 객체를 반환하며, 삭제 결과를 포함합니다.
*/
export function deleteCanvas(id) {
return canvases.delete(`/${id}`);
}
/**
* Create a new canvas.
* @param {object} canvasData - 생성할 캔버스의 데이터.
* @returns {Promise} - Promise 객체를 반환하며, 생성된 캔버스 데이터를 포함합니다.
*/
export function createCanvas(canvasData) {
return canvases.post('/', canvasData);
}
/**
* Update an existing canvas by ID.
* @param {number} id - 업데이트할 캔버스의 ID.
* @param {object} canvasData - 업데이트할 캔버스의 데이터.
* @returns {Promise} - Promise 객체를 반환하며, 업데이트된 캔버스 데이터를 포함합니다.
*/
export function updateCanvas(id, canvasData) {
return canvases.put(`/${id}`, canvasData);
}
설명:
getCanvases
: 모든 캔버스 데이터를 가져옵니다.deleteCanvas
: 특정 ID를 가진 캔버스를 삭제합니다.createCanvas
: 새로운 캔버스를 생성합니다.updateCanvas
: 특정 ID를 가진 캔버스를 업데이트합니다.Home.jsx
리팩토링기존의 fetch
API를 사용하여 데이터를 가져오던 코드를 axios
로 리팩토링하고, API 요청을 모듈화하여 관리하도록 변경합니다.
import { useEffect, useState } from 'react';
import CanvasList from '../components/CanvasList';
import SearchBar from '../components/SearchBar';
import ViewToggle from '../components/ViewToggle';
import axios from 'axios';
function Home() {
const [searchText, setSearchText] = useState('');
const [isGridView, setIsGridView] = useState(true);
const [data, setData] = useState([]);
async function fetchData() {
const response = await axios.get('http://localhost:8000/canvases');
setData(response.data);
}
useEffect(() => {
fetchData();
}, []);
const handleDeleteItem = id => {
setData(data.filter(item => item.id !== id));
};
const filteredData = data.filter(item =>
item.title.toLowerCase().includes(searchText.toLowerCase()),
);
return (
<div className="container mx-auto px-4 py-16">
<div className="mb-6 flex flex-col sm:flex-row items-center justify-between">
<SearchBar searchText={searchText} setSearchText={setSearchText} />
<ViewToggle isGridView={isGridView} setIsGridView={setIsGridView} />
</div>
<CanvasList
filteredData={filteredData}
isGridView={isGridView}
searchText={searchText}
onDeleteItem={handleDeleteItem}
/>
</div>
);
}
export default Home;
import { useEffect, useState } from 'react';
import CanvasList from '../components/CanvasList';
import SearchBar from '../components/SearchBar';
import ViewToggle from '../components/ViewToggle';
- import axios from 'axios';
+ import { getCanvases, deleteCanvas, createCanvas, updateCanvas } from '../api/canvas';
function Home() {
const [searchText, setSearchText] = useState('');
const [isGridView, setIsGridView] = useState(true);
const [data, setData] = useState([]);
async function fetchData() {
- const response = await axios.get('http://localhost:8000/canvases');
+ try {
+ const response = await getCanvases();
+ setData(response.data);
+ } catch (error) {
+ console.error('데이터를 가져오는 중 오류가 발생했습니다:', error);
+ }
}
useEffect(() => {
fetchData();
}, []);
const handleDeleteItem = async id => {
try {
await deleteCanvas(id);
setData(data.filter(item => item.id !== id));
} catch (error) {
console.error('캔버스를 삭제하는 중 오류가 발생했습니다:', error);
}
};
const filteredData = data.filter(item =>
item.title.toLowerCase().includes(searchText.toLowerCase()),
);
return (
<div className="container mx-auto px-4 py-16">
<div className="mb-6 flex flex-col sm:flex-row items-center justify-between">
<SearchBar searchText={searchText} setSearchText={setSearchText} />
<ViewToggle isGridView={isGridView} setIsGridView={setIsGridView} />
</div>
<CanvasList
filteredData={filteredData}
isGridView={isGridView}
searchText={searchText}
onDeleteItem={handleDeleteItem}
/>
</div>
);
}
export default Home;
설명:
임포트 변경:
axios
임포트를 제거하고, ../api/canvas
에서 정의한 API 함수를 임포트합니다.import axios from 'axios';
import { getCanvases, deleteCanvas, createCanvas, updateCanvas } from '../api/canvas';
fetchData
함수 리팩토링:
axios.get
을 직접 호출하던 부분을 getCanvases
함수를 호출하도록 변경했습니다.const response = await axios.get('http://localhost:8000/canvases');
setData(response.data);
try {
const response = await getCanvases();
setData(response.data);
} catch (error) {
console.error('데이터를 가져오는 중 오류가 발생했습니다:', error);
}
handleDeleteItem
함수 리팩토링:
axios.delete
를 직접 호출하던 부분을 deleteCanvas
함수를 호출하도록 변경했습니다.const handleDeleteItem = id => {
setData(data.filter(item => item.id !== id));
};
const handleDeleteItem = async id => {
try {
await deleteCanvas(id);
setData(data.filter(item => item.id !== id));
} catch (error) {
console.error('캔버스를 삭제하는 중 오류가 발생했습니다:', error);
}
};
코드 간결성 및 유지보수성 향상:
Home.jsx
파일의 책임을 줄이고, 코드의 가독성을 높였습니다.src/api/canvas.js
파일만 수정하면 되므로 유지보수가 용이해집니다.결과:
Home
컴포넌트는 이제 axios
대신 axios
를 기반으로 한 모듈화된 API 함수를 사용하여 데이터를 가져오고 삭제합니다.이번 섹션에서는 axios
를 활용하여 API 요청을 리팩토링하고 모듈화함으로써 코드의 효율성과 유지보수성을 크게 향상시켰습니다. 전체적인 구조와 동작 방식을 요약하면 다음과 같습니다:
src/api/http.js
:
axios
인스턴스를 생성하고, 공통된 설정(예: baseURL
, 인터셉터)을 적용합니다.axios
인스턴스를 생성하여 내보냅니다.src/api/canvas.js
:
http.js
에서 생성한 axios
인스턴스를 임포트하여, 캔버스 관련 API 요청 함수를 정의합니다.src/pages/Home.jsx
:
axios
를 직접 사용하지 않고, canvas.js
에서 정의한 API 함수를 사용하여 데이터를 가져오고 삭제합니다.Home
컴포넌트:searchText
: 사용자의 검색 입력을 저장합니다.isGridView
: 현재 보기 방식을 저장합니다 (그리드 보기 또는 리스트 보기).data
: 서버로부터 가져온 캔버스 데이터를 저장합니다.useEffect
훅을 사용하여 컴포넌트가 마운트될 때 데이터를 가져옵니다.axios
기반의 API 함수를 사용하여 데이터를 비동기적으로 가져옵니다.searchText
에 따라 데이터를 필터링하여 사용자에게 원하는 결과를 제공합니다.try-catch
블록을 통해 처리하여, 오류 발생 시 콘솔에 명확한 메시지를 출력합니다.모듈화된 API 요청:
canvas.js
)로 분리되어, 다른 컴포넌트에서도 쉽게 재사용할 수 있습니다.인터셉터 활용:
환경 변수 활용:
baseURL
을 하드코딩하지 않고, 환경 변수(.env
파일)를 사용하여 관리하면, 개발 환경과 배포 환경 간의 설정을 유연하게 변경할 수 있습니다.// src/api/http.js
const baseURL = import.meta.env.VITE_API_BASE_URL || 'http://localhost:8000/canvases/';
const instance = axios.create({
baseURL,
// 기타 설정...
});
전역 에러 핸들링:
타입스크립트 도입:
이번 실습에서는 axios
를 활용하여 API 요청을 리팩토링하고 모듈화함으로써, 프론트엔드 애플리케이션의 코드 품질과 유지보수성을 크게 향상시켰습니다. 주요 작업은 다음과 같습니다:
axios
설치 및 기본 설정:axios
를 프로젝트에 설치하고, 공통된 설정을 가진 axios
인스턴스를 생성하여 재사용성을 높였습니다.http.js
와 canvas.js
파일을 생성하여, API 요청을 중앙에서 관리하고, 코드의 재사용성을 높였습니다.Home.jsx
파일을 리팩토링하여, 직접 axios
를 사용하지 않고 모듈화된 API 함수를 사용하도록 변경했습니다. 이를 통해 코드의 가독성과 유지보수성을 향상시켰습니다.try-catch
블록을 사용하여 네트워크 요청 중 발생할 수 있는 오류를 효과적으로 처리했습니다. 이는 애플리케이션의 안정성을 높이는 데 기여했습니다.종합적으로, axios
를 활용한 API 요청 리팩토링과 모듈화는 프론트엔드 개발 시 코드의 효율성과 유지보수성을 크게 향상시키는 중요한 단계입니다. 이러한 접근 방식을 통해, 보다 견고하고 확장 가능한 애플리케이션을 구축할 수 있을 것입니다. 앞으로도 axios
의 다양한 기능을 활용하여, 네트워크 요청을 더욱 효과적으로 관리하고, 사용자에게 안정적인 서비스를 제공할 수 있을 것입니다.