새소식

프론트엔드 공부/최적화, 배포

Proxy를 설정해 HTTP 통신하기

  • -

 

과제1

webpack dev server의 proxy 기능을 사용해 우회하여 응답을 받아오기

package.json

// api/packge.json
....
},
	"proxy" : "http://localhost:3080"
}

BookService.js

기존의 fetch, 혹은 axios를 통해 요청하던 부분에서 도메인 부분을 제거하여주니 CORS 에러를 해결됨

// my-app/services/BookService.js
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();

과제2

레포지토리로 받아온 파일에 보면 api2 라는 폴더가 존재하고 있다.
실제로 프로젝트 및 실무를 할 때, 하나의 도메인이 아닌 여러 개의 도메인에서 응답을 받아와야 하는 경우가 종종 있는데, 이럴 때는 유연하게 proxy를 설정해주어야 한다.

  • 이번 과제는 webpack dev server의 proxy 기능 대신 http-proxy-middleware의 proxy 기능을 사용하여 proxy를 유연히 설정해 2개의 도메인에서 모두 응답을 받아왔고, api2에 관련된 fetch 함수를 만들고, 컴포넌트를 하나 이상 만들어 2개의 도메인에서 모두 응답을 받아오는지 테스트 하여야 한다.


package.json

먼저 우회할 api 주소 제거(과제 1에서 작성했던 porxy 삭제)

....
},

}

setupProxy.js

http-proxy-middleware 라이브러리 설치(webpack dev server의 proxy 기능 대신 http-proxy-middleware의 proxy 기능을 사용하여 proxy를 유연히 설정해 2개의 도메인에서 모두 응답을 받아오기)

//파일 전역에다가 설치
npm install http-proxy-middleware --save

React App의 src 파일 안에서 setupProxy.js 파일을 생성 후 아래와 같이 작성.

const { createProxyMiddleware } = require('http-proxy-middleware');

module.exports = function(app) {
    app.use(
        "/api",
        createProxyMiddleware({
            target: "http://localhost:3080",
            changeOrigin: true,
        })
    ),
    app.use(
        "/api2",
        createProxyMiddleware({
            target: "http://localhost:3070",
            changeOrigin: true,
        })
    );
};

또는 아래처럼 더 간결히 적을 수 있다.

//my-app/src/setupProxy.js
const { createProxyMiddleware } = require("http-proxy-middleware");

module.exports = function (app) {
  app.use(
    ["/api", "/api2"],
    createProxyMiddleware({
      target: "http://localhost:3080",
      changeOrigin: true,
      router: { "/api2": "http://localhost:3070" },
    })
  );
};

※ 추가 설명 + 예시코드

위처럼 코드에서 API가 지속적으로 추가될 때마다 router 객체에 새로운 경로와 해당 경로가 대응하는 대상 서버를 추가할 수 있습니다. 그러나 이렇게 진행하면 코드가 복잡해지고 유지 보수가 어려워질 수 있으므로, 가능하면 미리 모든 API 경로를 파악하여 router 객체에 추가하는 것이 좋습니다.

// my-app/src/setupProxy.js
const { createProxyMiddleware } = require("http-proxy-middleware");

const apiRoutes = {
  "/api": "http://localhost:3080",
  "/api2": "http://localhost:3070",
  "/api3": "http://localhost:3060",
  // add more API routes here as needed
};

module.exports = function(app) {
  const routes = Object.keys(apiRoutes);
  routes.forEach(route => {
    app.use(
      route,
      createProxyMiddleware({
        target: apiRoutes[route],
        changeOrigin: true,
      })
    );
  });
};

코드에서는 apiRoutes 객체에 모든 API 경로와 대상 서버를 정의합니다. Object.keys() 메서드를 사용하여 apiRoutes 객체에서 모든 API 경로를 가져오고, forEach() 메서드를 사용하여 각 경로에 대해 createProxyMiddleware() 함수를 호출하여 미들웨어를 등록합니다. 이렇게 하면 새로운 API 경로가 추가될 때마다 코드를 수정할 필요 없이, apiRoutes 객체에 경로와 대상 서버만 추가하면 됩니다.


BookService.js

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 createTodos = async (data) => {
    const response = await fetch('/api2/todo', {
        method: 'POST',
        headers: {'Content-Type': 'application/json'},
        body: JSON.stringify({todo: data})
      })
    return await response.json();
}

CreateTodo.js

