adminbro 를 활용한 관리자 패널 만들기

·2022년 6월 7일
3

팀 프로젝트

목록 보기
27/34
post-thumbnail

코드가 있는 곳으로 이동합니다! 클릭시 이동

관리자 페이지, 있으면 좋을 것 같은데

원래 생각한 것은 특정한 조건에서 자주 검색되는 것들
하루에 유저가 들어온 유입량, 작성되는 글 등을
차트로 보여줄 수 있는 관리자 페이지가 있으면 좋겠다 라는 생각이 들었다.

하지만 그 무렵 작업량이 미쳐날뛰고 있을 시기라 고급스러운 관리자 페이지는 무리가 있었고
프론트쪽에서 권한 분기라거나 이런저런 작업을 하면서 데이터의 CRUD를 계속 요청했다.

이유는 처음 써보는 api다보니 사용에 어려움이 있었고
플레이그라운드상에서 계속 사용하는 것에는 불편함도 너무 많다는 이유였는데
그래서 CRUD만 되는 관리자 페이지가 있으면 좋지 않을까? 라는 생각이 들었다.

어떻게 보면 그냥 디비를 연결시켜주는 것과 같다는 생각도 들었지만
만약 운영자(비개발직군)이 함께 한다면 디비라는 것을 어렵게 생각할 수 있으니
혼자서 할 수 있는 방향이 분명 있을 것이라 생각하고 한번 찾아봤다.


이것저것을 찾아봤는데 타 언어에 비해서 노드는 관리자패널이 제대로된게 없었다(...)

그러던 와중 찾은 것이 adminbro 였고 연결을 하면서 이런저런 문제가 있었는데 차례대로 풀어가면서 이야기를 해보려고 한다.

로그인 페이지

세부 페이지

1.1.0 버전은 진짜 아닌 것 같은데...

typeORM은 0.3버전인데...? 그래서 문제가 많잖아요

찾아보니 adminbro@nestjs라는 라이브러리가 있는 것을 확인했다.

그런데 단지 다운로드 수를 포함하여 버전이 낮다는 이유로 놔주기로 했다.

그래서 선택을 한 것이 @admin-bro/express 였다.

결국 Nest.JS도 Node.JS 위에서 돌아가니까 당연히 연결이 된다고 생각했으니 두개를 이어붙이는 작업을 시작했다.

AdminBro의 구성요소 3가지

어드민브로를 사용하기 위해서는 3개의 라이브러리를 설치해야한다.

  1. 어드민브로와 DB를 연결해주기 위한 @admin-bro/typeorm
    1.1 다른 글들을 보면 @admin-bro/mongoose를 이용해서 몽고디비에 연결을 많이하던데,
    나는 Mysql에 연결을 하려고 typeorm으로 작업을 했다.
  2. 어드민브로에 해당하는 Entity를 그려주기 위한 admin-bro
  3. 서버와 어드민브로 패널을 자동 렌더링해주기 위한 @admin-bro/express

여기서 보면 혼자 @admin-bro/express만 require문을 사용해놨는데
내가 이것저것 만져봐도 도무지 import쪽으로는 선언을 하면
계속 에러가 발생해서 임시로 변경해놓은 상태다.

그럼 이제부터 설정하는 작업에 들어가보도록 하자!

1. Adminbro와 DB를 연결하기

첫번째로 해줘야하는 것은 데이터베이스과 데이터들을 연동시키는 작업이 필요하다.

이것은 한줄이면 손쉽게 처리할 수 있다.

  AdminBro.registerAdapter({ Database, Resource });

말 그대로 어드민브로에 어댑터를 꽂아서 디비와 해당하는 리소스를 연결시켜주는 역할을 한다.

다음으로는 라우터 주소와 리소스를 선택해서 입력해주는 작업이 필요하다.

  const adminBro = new AdminBro({
    resources: [
      Board,
      BoardLike,
      BoardSide,
      BoardTag,
      Comment,
      CommentLike,
      Message,
      MessageInfo,
      Notice,
      PaymentHistory,
      Shop,
      Place,
      SubCategory,
      TopCategory,
      User,
    ],
    rootPath: '/admin',
  });

