본문 바로가기

Javascript & Typescript/실무와 가까워지는 Node.js 백엔드 개발

[3주차] Order API 코드 작성

이번 주차에서 주어진 과제는 이미 추가된 메뉴 추가 코드에 메뉴 삭제 API를 하나 더 만드는 것이다.

@/models/events.model.ts

class EventType {
  // ... (생략) ...
    
  async removeOrderByTransaction(args: IRemoveOrderReqParams): Promise<void> {
    try {
      await FirebaseAdmin.getInstance().Firestore.runTransaction(async (transaction) => {
        const doc = await transaction.get(this.OrdersCollection(args.eventId).doc(args.guestId));
        if (doc.exists === false) {
          return Promise.reject(new DeleteOrderError(args.eventId, args.guestId));
        }
        transaction.delete(this.OrdersCollection(args.eventId).doc(args.guestId));
      });
      await this.updateCache({ eventId: args.eventId });
    } catch (err) {
      return Promise.reject(err);
    }
  }
}

@/models/errors/DeleteOrderError.ts

export class DeleteOrderError extends Error {
  eventId: string;

  docId: string;

  constructor(eventId: string, docId: string) {
    super(`Failed to delete doc of guest ${docId} in ${eventId}`);
    this.eventId = eventId;
    this.docId = docId;
  }
}

과제에서 트랜잭션을 사용해보는 것을 추천했기 때문에 이를 사용하는 코드를 작성했다. 그리고 과제에서는 주어지지 않았지만 트랜잭션 중 실패했을 때, 이러한 상황을 알려주는 무언가가 필요하다고 생각해서 코드를 짜다보니 자연스럽게 예외도 별도로 정의해 주었다. 그리고 Promise의 reject 함수를 사용하여 에러를 반환했다.

지금 코드를 보니 reject가 중복으로 적용된 것 같다. try-catch문은 삭제해도 될 것으로 보인다.

@/controllers/event/event.controller.ts

// ...
import { Events } from '@/models/events.model';


export default class EventController {

// ...(생략)...

  static async deleteOrder(req: Request, res: Response): Promise<void> {
    const token = req.headers.authorization;
    if (token === undefined) {
      log('No auth');
      return res.status(401).end();
    }

    try {
      await FirebaseAdmin.getInstance().Auth.verifyIdToken(token);
    } catch (err) {
      log('Not valid auth');
      return res.status(400).end();
    }

    const validateReq = validateParamWithData<IRemoveOrderReq>(
      {
        params: req.query,
      },
      JSCRemoveOrder,
    );

    if (validateReq.result === false) {
      log('Invalid Request');
      return res.status(400).send({
        text: validateReq.errorMessage,
      });
    }

    try {
      const result = await Events.removeOrderByTransaction(validateReq.data.params);
      return res.json(result);
    } catch (err) {
      log(err);
      if (err instanceof DeleteOrderError) {
        return res.status(404).send(err.message);
      }

      return res.status(500).send(err.toString());
    }
  }
}

기본적인 구조는 지난주와 했던 것에서 크게 차이는 없다. 앞서 MVC 구조에서 설명한 봐와 같이 모델 부분인 Events에서 작성한 함수를 호출하여 결과를 얻는다.

@/pages/api/events/[eventId]/orders/[guestId].ts

// ...(생략)...

export default async function handle(req: NextApiRequest, res: NextApiResponse): Promise<void> {
  // eslint-disable-next-line no-console
  const { method } = req;
  log(method);
  const supportMethod = ['DELETE'];
  if (supportMethod.indexOf(method!) === -1) {
    return res.status(400).end();
  }
  if (method === 'DELETE') {
    await eventController.deleteOrder(req, res);
  }
}

또한, 과제에서 주어진대로 Delete함수만 호출하도록 라우터를 작성하였다.