Chapter 7. Express 미들웨어 & API 응답 통일 & 에러 핸들링

Arin·2025년 12월 26일

UMC 8기 - Node.js

목록 보기
9/11

Express 미들웨어 (app.use() 와 app.METHOD())

아래는 경로가 없는 미들웨어 함수이기 때문에 이 함수는 모든 경로, 모든 HTTP 메서드 요청에 대해서 실행된다

const myLogger = (req, res, next) => {
	console.log("LOGGED");
	next(); // 다음 미들웨어 또는 라우트 핸들러로 제어를 넘김
}

**app.use**(myLogger); 

/* ----> 위에 두 개의 코드를 하나로 합친 것이다. 
app.use((req, res, next) => {
	console.log("LOGGED");
	next(); 		
});
*/

'/' 경로로 들어오는 모든 HTTP 요청에 대해 실행되는 미들웨어 함수는 app.use()와 '/'를 이용한다.

**app.use('/'**, (req, res, next) => {
  console.log('Request Type:', req.method)
  next();
});

'/' 경로로 들어오는 GET 요청에 대해 실행되는 미들웨어 함수는 app.get()'/' 를 이용한다.

app.get('/', (req, res) => {
	console.log("/");
	res.send("Hello UMC!");
}

Express 미들웨어 (next())

next() 는 다음 미들웨어 함수 or 해당 라우트 핸들러로 제어를 넘기는 함수이다. 따라서 next()를 호출하지 않으면 요청이 다음 단계로 진행되지 않는다.

const myLogger = (req, res, next) => {
  console.log("LOGGED");
  // next();
}

app.use(myLogger);

app.get('/', (req, res) => {
  console.log("/");
  res.send("Hello UMC!");
});

app.get('/hello', (req, res) => {
  console.log('/hello');
  res.send('Hello world!');
});

next() 를 주석처리 했더니 '/' 경로로 요청을 해도 LOGGED만 출력되고 '/hello' 경로로 요청을 해도 LOGGED만 출력되는 것을 확인했다.

즉, app.use(myLogger)미들웨어만 실행되고 이후에 실행되어야 할 라우트 핸들러는 실행되지 않는 것이다. 이렇게 되면 res.send() 코드가 실행되지 않아 응답이 전송되지 않고 이로 인해 무한 로딩 상태 or 타임 아웃으로 끝난다.

Error 처리

export const handleMemberSignUp = async (req, res, next) => {
  console.log("회원가입을 요청했습니다!");
  console.log("[MemberController] request body:", req.body); // 값이 잘 들어오나 확인하기 위한 테스트용

  const member = await memberSignUp(bodyToMember(req.body)); 
  // res.status(StatusCodes.OK).json({ 
  //   resultType: "SUCCESS",
  //   error: null,
  //   success: member,
  // });

  res.status(StatusCodes.OK).success(member);
};

member.service.js

member 저장 + preference 저장 두 db 작업을 트랜잭션을 사용하여 하나로 묶었다.
해당 트랜잭션 함수를 repository 계층으로 분리하는 작업은 이후에 리팩토링 할 예정이다.

export const memberSignUp = async (data) => {
  try{
    const result = await prisma.$transaction(async (tx) => {
      // 1. 이메일 중복 검사
      const existing = await tx.member.findFirst({where:{email:data.email}});
      if(existing)
        throw new error("이미 존재하는 이메일입니다.");
      // 2. member 생성
      const created = await tx.member.create({
        data:{
          email: data.email,
          name: data.name,
          gender: data.gender,
          age: data.age,
          address: data.address,
          specAddress: data.specAddress,
          phoneNumber: data.phoneNumber,
        }
      });

      const memberId = created.id;
      
      // 3. member_prefer 생성 
      for (const categoryId of data.preferences) {
        await tx.memberPrefer.create({
          data: {
            memberId: memberId,
            categoryId: categoryId,
            createdAt: new Date(),
            updatedAt: new Date(),
          },
        });
      }
      // 4. member + member_prefer 정보 조회 
      const member = await tx.member.findUnique({ where: { id: memberId } });
      const preferences = await tx.memberPrefer.findMany({
        where: { memberId },
        orderBy: { categoryId: 'asc' },
        select: { categoryId: true }
      });

      return responseFromMember({ member, preferences });
    });

    return result;
  
  } catch(error) {
    // rollback
    console.error("회원가입 실패: ", error.message);
    throw error;
  }

GET /api/member

profile
헤맨만큼 내 땅

0개의 댓글