CreateBook의 컴포넌트를  CreateTodo로 복사하여 이름과 내용만 바꿔서 사용했다.

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="todo" 
                            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>isComplete</label>
                        <input 
                            type="text" 
                            onChange={(e) => onChangeForm(e)} 
                            name="isComplete"
                            placeholder="isComplete" 
                        />
                    </div>
                    <button 
                        className="submit-button"
                        onClick= {() => handleSubmit()}
                    >Submit
                    </button>
                </form>
            </div>
        </div>
    )
}
export default CreateTodo;

DisplayBoard.js

DisplayBoard에서는 getAllTodo의 버튼이 클릭 될 때 실행 되도록 만들어 주었다.

import React from "react";
const DisplayBoard = ({ numberOfBooks, getAllBook, getAllTodo, numberOfTodos }) => {

    return (
        <div className="display-wrapper">
            <div className="display-box">
                <div className="display-board">

                    <div className="number">
                        <h4>책은 모두 <span className="spanColor">{numberOfBooks}</span> 개 입니다</h4>
                    </div>
                    <button className="get-button" onClick={() => getAllBook()}>책 불러오기</button>
                </div>
                <div className="display-board">

                    <div className="number">
                        <h4>해야할 일은 <span className="spanColor">{numberOfTodos}</span> 개 입니다</h4>
                    </div>
                    <button className="get-button" onClick={() => getAllTodo()}>할 일 불러오기</button>
                </div>
            </div>
        </div>
    )
}

export default DisplayBoard;

TodoTable.js

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>todo</th>
                <th>Category</th>
                <th>isComplete</th>
              </tr>
            </thead>
            <tbody>
              {todos.map((todo, index) => {
                return (
                  <tr key={index} className={index % 2 === 0 ? 'odd' : 'even'}>
                    <td>{index + 1}</td>
                    <td>{todo.todo}</td>
                    <td>{todo.category}</td>
                    <td>{todo.isComplete}</td>
                  </tr>
                )
              })}
            </tbody>
          </table>
        </div>
      </div>
    </div>
  )
}

export default TodoTable;

App.js

모든 컴포넌트들을 내려 주기만 하면 끝!

import { useState } from 'react';
import './App.css';
import Header from './components/Header';
import BookTable from './components/BookTable';
import DisplayBoard from './components/DisplayBoard';
import CreateBook from './components/CreateBook';
import { getAllBooks, createBook, createTodos, getAllTodos } from './services/BookService';
import Footer from './components/Footer';
import CreateTodo from './components/CreateTodo';
import TodoTable from './components/TodoTable';

