티스토리 뷰

미들웨어 작성시 validation과 더불어 적절한 반환값을 반환해야한다.

프로젝트를 진행하면서 설계한 사례를 알아보자.

아래 API들은 가장 기초적인 형태로만 알아본다.

개발환경

라이브러리

  • express
  • sequelize

DB

  • Postgres

get

전체 데이터조회시에는 아래와 같이 진행.

express에서 제공하는 HttpStatusCodes 를 사용하면 명시적으로 status를 간편하게 지정할 수 있다.

전체

router.get('/', async (_, res) => {
    const result = await MyTest.findAll({ raw: true })
    res.status(HttpStatusCodes.OK).send(result)
})

단건

router.get('/:seq', async (_, res) => {
    const result = await MyTest.findOne({ where: { seq }, raw: true })
    res.status(HttpStatusCodes.OK).send(result)
})

post

데이터 생성할때는 기본적으로 post를 사용하였다. 기본적으로 단건만 생성하도록 하였다.

생성된 객체를 반환하는 이유는 사용자 정의 데이터 뿐만아니라, Autoincrement PK를 사용하거나 프로그램 정의 데이터도 추가되는 경우 해당된다.

프로그램 정의 데이터

MyTest 테이블에 created_at, updated_at, created_by, updated_by컬럼이 정의된 경우,

해당 컬럼들은 데이터 생성시에 서버쪽에서 삽입하는 데이터이다.

router.post('/', async (req, res) => {
    const body = req.body
    const nTest = await MyTest.create(body)

    res.status(HttpStatusCodes.CREATED).send(nTest.get({ plain: true }))
})

☑️ 응답 데이터를 고르는 기준은?

사용자가 CUD(생성, 수정, 삭제)시에 해당 리소스를 재조회를 하지 않아도 갱신된 데이터를 응답 받을 수 있어야하는게 기준이다. 예를 들어 생성시에 자동 증가 PK를 사용하는 경우는 생성된 식별키를 응답에 포함시켜 사용자가 생성된 데이터를 온전히 받아 볼 수 있어야한다.

수정, 삭제시에도 마찬가지로, 일관된 응답 데이터가 있기보다는 재조회를 하지 않고도 처리된 데이터 정보를 알 수 있게 하는 것이 핵심이다.

patch

쿼리 파라미터(/:seq)를 통해서 단건 수정을 수행한다.

이때 쿼리 파라미터 validation이 필요하다.

router.patch('/:seq', async (req, res) => {
    const body = req.body
    // 쿼리 파라미터
    const { seq } = req.params
    const [cnt] = await MyTest.update(body, { where: { seq } })
    const test = MyTest.findOne({ where: { seq } })
    if (test == null) {
        res.status(400).send(ResponseError.get(Codes.NOT_EXIST_ID))
        return
    }

    const resData = cnt == 0 ? null : test
    res.status(HttpStatusCodes.OK).send(resData)
})

쿼리 파라미터에 식별키가 존재하지 않는 경우 상태코드 400 을 반환한다.

존재하는 경우 cnt 를 확인하여 수정이 되었는지 파악 후, 수정되었으면 수정된 객체를 반환한다.

❗ update 함수가 성공적으로 수행되어도 수정되지 않은 경우는 cnt 가 0으로 반환될 수 있으니 확인해야한다.

delete

쿼리 파라미터에 대한 식별키 존재여부에 대한 validation을 거친다.

router.delete('/:seq', async (req, res) => {
    const { seq } = req.params

    const delCnt = await MyTest.destroy({ where: { seq } })
    if (delCnt == 0) {
        res.sendStatus(HttpStatusCodes.BAD_REQUEST)
        return
    }
    
    res.sendStatus(HttpStatusCodes.NO_CONTENT)
})

삭제 개수가 0이면 해당 식별키가 존재하지 않는 경우므로 400 상태코드를 반환한다.

단건과 다건 다루기

위 예제에서는 단건에 대한 RestAPI만 다뤘는데, 다건으로 처리해야하는 경우가 종종 있다.

예를들어 어떠한 리소느는 다건으로 CUD 처리를 자주해야 한다면, 성능상으로 한번에 통신으로 모든 처리를 해주는게 바람직하기 때문이다.

다건 처리에 대해서는 여러의견이 있었으나, 나는 batch prefix와 post Method를 조합한 처리를 선택하였다.

생성

생성시에는 쿼리 파라미터를 통해 식별키를 넘기는 구조를 사용할 수 없으므로, 2가지 방법이 존재한다. 첫번째는 위에서 언급한 batch prefix를 사용하여 단건, 다건 생성을 구분하는 것.

두번째는 요청 본문을 배열로 받아 처리하는 경우이다.

참고로 나는 다건 생성이 필요한 경우 두번째 형식으로 처리한다. 하나의 api로 다건, 단건을 다처리할 수 있기 때문이다.

다건 예

  1. prefix 사용

다건 생성을 분리하는 경우는 요청 본문이 배열인 경우만 요청을 수락한다.

또한 단건에 대한 요청 처리도 별도로 구현한다.

// 단건
router.post('/batch-create', async (req, res) => {
    ...
})
// 다건
router.post('/batch-create', async (req, res) => {
    const bodies = req.body
    if (Array.isArray(bodies) == false) {
        res.sendStatus(HttpStatusCodes.BAD_REQUEST)
    }
    ...
})

2. prefix 미사용

router.post('/', async (req, res) => {
    const bodies = Array.isArray(req.body) ? req.body : [req.body]
    const nTests = await Promise.all(bodies.map((body) => MyTest.create(body)))

    res.status(HttpStatusCodes.CREATED).send(nTests.map((test) => test.get({ plain: true })))
})

수정, 삭제

위에서 언급한대로 batch-update, batch-delete prefix를 사용하여구현한다.

// 수정
router.patch('/batch-update', async (req, res) => {
	...
})

// 삭제
router.post('/batch-delete', async (req, res) => {
    const payemnts = req.body as PaymentAttributes[]

    const delCnt = await Payment.destroy({
        where: {
            seq: {
                [Op.in]: payemnts.map((p) => p.seq),
            },
        },
    })

    res.sendStatus(HttpStatusCodes.NO_CONTENT)
})

 

이상으로 express를 통해 CURD에 대한 REST-API 작성법 및 단건, 다건 처리에 대해서 간단히 살펴보았다.

최초로 REST-API를 작성할 때는 기준을 뭘로 해야할지 막막했는데,
이 계기로 응용을 위한 기본 토대는 다지게 된 것 같다. 😁

댓글