제목 날짜 내용 발행일 23.04.04
해당 포스트는
Proxy
를 학습한 것을 정리한 내용입니다.
백엔드의 개발 서버 역할을 해줄 api와, 프론트엔드의 개발 서버 역할을 해줄 my-app에 각각 접근하여 npm install
cd api
npm install
cd my-app
npm install
백엔드와 프론트엔드 각각 npm install
을 할 시, 터미널 각각 따로 열어 npm install
을 해줘야 데이터 요청 흐름을 확인하기 편하다.
이어 api에 접근한 터미널에서는 npm run dev
를, my-app
에 접근한 터미널에서는 npm start
를 통해 각기 개발 서버를 연다.
//api terminal
npm run dev
//my-app terminal
npm start
그러면 터미널이 각기 이런 화면이 뜬다.
이 상태에서 Get all Books 라고 쓰여 있는 버튼을 눌러 봅시다.
CORS 에러가 뜨며 응답을 제대로 받아오지 못하는 것을 확인할 수 있다.
이 단계에서부터 proxy 기능을 사용해 CORS 에러를 해결해보자.
페어와 함께 webpack dev server의 proxy 기능을 사용해 우회하여 응답을 받아옵니다.
레포지토리로 받아온 과제를 살펴보면 api2 라는 폴더가 존재하고 있습니다. 실제로 프로젝트 및 실무를 할 때, 하나의 도메인이 아닌 여러 개의 도메인에서 응답을 받아와야 하는 경우가 종종 있습니다. 이럴 때는 유연하게 proxy를 설정할 필요가 있습니다.
페어와 함께 webpack dev server의 proxy 기능 대신 http-proxy-middleware의 proxy 기능을 사용하여 proxy를 유연히 설정해 2개의 도메인에서 모두 응답을 받아옵니다.
페어와 함께 api2에 관련된 fetch 함수를 만들고, 컴포넌트를 하나 이상 만들어 2개의 도메인에서 모두 응답을 받아오는지 테스트 해봅니다.
webpack dev server의 proxy 기능을 사용해 우회하여 응답을 받아오기
우회할 api주소를 적었다.
...
},
"proxy" : "http://localhost:3080"
}
그리고 기존의 fetch, 혹은 axios를 통해 요청하던 부분에서 도메인 부분을 제거하여주니 CORS 에러를 해결해 되었다.
export const getAllBooks = async () => {
const response = await fetch('/api/books');
return await response.json();
}
export const createBook = async (data) => {
const response = await fetch('/api/book', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({book: data})
})
return await response.json();
}
하나의 도메인이 아닌 여러 개의 도메인에서 응답을 받아와야 하는 경우가 종종 있는데, 이럴 때는 유연하게 proxy를 설정해야 한다.
- 이번에는 webpack dev server의 proxy 기능 대신 http-proxy-middleware의 proxy 기능을 사용,
- proxy를 설정해 2개의 도메인에서 모두 응답을 받고,
- api2에 관련된 fetch 함수를 만들고,
- 컴포넌트를 하나 이상 만들어 2개의 도메인에서 모두 응답을 받는지 테스트했다.
api2 폴더에도 동일하게 npm install
후 npm run dev
로 실행 시킨다.
앞서 작성했던 우회할 api주소를 지운다(맨 밑).
....
},
}
http-proxy-middleware 라이브러리 설치
npm install http-proxy-middleware --save
그리고 React App의 src 파일 안에서 setupProxy.js 파일을 생성 후 아래와 같이 작성
const { createProxyMiddleware } = require('http-proxy-middleware');
module.exports = function (app) {
return (app.use(
"/api",
createProxyMiddleware({
target: "http://localhost:3080",
changeOrigin: true,
})
),
app.use(
"/api2",
createProxyMiddleware({
target: "http://localhost:3070",
changeOrigin: true,
})
)
)
};
먼저 api2에 관련된 fetch 함수를 만든다.
export const getAllBooks = async () => {
const response = await fetch('/api/books');
return await response.json();
}
export const createBook = async (data) => {
const response = await fetch('/api/book', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ book: data })
})
return await response.json();
}
export const getAllTodos = async () => {
const response = await fetch('/api2/todos');
return await response.json();
}
export const createTodo = async (data) => {
const response = await fetch('/api2/todo', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ book: data })
})
return await response.json();
}
CreateBook 컴포넌트를 재활용했다.
const CreateTodo = ({ onChangeForm, handleSubmit }) => {
return (
<div className="form-wrapper">
<div className="form">
<form>
<div className="input-group">
<label>todo</label>
<input
type="text"
onChange={(e) => onChangeForm(e)}
name="book"
placeholder="todo"
/>
</div>
<div className="input-group">
<label>category</label>
<input
type="text"
onChange={(e) => onChangeForm(e)}
name="category"
placeholder="category"
/>
</div>
<div className="input-group">
<label>author</label>
<input
type="text"
onChange={(e) => onChangeForm(e)}
name="author"
placeholder="author"
/>
</div>
<button
className="submit-button"
onClick={() => handleSubmit()}
>Submit
</button>
</form>
</div>
</div>
)
}
export default CreateTodo;
DisplayBoard에서는 getAllTodo의 버튼이 클릭 될 때 이벤트를 만들었다.
const DisplayBoard = ({ numberOfBooks, getAllBook, numberOfTodos, getAllTodo }) => {
return (
<div className="display-wrapper">
<div className="display-box">
<div className="display-board">
<h4>생성된 book 수</h4>
<div className="number">
{numberOfBooks}
</div>
</div>
<div className="display-board">
<h4>생성된 todo 수 </h4>
<div className="number">
{numberOfTodos}
</div>
</div>
<div className="get-button">
<button onClick={() => getAllBook()}>Get all Books</button>
<button onClick={() => getAllTodo()}>Get all Todos</button>
</div>
</div>
</div>
)
}
export default DisplayBoard;
BookTable을 재활용했다.
const TodoTable = ({ todos }) => {
if (todos.length === 0) return null;
return (
<div className="table-wrapper">
<div className="table-box">
<h2>My Todos</h2>
<div className="table-scroll">
<table>
<thead>
<tr>
<th>Id</th>
<th>Todos</th>
<th>Category</th>
<th>Author</th>
</tr>
</thead>
<tbody>
{todos.map((todo, index) => {
console.log(todo.todo)
return (
<tr key={index} className={index % 2 === 0 ? 'odd' : 'even'}>
<td>{index + 1}</td>
<td>{todo.todo}</td>
<td>{todo.category}</td>
<td>{todo.author}</td>
</tr>
)
})}
</tbody>
</table>
</div>
</div>
</div>
)
}
export default TodoTable;
모든 컴포넌트들을 내려 준다.
import { useState } from 'react';
import './App.css';
import Header from './components/Header';
import BookTable from './components/BookTable';
import TodoTable from './components/TodoTable';
import DisplayBoard from './components/DisplayBoard';
import CreateBook from './components/CreateBook';
import CreateTodo from './components/CreateTodo';
import { getAllBooks, createBook, getAllTodos, createTodo } from './services/BookService';
import Footer from './components/Footer';
function App() {
const [bookShelf, setBookShelf] = useState({});
const [myTodo, setMyTodo] = useState([]);
const [books, setBooks] = useState([]);
const [todos, setTodos] = useState([])
const [numberOfBooks, setNumberBooks] = useState(0);
const [numberOfTodos, setNumberTodos] = useState(0);
const handleSubmit = () => {
createBook(bookShelf)
.then(() => {
setNumberBooks(numberOfBooks + 1);
});
}
const handleTodoSubmit = () => {
createTodo(myTodo)
.then(() => {
setNumberTodos(numberOfTodos + 1);
});
}
const getAllBook = () => {
getAllBooks()
.then(data => {
setBooks(data);
setNumberBooks(data.length);
});
}
const getAllTodo = () => {
getAllTodos()
.then(data => {
setTodos(data);
setNumberTodos(data.length);
});
}
const handleOnChangeForm = (e) => {
let inputData = bookShelf;
if (e.target.name === 'book') {
bookShelf.book = e.target.value;
} else if (e.target.name === 'category') {
bookShelf.category = e.target.value;
} else if (e.target.name === 'author') {
bookShelf.author = e.target.value;
}
setBookShelf(inputData);
}
const handleOnChangeTodoForm = (e) => {
let inputData = myTodo;
if (e.target.name === 'todo') {
myTodo.todo = e.target.value;
} else if (e.target.name === 'category') {
myTodo.category = e.target.value;
} else if (e.target.name === 'author') {
myTodo.author = e.target.value;
}
setMyTodo(inputData);
}
return (
<div className="main-wrapper">
<div className="main">
<Header />
<CreateBook
bookShelf={bookShelf}
onChangeForm={handleOnChangeForm}
handleSubmit={handleSubmit}
/>
<CreateTodo
myTodo={myTodo}
onChangeForm={handleOnChangeTodoForm}
handleSubmit={handleTodoSubmit}
/>
<DisplayBoard
numberOfBooks={numberOfBooks}
getAllBook={getAllBook}
getAllTodo={getAllTodo}
numberOfTodos={numberOfTodos}
/>
<BookTable books={books} />
<TodoTable todos={todos} />
<Footer />
</div>
</div>
);
}
export default App;