새로운 어드민브로를 선언하고 리소스 배열 속에 접근하고 싶은 Entity를 이어주면 된다.
그리고 rootPath로 /admin을 적용하면 작업이 마무리된다.

여기서 주의할 점 !

rootPath에 /admin을 제외한 부가적인 옵션이 들어가면 홈페이지로 접근이 되지 않는다.
나온지 별로 안되서 그런진 모르겠지만 이건 이유를 도대체가 찾을 수가 없어서(...)
그냥 rootPath는 /admin가 고정이라고 보는게 좋을 듯 하다.

더불어서 이게 본론인데, 리소스에 Entity를 넣기만 하면 아래와 같은 에러가 발생되는 것을 확인할 수 있다.

어드민브로가 디비에 직접적으로 접근하는 것이 아니라
ORM으로 작동하는 것으로 확인이 되고 있는데

Active Record 패턴이라는 것이 존재한다.

Active Record 패턴?

아주 쉽게 말해서 SQL을 사용하지 않고 DB에 접근을 해서 조작을 하는 패턴을 이야기한다.

살짝 위를 올라가서 확인을 해보면
수업에서는 nestjs에서 typeorm과 mysql을 이어주기 위하여
mysql2라는 라이브러리를 다운받았던 적이 있었는데
그런것을 사용하지 않고 순수하게 typeorm만으로 연동을 해서 사용하다보니 순수하게 orm 기능으로 작동을 하는 것이라 발생하는 오류라고 판단이 됐다.

그래서 이러한 부분을 해결하기 위헤서는 아주 간단하게

해당하는 entity에 extends BaseEntity를 달아주면 해결할 수 있다.

옵션을 달아놓은 모습

마지막으로 렌더링 설정

렌더링은 간단하게 하는 방법이 있고, 복잡하게 하는 방법이 있다.

간단하게 하는 방법에는 그냥 URL을 치면 누구든 접근이 가능한 방식인데

const router = AdminBroExpress.buildRouter() 이러면 끝난다.

하지만 로그인을 하고 들어가는 복잡하게 하는 방법은 코드가 조금 길다.

const router = AdminBroExpress.buildAuthenticatedRouter(
    adminBro,
    {
      cookieName: 'adminBro', <<-
      cookiePassword: 'session Key', <<-
      authenticate: async (email, password) => {
        const user = await getConnection()
          .createQueryBuilder()
          .select('user')
          .from(User, 'user')
          .where({ userEmail: email })
          .getOne();

        if (!user || user.userState === false) {
          return false;
        } else {
          const isAuth = await bcrypt.compare(password, user.userPassword);
          if (isAuth) {
            return user;
          }
        }
      },
    },
    null,
    {
      resave: false, <<- 
      saveUninitialized: true, <<- 
    },
  );

그런데 유심히 보면 사실상 검증하는 99%를 차지하는 것을 볼 수 있다.

그 중 주석처리를 해놔도 상관없지만, 한번쯤은 생각해봐야하는 코드가 있는데 <<- 를 입력해놓은 곳이다.
해당하는 부분을 주석처리를 하고 서버를 올려보면 이와같은 에러를 볼 수 있다.

secret, resave ,saveUninitialized option의 값이 존재하지 않는다는 것을 볼 수 있다.

req.secret provide secret option?

이건 진짜 몰라서 찾아봤다.
세션에 secret이란 쿠키를 서명하는데 사용되는 것으로 확인이 되고 있는데.
이것을 입력하지 않을 경우 백엔드쪽에서 확인을 하지 못해서? 접근 자체가 불허가 되는 것 같다.

그렇다면 이것 또한 암호처럼 사용하는 것이기 때문에 환경변수에 추가를 해줘야할 것 같다.
실제로 값은 아무렇게나 존재하기만 해도 에러가 발생하진 않는다.

resave?

이것은 에러코드 앞에도 적혀있지만 express-session에서 적용되는 사항인데
request마다 세션에 변경 사항이 없을 경우에도 다시 저장하는 옵션이다.

