React + Nodejs 프로젝트 세팅

cometLEE·2023년 5월 16일
1

nodeJS

목록 보기
7/9

프로젝트 세팅

새로 시작하는 프로젝트 환경설정을 위해서 이것저것 많이 알아보게 되었다.
React와 Nodejs의 서버가 각자라는 소식은 나의 생각을 많게 만들었다...

작업폴더 제작

  1. 디렉터리 구조
  • <프로젝트명>
    • client
      • react files...
    • server
      • router
        • api ...
      • server.js

frontend 설정

npx create-react-app client

최상단의 프로젝트 디렉토리에서 cra명령어를 입력합니다.

주의점! CRA를 할 때 폴더명은 소문자로 해줘야 에러가 나지 않습니다!


backend 설정

1. nodemon

개발 시, 변경사항을 업데이트 해주는 모듈

2. concurrently

서버를 동시에 실행시키기 위한 모듈

script 설정(package.json)

{
  "name": "food-management",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
		// 여기를 주목!
    "start": "nodemon server/index.js",
    "server": "nodemon server/index.js",
    "client": "cd client && npm start",
    "dev": "concurrently \"npm run server\" \"npm run start --prefix client\"",
		// 테스트 서버는 일단 넘어가요
    "test": "echo \\"Error: no test specified\\" && exit 1",
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "concurrently": "^7.2.2",
    "express": "^4.18.1",
    "nodemon": "^2.0.16"
  }
}

최상위 디렉토리에서 명령어

npm start

nodemon으로 서버동작

npm run server

마찬가지로 서버코드가 동작하는 명령어. server.js가 동작

npm run client

클라이언트 코드만 동작

npm run dev

nodejs 서버와 클라이언트 서버를 동시에 동작
--> 자원을 요청하기 위해..

출처: 해씨코드

다른 것은 몰라도 스크립트 설정은 매우 간편한 것 같습니다!!!

연동의 문제점

클라이언트에서 CORS 해결하기

이제 클라이언트에서 서버측으로 데이터를 요청해보도록 해보겠다.
내가 원하는 것은 서버/index로 접근하면 데이터를 axios로 가져오도록 하고 싶다.

// server.js
const express = require('express');
const path = require('path');
const app = express();
const index = require('./routes/index')
app.use(express.json());
const cors = require('cors');
app.use(cors()); 

app.use('/index',index)

const port = 3001;
app.listen(port, function () {
  console.log(`listening on ${port}`);
}); 
// server.js
const express = require('express');
const path = require('path');
const app = express();
const index = require('./routes/index')
app.use(express.json());
const cors = require('cors');
app.use(cors()); 

app.use('/index',index)

const port = 3001;
app.listen(port, function () {
  console.log(`listening on ${port}`);
}); 
// index.js
const express = require('express');
const router = express.Router();

router.get('/',(req,res)=>{ 
    console.log('http://localhost:3001/index/');
    res.send({
        api: "hello, react!",
        value: "mark",
    });
})

module.exports = router;

데이터를 가져오기 전에 우리는 CORS라는 것에 대해 생각해보아야 한다.

SOP?

Same Origin Policy

자바스크립트 엔진 표준 스펙의 보안 규칙 중, 하나의 출처(Origin)에서 로드된 자원이 일치하지 않는 자원과 상호작용을 못하도록 요청발생을 제한 하는 정책

동일 출처

protocol, host, port가 같아야 동일한 출처입니다.

  • localhost:3000 (react server)
  • localhost:3001 (nodejs server)

이 둘은 port가 다르기 때문에 다른 출처로 인식이 됩니다.
하지만, localhost:3000/user는 localhost:3000과 같은 출처입니다.

왜 sop를 준수해야 하는가

보안적인 측면으로, CSRF같은 사이버 공격에 있어서 공격당하는 주소와 공격하는 주소가 다르기 때문에 CORS 에러를 발생시킵니다.

CORS?

Cross Origin Resource Sharing, 교차 출처 리소스 공유

CORS는 다른 출처의 자원의 공유를 가능하게 만듭니다. 또한, 추가 HTTP 헤더를 사용하여, 한 출처에서 실행 중인 웹 에플리케이션이 다른 출처의 선택한 자원에 접근할 수 있는 권한을 부여하도록 브라우저에 알려주는 체제입니다. CORS 에러는 브라우저가 뿜어내는 것입니다. Server↔Server는 CORS 에러가 나지 않습니다.

CORS 해결하기

CORS에러는 클라이언트에서 서버로 접근하여 리소스를 가져올 때, 출처가 같지 않으면 에러를 발생시킵니다.
server에서 해결할 수 있고, client에서 해결할 수 있는데,
server측에서 우리는 보편적으로 사용하는 COR 모듈을 사용하는 법과
client측에서 proxy를 설정하는 법이 있습니다.

1. COR모듈 사용해보기

const cors = require('cors');
app.use(cors());

