프로젝트 기록 및 회고/고객관리 프로그램

CRM 백엔드 스몰톡: 유저정보 유지, 라우팅, 모델 ,권한 등..(react-router-dom + express)

cantor 2023. 7. 6. 11:14

최근에 팀장님이 도커를 이용한

자동 배포 로직을 만들고 보일러 플레이트를 제공하여 주셨습니다.

 

드디어 첫번째 구현시간이라 

서버를 세팅하고 로그인, 회원가입 컨트롤러를 만들었습니다.

강의 공부에서 스택이 크게 변하지않았는데 고민할것이 많은거같습니다.

 

 

1. 라우팅 설계

처음에 팀원분이 만들어주신 라우터를 토대로
express에 get과 post를 처리하는 함수를 만들고 있었습니다.

그런데 팀원분이 이것은 임시로 만든 거라 단지 참고자료로 삼자고 했습니다.

 rootRouter
/ -> Home
/join -> Join
/login -> Login
/logout -> Log Out
/search -> Search User
/accounting -> Accounting

userRouter
/user/upload -> Upload User
/user/edit -> Edit User
/user/remove -> Remove User
/user/attend -> User Attendance
/user/lockers -> User Lockers

programRouter
/program -> See Programs
/program/upload -> Upload programs
/program/edit -> Edit Programs
/program/remove -> Remove Programs 

3. 유저 로그인유지 관리

브라우저가 로그인 상태를 유지하도록 하는 기능이 필요합니다.

 

유튜브 강의 코드에선 클라이언트 쪽 로그인 유저의 정보관리를

pug engine이 제공해주는 res.locals 객체를 통해 했었습니다.

 

로그인 시 req.session에 유저 정보를 저장하고
res.localsreq.session 의 데이터를 옮겨줍니다.

 

이렇게 req.locals의 프로퍼티에 정보를 삽입하는 미들웨어를

라우팅 전에 실행시켜 주면

 

해당 데이터들을 pug에서 그냥 가져다 쓸 수 있습니다.

이해를 돕기 위한 유튜브 강의코드

// 1. 세션에 유저정보 저장

export const postLogin = async (req, res) => {

    //중략.. 로그인성공

    req.session.loggedIn = true;
       req.session.user = user;

     return res.redirect("/")
}


// 2. res.locals에 필요한 변수 저장 
export const localsMiddleware = (req, res, next) => {

    res.locals.loggedIn = Boolean(req.session.loggedIn);
    res.locals.siteName = "NotThatShorts";
    res.locals.loggedInUser = req.session.user || {};
    next();
};

// server.js 에서 라우팅 전에 실행
app.use(localsMiddleware);


// 3. pug 엔진에서 해당 변수 사용
a(href=`/users/${loggedInUser._id}`) #{loggedInUser.username}

그런데 react 에선 저런 res.locals가 없습니다.

일단 똑같이 로그인 시 req.session에 유저정보를 저장하게 했습니다.


그리고 apiRouter에 해당 정보를 요청받을 시 전송하는 코드도 만들었습니다.

// 세션에 유저정보 저장
const postTrainerLogin: customMiddleware = async (req, res) => {

    //중략 로그인 성공
    req.session.loggedIn = true;
    req.session.user = user;
    return; 
}

// apiRouter 코드:  클라이언트 요청시 data 객체 전송
apiRouter.get('/data', (req, res) => {
    const data = {
        //@ts-ignore
      loggedIn: Boolean(req.session.loggedIn),
      //@ts-ignore
      loggedInUser: req.session.user || {},
    };
    res.json(data);
  });


// server.ts 코드
app.use("/api", apiRouter);

문제가 있다면, 페이지에서 로그인정보를 유지하려면
유저 정보를 포함하는 모든 컴포넌트가 전부다 /api/get 에 요청을 보내야 한다는 점입니다.

 

- 모든 컴포넌트가 아래의 코드를 가져야 한다면 엄청난 성능저하가 예상됩니다.

const [loggedIn, setLoggedIn] = useState("false");
const [user, setUser] = useState({});

const handleUserData = () => {
const response = await axios.get("/api/get")
const [loggedIn, user] =  response.data;
setLoggedIn(loggedIn);
setUser(user)
}

- axios 함수는 잘 모릅니다. 일단 임시로 보여주기 위해 작성했습니다.

하지만 redux-store에 정보를 저장하는 방식을 이용하면,
요청을 반복할 필요가 없다는 결론이 나왔습니다.

 

redux강의를 듣고 todo-list: redux 버전을 구현해 보아서 다행입니다.

3. csr + backend

저희는 express로 백엔드 로직을 처리하고
renderingreact-router-dom으로 해주기로 했습니다.

// 백엔드 데이터 로직 처리하는 라우터들
app.use("/", rootRouter);
app.use("/api", apiRouter);

//react가 build한 html 파일이  react-router-dom 으로 랜더링 로직을 처리한다 
 app.get('*', (req, res) => {
     res.sendFile(path.join('./client/build/index.html'));
});

그렇다면 백엔드 로직 라우터들이 response를 끝내버리면안됍니다.

app.get('*') 부분이 실행되기 전에 상단의 라우터들에서 통신이 종료되면

화면을 랜더링이 실행되지 않으니까요.

따라서 중간에 있는 라우터의 컨트롤러(미들웨어)에
res.send, res.render, res.sendFile 등 통신을 종료하는 메서드 사용이 없어야 합니다.

모델 설계

브라우저에 전달되는 user 객체에서 어떤 칼럼을 이용해서 권한을 부여할지,

admin이나 trainer의 권한별로 아예 다른 페이지로 갈지,

동일 컴포넌트 내부에서 다른 화면을 그려주게 할지 이런 것들을 선택해야 합니다.

 

그래서 어떤 칼럼을 집어넣어야 하나 많이 고민하면서

boiler plate내부에 User모델의 칼럼을 조작하고 있었습니다.

 

그런데 팀원분이 지난달에 팀장님이 노션에 작성한 임시 모델에서

필요한 값만 조금 조작하자고 제안해 주셨습니다.
https://www.notion.so/a3133ab07d2f4e14b4c8d9f0835cdf3c

그래서 백엔드가 할 것

trainer가 회원가입 시 기본적으로 authorized 칼럼값이 false가 되게 설정합니다.
회원가입 시 자동으로 admin 계정에게 권한 허가 요청을 보냅니다.

 

만약 허가를 누른다면, trainer 모델 내부에 authorized 값을 true로 바꿔주는 로직을 만드려고 합니다

 

아직 해당 권한으로 무엇을 할지는 정하지 않았습니다.

 

클라이언트분이 원하시는 방식대로 간단히 권한을 표시하게만 해주시면 될 거 같습니다.

 

ex)
권한 허가받은 계정과 대기 중인 계정에게 보여주는

화면의 색상을 달리한다던가, 그냥 권한을 텍스트로 표시해 준다거나

여러가지 다양한 방법이 있습니다.

 

리액트 공부를 하고 있어서 그런가 클라이언트부분도 생각해보니 재밌는거같습니다.

 

 

 

읽어주셔서 감사합니다

오타나 질문, 조언은 댓글로 남겨주세요.