react, nest, db와 함께하는 수직적 기능 구현 간살

모기·2025년 7월 1일

나는 가끔 타입스크립트가 밉다.

// src/types/express.d.ts

// Express 모듈의 전역 네임스페이스를 확장합니다.
// NestJS가 기본적으로 Express를 사용하기 때문에 이 방식이 NestJS에서도 유효합니다.
declare namespace Express {
  // JwtStrategy의 validate 메서드에서 반환하는 객체의 타입을 정의합니다.
  // 이 타입은 req.user에 주입될 사용자 정보의 구조를 나타냅니다.
  // 예시: { id: number; name: string; }
  interface User {
    id: number; // 사용자 고유 ID
    name: string; // 사용자 이름 또는 닉네임
    // 필요에 따라 이 외의 다른 사용자 속성들을 여기에 추가할 수 있습니다.
    // 예: email?: string; roles?: string[];
  }

  // Express의 Request 인터페이스를 확장하여 'user' 속성을 추가합니다.
  interface Request {
    user?: User; // 'user' 속성은 선택적일 수 있습니다 (인증 가드가 없는 라우트에서는 없을 수 있음).
  }
}

이건 오버로딩도, 오버라이딩도 아닌 선언 병합/모듈 보강이라는
타입스크립트의 기능이라고 한다.

    for (const item of toDelete) {
      await this.sessionEnsembleRepo.delete({ sessionId: item.sessionId });
    }

    for (const item of toUpdate) {
      if (item.sessionId) {
        const sessionToUpdate = await this.detailSession(item.sessionId);
        await this.patchSession(item.sessionId, sessionToUpdate, postId);
      }
    }

    for (const item of toAdd) {
      if (item.sessionId) {
        await this.enrollSession(item, postId);
      }

이렇게 열심히 코드를 짜놓았는데, AI의 '트랜잭션' 한 단어에
세상이 무너져 내린다.

  • 나 -
    if (user) {
      const selfApplication = applicationEnsembleList.find((app) => app.username === user.username)
      if (selfApplication) {
        setApplication(selfApplication);
        setIsIn(true);
      } else {
        setIsIn(false);
      }
    } else {
      setIsIn(false);
    }
  • AI -
// 1. user가 존재할 경우에만 find를 실행하고, 없으면 undefined를 할당합니다.
const selfApplication = user 
  ? applicationEnsembleList.find((app) => app.username === user.username) 
  : undefined;

// 2. find의 결과(selfApplication)가 있는지 여부에 따라 상태를 한 번에 업데이트합니다.
setIsIn(!!selfApplication);
setApplication(selfApplication || null); // 찾은 값이 있으면 설정, 없으면 null로 초기화

진짜 잘짬...


글을 마지막으로 쓴지 꽤 오래됐다.
다양한 기능들을 계속 구현하다보니 이제는 nest가 어떻게 돌아가는지 대충 눈에 익는다.

중고 거래와 같은 기본적인 게시판부터, 합주라는 테마의 게시판, 좋아요 및 댓글들을 구현했다.

정확하게 좋아요와 댓글은 리팩토링이라고 볼 수 있다.
둘 다 post라는 모듈에만 적용이 되는 api였는데, 좋아요, 댓글은 통합적으로 관리하는 게 좋다고해서 리팩토링하게 되었다.

그 밖에도 유저 프로필 이미지에 대한 데이터를 처리하다보니 dto의 중요성에 대해서도 알게 됐다.

클라이언트가 요청해서 클라이언트가 응답을 받기까지의 과정을 중심으로 고찰하려 한다.

1. 클라이언트의 요청

react나 react native에서 원하는 데이터를 처리하기 위해서는 api를 호출한다.

api를 호출하는 형식은 비슷하다. 또한 api를 호출하는 방식에는 다양한 방법 있는데, 유지 보수 및 보안(토큰) 등을 활용하기 위해서 axios를 커스텀해서 사용한다.

const response = await axiosInstance.get<형식>(`api 주소`)
const response = await axiosInstance.post(`api 주소`, payload);

이런 느낌으로 back에 요청을 하게된다.

2. 서버의 응답

1. CORS

서버는 먼저 요청이 제대로된 요청인지 확인한다. 이를 CORS라는 보안 정책을 통해서 확인한다.
기본적으로는 출처가 다른 요청에 대해서는 그 요청을 원천 차단하지만,
특별한 설정을 해주면 요청을 받아준다.

  app.enableCors({
    origin: allowedOrigins, // ⬅️ origin을 배열로 전달
    methods: 'GET,HEAD,PUT,PATCH,POST,DELETE',
    credentials: true,
    allowedHeaders: 'Content-Type, Accept, Authorization',
    exposedHeaders: 'Authorization',
  });

그 밖의 특별한 전처리 과정을 메인에서 거친다.

2. app Module

이후 app Module에 등록된 api에 한해 해당 모듈로 요청을 보낼 수 있다.
서버에서 처리하는 동작들은 모듈의 형태로 구현되어 있다.
예를 들어, 게시물을 등록, 수정, 삭제하는 기능들은 해당 게시물 모듈의 서비스에서 처리한다.

그 밖에도 app Module에서는 DB와 연결하거나 서버 실행시 초기 설정들을 담당한다.

3. post Module

다양한 모듈의 형태가 있다. 실제로 나는 중고거래, 합주 모집, 좋아요, 댓글과 같은 모듈을 만들었다. 그 중 제일 설명하기 간편한 post Module을 예시로 들 것이다.

1. controller

컨트롤러에서는 Http 메소드에 따라서, 그리고 요청 url에 따라서 적당한 메소드를 호출한다. guard가 붙어있으면 guard 종류에 따라 jwt 토큰이 유효한지, 로그인이 된 유저인지, 역할이 있는지 등등을 판단하여 적절하지 않은 경우 결과를 반환한다.

또한 Dto에 적합하지 않으면 다시 반환한다. 모든 조건에 부합하면 service 객체의 함수를 호출하여 처리를 진행한다.

2. service

service는 객체 형태로 관리되며 객체가 주입되면 별도로 선언하지 않고 사용할 수 있다.

다른 service 객체를 사용하기 위해선 해당 객체를 모듈의 provider에 등록을 하고, 해당 모듈을 export 해야한다. 그럼 다른 모듈에서는 해당 모듈의 제공자를 주입시켜 사용할 수 있다.

예를 들어 post Module에서 user service를 사용하고 싶으면 user Module의 provider에 service 등록 및 user module을 export하고 post module에서는 user module을 import한다.

그럼 post service에서 constructor를 통해 의존성을 주입한다.
DB에 저장이 필요한 경우에는 entity를 바탕으로 한 repository를 주입받고 사용할 수 있다.

4. DB

db에는 다양한 방식으로 접근할 수 있는데, 주입받은 repository를 사용하거나 transactionalEntityManager을 사용한다. 특히 transactionalEntityManager은 여러 DB 작업을 transaction하게 처리해야할 때 사용할 수 있다.

데이터 수신

서버에서 데이터가 처리되면 데이터는 return을 통해서 전달되고 최종적으로는 프론트까지 데이터가 전달된다.

그리고 받은 데이터는

response.data

의 형태로 꺼내서 사용할 수 있다.

profile
안녕

0개의 댓글