이렇게 사용하면, 모든 도메인에 제한없이 서버에 요청을 보내고 응답을 받을 수 있게 됩니다.

let corsOptions = {
    origin: 'https://www.domain.com',
    credentials: true
}
app.use(cors(corsOptions));

옵션으로 제한을 두고 싶으면, 이렇게 변수로 저장하여 cors모듈을 사용하시면 됩니다.

2. http-proxy-middleware 모듈 사용해보기

react프로젝트 파일의 src에 setupProxy.js라는 파일을 만듭니다.

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

module.exports = function(app){
    app.use(
        createProxyMiddleware('/index', { // 도메인 /index로 호출
            target: 'http://localhost:3001',
            changeOrigin: true
        }),
        
    );
};
  • createProxyMiddleware('/index',{...})의 '/index'는 '/index'로 시작하는 endpoint를 가진 api와 모두 매칭됩니다.
  • target은 endpoint를 제외한 origin만 명세합니다. ex) www.example.com/user에서 www.example.com만 명세
  • changeOrigin은 호스트 헤더의 출처를 대상 URL로 변경하는지 여부입니다. CORS처리를 위해 true로 설정합니다.

설정을 끝냈으면, APP.js에서 url의 endpoint인 /index로 api를 호출합니다.

function APP() {
const sendRequest = async() => {
    const res = await axios.get('/index');
    console.log(res.data);
  };

  useEffect(()=>{
    sendRequest();    
  });
  
  return (
    <div className="App">
      ...(생략)
      </header>
    </div>
  );
}

export default App;

데이터를 잘 가져오는 것을 알 수 있습니다!
참고자료: https://www.datoybi.com/http-proxy-middleware/

3. Deprecation Warning

(node:1681) [DEP_WEBPACK_DEV_SERVER_ON_AFTER_SETUP_MIDDLEWARE] DeprecationWarning: 'onAfterSetupMiddleware' option is deprecated. Please use the 'setupMiddlewares' option.
(Use node --trace-deprecation ... to show where the warning was created)
(node:1681) [DEP_WEBPACK_DEV_SERVER_ON_BEFORE_SETUP_MIDDLEWARE] DeprecationWarning: 'onBeforeSetupMiddleware' option is deprecated. Please use the 'setupMiddlewares' option.

이런 에러가 나와서 당황했었습니다.. 갑자기 app이 깨져서 react서버가 동작하지 않았거든요. 이럴 때에는 react폴더 node_modules의 '/config/webpackDevServer.config.js'에 들어가서, 맨 마지막에

onBeforeSetupMiddleware(devServer) {
// Keep evalSourceMapMiddleware
// middlewares before redirectServedPath otherwise will not have any effect
// This lets us fetch source contents from webpack for the error overlay
devServer.app.use(evalSourceMapMiddleware(devServer));
if (fs.existsSync(paths.proxySetup)) {
// This registers user provided middleware for proxy reasons
require(paths.proxySetup)(devServer.app);
}
},
onAfterSetupMiddleware(devServer) {
// Redirect to PUBLIC_URL or homepage from package.json if url not match
devServer.app.use(redirectServedPath(paths.publicUrlOrPath));
// This service worker file is effectively a 'no-op' that will reset any
// previous service worker registered for the same host:port combination.
// We do this in development to avoid hitting the production cache if
// it used the same host and port.
// https://github.com/facebook/create-react-app/issues/2272#issuecomment-302832432
devServer.app.use(noopServiceWorkerMiddleware(paths.publicUrlOrPath));
},

이부분을

setupMiddlewares: (middlewares, devServer) => {
if (!devServer) {
throw new Error('webpack-dev-server is not defined')
}
if (fs.existsSync(paths.proxySetup)) {
require(paths.proxySetup)(devServer.app)
}
middlewares.push(
evalSourceMapMiddleware(devServer),
redirectServedPath(paths.publicUrlOrPath),
noopServiceWorkerMiddleware(paths.publicUrlOrPath)
)
return middlewares;
},

이렇게 수정하시면 문제가 해결됩니다!!!
출처: https://github.com/facebook/create-react-app/issues/12035

번외) console.log가 두번씩 호출

데이터는 잘 가져와 지는데 왜 콘솔이 두번씩 출력이 될까요??
그 말은 서버로 데이터를 2번씩 가져온다는 것인데, 무언가 잘못될까봐 찾아보게 되었습니다.

app.js 의 useEffect가 두 번씩 실행되는 것을 알게 되었습니다.

왜 두번씩 실행되는가?

프로젝트의 src/index.js에서 <React.Strict> 태그로 이 감싸져 있으면 개발모드에서 두 번씩 렌더링됩니다. (오류를 잘 잡기 위해서)

따라서

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  //<React.StrictMode>
    <App />
  //</React.StrictMode>
);

코드를 지워주면

profile
server, kubernetes에 관심이 많이 있습니다

0개의 댓글