function App() {

  const [bookShelf, setBookShelf] = useState({});
  const [books, setBooks] = useState([]);
  const [numberOfBooks, setNumberBooks] = useState(0);
  const [todayTodo, setTodayTodo] = useState({});
  const [todos, setTodos] = useState([]);
  const [numberOfTodos, setNumberOfTodos] = useState(0);

  const handleSubmit = () => {
    createBook(bookShelf)
      .then(() => {
        setNumberBooks(numberOfBooks + 1);
      });
  }

  const handleTodoSubmit = () => {
    createTodos(todayTodo)
      .then(() => {
        setNumberOfTodos(numberOfTodos + 1);
      });
  }
  const getAllBook = () => {
    getAllBooks()
      .then(data => {
        setBooks(data);
        setNumberBooks(data.length);
      });
  }
  const getAllTodo = () => {
    getAllTodos()
      .then(data => {
        setTodos(data);
        setNumberOfTodos(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 handleOnChangeTodo = (e) => {
    let inputData = todayTodo;
    if (e.target.name === 'todo') {
      todayTodo.todo = e.target.value;
    } else if (e.target.name === 'category') {
      todayTodo.category = e.target.value;
    } else if (e.target.name === 'isComplete') {
      todayTodo.isComplete = e.target.value;
    }
    setTodayTodo(inputData);
  }


  return (
    <div className="main-wrapper">
      <div className="main">
        <Header />
        <div className='formWrap'>
          <CreateBook
            bookShelf={bookShelf}
            onChangeForm={handleOnChangeForm}
            handleSubmit={handleSubmit}
          />
          <CreateTodo
            todayTodo={todayTodo}
            onChangeForm={handleOnChangeTodo}
            handleSubmit={handleTodoSubmit}
          />
        </div>

        <DisplayBoard
          numberOfBooks={numberOfBooks}
          getAllBook={getAllBook}
          numberOfTodos={numberOfTodos}
          getAllTodo={getAllTodo}
        />
        <div className='tableWrap'>
          <BookTable books={books} />
          <TodoTable todos={todos} />
        </div>
        <Footer />
      </div>
    </div>
  );
}

export default App;

CSS( 수정된 부분이 있어서 다를 수 있음)

body, html {
  margin: 0;
  height:100%; 
  overflow: auto;
  background-color: rgb(58, 58, 58);
}
button{
  cursor: pointer;
}
button:hover{
  background-color: pink;
}
.spanColor{
  color:rgb(192, 43, 1);
  font-weight: 800;
}
.main-wrapper {
  margin: 0;
  min-height: 100vh;
  background-color: #fff;
  bottom: 0;
  box-shadow: 1px -15px 1px 1px rgba(42, 42, 42, 0.6);
}

.main {

}

.header {
  width: 100%;
  padding: 1%;
  color: rgb(37, 38, 41);
  text-align: center;
  background-color: rgb(199, 221, 223);
}

.form-wrapper{
  display: grid;
  justify-content: center;
  align-items: center;
  width: 100%;
  height: 300px;
  text-align: center;
  margin-top: 2rem;
  margin-bottom: 2rem;
}
.formWrap{
      display: flex;
  }
.form {
  width: 350px;
  height: 230px;
  padding: 1rem;
  border: 1px solid rgb(205, 205, 205);
  border-radius: 0.5rem;
  box-shadow: 0px 7px 2px 2px rgba(42, 42, 42, 0.2);
}

.form:hover {
  border: 1px solid rgb(203, 218, 255);
  box-shadow: 0px 7px 2px 2px rgba(47, 91, 127, 0.2);
  transition: 1s ease;
}

.form h2 {
  margin: 0.5rem;
}


.input-group {
  display: flex;
  width: 80%;
  justify-content: space-between;
  align-items: center;
  margin-top: 1rem;
  margin-bottom: 1rem;
  margin-left: 2rem;
}

.input-group label {
  color: #3c3c3c;
}

.input-group input {
  height: 30px;
  width: 150px;
  padding: 0.2rem 1rem 0.2rem 1rem;
  border: 1px solid rgb(205, 205, 205);
  border-radius: 0.2rem;
}

.input-group input:focus {
  outline: none !important; 
  border-color: rgb(203, 218, 255); 
  box-shadow: 0 0 5px rgb(203, 218, 255); 
}

button {
  height: 40px;
  width: 100px;
  background-color: rgb(158, 178, 228);
  border: none;
  border-radius: 0.5rem;
  color: #fff;
}

.display-wrapper {
  display: flex;
  justify-content: center;
  align-items: center;
  height: 100px;
  padding-bottom: 2rem;
}

.display-box {
  display: flex;
  align-items: center;
  border: none;
  border-bottom: 1px solid rgba(158, 178, 228, 0.5);
}

.display-board {
  width: 450px;
  height: 30px;
  padding: 1rem;
  display: flex;
  align-items: center;
  justify-content: center;
}

.number {
  color: rgb(92, 115, 174);
  margin-left: 1rem;
  font-size: 1.5rem;

}

.get-button{
  margin-left: 20px;

}

.table-wrapper {
  position: relative;
  text-align: center;
  display: flex;
  justify-content: center;
  width: 100%;
  height: 500px;
}

.table-box {
  width: 400px;
  height: 450px;
}

.table-scroll {
  height: 450px;
  overflow: scroll;
}


table {
  width: 100%;
  border-collapse: collapse;
}

th, td {
  padding: 10px;
  border-bottom: 0.5px solid #444444;
}

.odd:hover {
  background-color: rgb(203, 218, 255);
  transition: 1s ease;
}

footer {
  position: fixed;
  width: 100%;
  height: 3rem;
  display: flex;
  justify-content: center;
  align-items: center;
  color: rgb(92, 115, 174);
  background-color: #fff;
  bottom: 0;
}

.tableWrap{
  display: flex;

}

 

'프론트엔드 공부 > 최적화, 배포' 카테고리의 다른 글

Proxy [CORS 와 Webpack DevServer Proxy]  (0) 2023.04.04
CI/CD  (0) 2023.04.03
개발 프로세스  (0) 2023.04.03
사이트 성능 측정 검사 도구 Google Lighthouse  (0) 2023.03.30
Contents

포스팅 주소를 복사했습니다

이 글이 도움이 되었다면 공감 부탁드립니다.