[nodejs] 간단한 restfulAPI 서버만들기3 - 코드개선(추상화), 리펙토링
본문 바로가기

Backend/node.js

[nodejs] 간단한 restfulAPI 서버만들기3 - 코드개선(추상화), 리펙토링

추상화?

로직을 묶는다는 것은 추상화를 한다는 것 공통된 패턴을 빼내 항상 일정한 규격에 맞게 행동하도록 코드를 재구축하는것.

현 코드에서는 statusCode, header를 정하고 http body에 표시하는것을 공통으로 쓰고있고 각 요청들마다 url과 메소드등이 필요하다.

잘추상화한 함수를 만들어둬서 타입체크를하도록 잘막아둬야 굳이 런타임에 가지않아도, 유저가 사용하기전에 미리 테스트를 해보기 때문에 안전하다.

 

api.js

기본구조로써 Route와 APIResponse가 들어갈예정. 모듈화가되어 route를  export 할수있게됨

//@ts-check

/**
 * @typedef APIResponse
 * @property {number} statusCode
 * @property {*} body
 */

// /**
//  * @tyoedef Route
//  * @property {string} url
//  * @property {string} method
//  * @property {(valuse:Object)=> string} callback
//  */

/**
 * @typedef Route
 * @property {RegExp} url
 * @property {'GET' | 'POST'} method
 * @property {()=> Promise<APIResponse>} callback
 */

/** @type {Route[]} */
const routes = [
 ... 
]

// 이 파일은 모듈이고 모듈에서 route를 내보냄
module.exports = {
  routes
}

1. route만들기

main.js

//@ts-check

// npm run server

/**
 * 글(post) API
 *
 * 전체보기:GET /posts
 * 특정글보기:GET /posts/:id
 * 글쓰기:POST /posts
 */

// 모듈을가져올땐 require이용
// http 모듈로 서버만들기
const http = require('http')
const { mainModule } = require('process')
// 내가만든 모듈에서 route가져오기
const { routes, posts } = require('./api')

const server = http.createServer((req, res) => {
  async function main() {
    // 일치하는 라우트찾기
    const route = routes.find(
      (_route) =>
        req.url &&
        req.method &&
        _route.url.test(req.url) &&
        _route.method === req.method
    )

    //route가 없을때
    if (!req.url || !route) {
      res.statusCode = 404
      res.end('Not found1')
      return
    }

    const regexResult = route.url.exec(req.url)

    if (!regexResult) {
      res.statusCode = 404
      res.end('Not found2')
      return
    }

    //post처리
    /** @type {Object.<string, *> | undefined} */
    const regBody =
      //header가 json의 조건을 만족해야(&&) 프로미스가 실행되고 아니면(||) undefined
      (req.headers['content-type'] === 'application/json' &&
        (await new Promise((resolve, reject) => {
          req.setEncoding('utf-8')
          req.on('data', (data) => {
            try {
              resolve(JSON.parse(data))
            } catch {
              reject(new Error('파싱이 실패했음'))
            }
          })
        }))) ||
      undefined

    // console.log(redBody) http POST localhost:8080/posts a=1 ==> {"a":"1"}

    // route가 있을때
    const result = await route.callback(regexResult, regBody)
    res.statusCode = result.statusCode
    // res.end(result.body)

    //body가 {}일때랑 string일때 대응
    if (typeof result.body === 'string') {
      res.end(result.body)
    } else {
      res.setHeader('Content-type', 'application/json; charset=utf-8')
      res.end(JSON.stringify(result.body))
    }
  }

  main()
})

const PORT = 8080

server.listen(PORT, () => {
  console.log(`the server is listening at port: ${PORT}`)
})

api.js

//@ts-check

/**
 * @typedef Post
 * @property {string} id
 * @property {string} title
 * @property {string} content
 */

/** @type {Post[]} */
const posts = [
  { id: '1', title: 'my first post', content: 'hi' },
  { id: '2', title: 'my second post', content: 'bye' },
]

/**
 * @typedef APIResponse
 * @property {number} statusCode
 * @property {string | object} body
 */

// /**
//  * @tyoedef Route
//  * @property {string} url
//  * @property {string} method
//  * @property {(valuse:Object)=> string} callback
//  */

/**
 * @typedef Route
 * @property {RegExp} url
 * @property {'GET' | 'POST'} method
 * @property {(maches: string[], body: object | undefined)=> Promise<APIResponse>} callback
 */

/** @type {Route[]} */
const routes = [
  {
    url: /^\/posts$/, //'posts' -> regExe로 고쳐야함
    method: 'GET',
    callback: async () => ({
      statusCode: 200,
      body: posts,
    }),
  },

  {
    url: /^\/posts\/([a-zA-Z0-9-_]+)$/, //'posts/:id' -> regExe
    method: 'GET',
    callback: async (maches) => {
      console.log(maches)
      const postId = maches[1]
      if (!postId) {
        return {
          statusCode: 404,
          body: 'Not found3',
        }
      }

      const post = posts.find((_post) => _post.id === postId)

      if (!post) {
        return {
          statusCode: 404,
          body: 'Not found',
        }
      }

      return {
        statusCode: 200,
        body: post,
      }
    },
  },

  {
    url: /^\/posts$/, //'posts' -> regExe
    method: 'POST',
    callback: async (_, body) => {
      if (!body) {
        return {
          statusCode: 400,
          body: 'no body',
        }
      }

      /** @type {string} */
      const title = body.title
      const newPost = {
        id: title.replace(/\s/g, '_'),
        title,
        content: body.content,
      }
      posts.push(newPost)
      // statusCode: 200,
      // body: {},
      return {
        statusCode: 200,
        body: newPost,
      }
    },
  },
]

// 이 파일은 모듈이고 모듈에서 route를 내보냄
module.exports = {
  routes,
  posts,
}

get posts/:id

main.js 에서 regexResult 값 req.url을 콜백에 넘겨줌

api.js 에서 async(matches)를 통해 받아옴

 

post

main.js 에서 regexBody 값으로 파싱된 데이타를 콜백에 넘겨줌

api.js 에서 async(_, body)를 통해 받아옴(여기서 _는 maches를 의미)

2. APIResponse

 

반응형