즉 쓸모없는 데이터 낭비 + 한번에 여러곳에서 작업할 경우 충돌이 날 수 있기 때문에
false 옵션을 걸어놔야한다.

saveUninitialized?

이것도 세션에서 적용되는 사항인데
세션이 저장되기 전에 uninitialized로 미리 저장을 한다...라고 적혀있는데 이 부분은 자세하게 공부를 해봐야할 것 같다. 도대체 뭔 소리인지 잘 모르겠다;

수업의 커리큘럼상 Jwt를 이용한 로그인을 하다보니 (쿠키사용) 상대적으로 세션에 대한 지식이 많이 모자른 것 같아서 생기는 문제같은데 한번 확인을 해봐야할 것 같다.


최종 적용

적용은 아주 간단하게 할 수 있다.

app.use(adminBro.options.rootPath, router)

요렇게 적어주면 적용 자체는 끝나게 된다.

일단 나는 main.ts에 넣어서 사용을 하고 있는데, 그러다보니 main.ts가 상대적으로 비대해져서(...) 밖으로 빼서 사용하는 것으로 한번 작업을 해봐야할 것 같다.

여기서 잠깐!

사람마다 다르겠지만, main.ts에 app.use(graphqlUploadExpress()) 를 적용해놓은 상태로 어드민브로에서 값을 수정하거나 생성할 경우에 아래와 같은 에러를 확인할 수 있다.

도대체 먼 괴상한 에러인가 했더니 파일 업로드를 할 때 사용하는 형식은 multipart인데
ORM을 통해서 수정을 하는 것은 multipart의 형식이 아니라 에러가 발생하는 것이였다.

그럼 도대체 어떻게 해야하는데요?

결국 최상단인 main.ts에서 어드민브로와 이미지업로드 모듈이 충돌을 하는 것이기에

어드민브로는 최상단에 두고, 이미지 업로드를 하위 파일로 내려주면 될 것이라고 생각을 했다.

위의 사진과 같이 AppModule에서 이미지업로드 모듈을 연결해줬더니 에러가 발생하지 않는 것으로 확인됐다.

이럼 main.ts에서 어드민브로를 빼내려고 했던 내 꿈은 도대체 어디로 가는 것인가....


글을 정리하며

나는 지금 NestJS의 프레임워크를 활용하고 있다보니
노드를 사용할 줄 모른다고 생각을 하고 있다.

노드의 자유로움때문에 네스트가 생겼다고는 하지만, 결국 노드를 할 줄 알아야한다고 생각을 하고 있었는데, 어드민브로를 작업하면서 마음만 먹으면 문제없이 할 수 있겠다 라는 생각도 들었고 상당히 재밌게 작업을 했던 기억이 난다.

이제서야 조금 더 찾아보니 어드민브로가 제공하는 옵션이 상당히 많고 커스텀도 지원을 하는 것 같다.

어드민브로의 삭제는 소프트딜리트가 아닌 하드딜리트로 구성이 되어있는데, 이것을 소프트딜리트로 바꾸는 작업도 차차 해봐야할 것 같다.

끝!

profile
물류 서비스 Backend Software Developer

5개의 댓글

comment-user-thumbnail
2022년 6월 7일

안녕하세요 adminbro 검색 유입으로 포스트 해주신 글 너무 잘봤습니다.
여쭤볼 점이 있는데요ㅠㅠㅠ혹시 adminbro는 DB가 mongoDB만 많은 분들이 사용하시는것같아서요
혹시 mysql로 하셨는지 mongoDB로 하셨는지 여쭤봐도될까요???

1개의 답글
comment-user-thumbnail
2022년 11월 17일
import AdminBroExpress from '@admin-bro/express';

로 받으시면 됩니다.

답글 달기
comment-user-thumbnail
2022년 12월 23일

AdminBro는 2021년 6월부로 AdminJS로 이름이 변경되었습니다! AdminBro로 이용 시 구버전을 이용하게 되므로 최신 버전을 이용하려면 AdminJS로 설치해야 합니다. 글에서 이 점 언급만 해 주시면 감사하겠습니다!
참고: https://github.com/SoftwareBrothers/adminjs/releases/tag/v5.0.0

답글 달기