Jaeseo's Information Security Story

[Next.js] - Express Custom Server 사용하기 (Feat. Boilerplate 만들기) 본문

Coding and Linux Study/React

[Next.js] - Express Custom Server 사용하기 (Feat. Boilerplate 만들기)

Jaeseokim 2020. 6. 6. 16:01

개발을 하다가 보니 기본적으로 제공하는 next.js의 Routing 시스템이 편리하지만 API 부분에 대해서는 조금 불편한 점이 발견되었습니다. 기본적으로 middleware를 추가하는 작업에 대해 번거로운 작업을 동반해야 하고 그리고 기존 Express에서 사용하던 middleware에 대해 호환이 완벽하게 안된다는 점에서 따로 찾아보게 되었습니다.

next.js 공식문서

개발을 하면서 multer를 사용하여 이미지 업로드 기능을 구현을 원하는 부분이 있었는데 multer를 사용하여 개발을 할려고 하자 아래와 같은 코드가 완성 되었습니다.

import multer from "multer"

const upload = multer({ dest: 'uploads/' })

export default (req, res) => {
  if (req.method === 'POST') {
    upload.single('img')(req, res, (err) => {
        // 파일 처리후 응답 작업 영역
    })
  } else {
    // Handle any other HTTP method
  }
}

이러한 상태로 구현에는 일단은 성공 했지만 원래 구현을 해야 하는 upload.array('img') 에 대해서는 또 오류가 발생 하는 문제가 발견되어서 아예 express는 API를 담당하게 하고 나머지는 기존 next.js을 사용 해야 하겠다는 마음을 먹게 되었습니다.

npx create-next-app --example custom-server-express custom-server-express-app

위의 명령어를 입력을 하여서 기본 제공되는 템플릿을 불러와 봅니다.!

const express = require('express')
const next = require('next')

const port = parseInt(process.env.PORT, 10) || 3000
const dev = process.env.NODE_ENV !== 'production'
const app = next({ dev })
const handle = app.getRequestHandler()

app.prepare().then(() => {
  const server = express()

  server.get('/a', (req, res) => {
    return app.render(req, res, '/a', req.query)
  })

  server.get('/b', (req, res) => {
    return app.render(req, res, '/b', req.query)
  })

  server.all('*', (req, res) => {
    return handle(req, res)
  })

  server.listen(port, err => {
    if (err) throw err
    console.log(`> Ready on http://localhost:${port}`)
  })
})

이제 여기서 기본 코드를 한번 확인해봅니다.

const app = next({ dev })
const handle = app.getRequestHandler()

next에서 app 객체를 받고 app.getRequestHandler() 통해 handle을 받는데 이게 기존 next Routing에 대한 역할을 담당합니다.

따로 경로를 지정하여 routing 하는 것 이외의 기본 next Routing은 마지막에 아래의 항목을 추가하여 next에게 처리를 넘기는 것을 볼 수 있습니다.

server.all('*', (req, res) => {
    return handle(req, res)
})

특정 경로에 대해 특정 페이지를 렌더링 하고 싶은 경우는 아래와 같은 형태로 해주어야 합니다.

server.get('/a', (req, res) => {
    return app.render(req, res, '/a', req.query)
  })

지정한 Route 포인트를 제외한 다른 곳으로 이동하는 것을 방지하기 위해서는 next.config.js 파일에 아래의 내용을 추가하면 됩니다.

module.exports = {
  useFileSystemPublicRoutes: false
}

저는 이제 제가 원하는 개발 방향은 /api 로 들어오는 요청은 express에서 처리를 하고 나머지는 next 에서 처리를 원하기 때문에 간단하게 api를 사용하는 boilerplate 만들어 봤습니다.

(원래는 그냥 간단하게 예제만 적으려고 하다가 나중에도 계속 수정하여서 사용할 수 있도록 boilerplate로 만들었습니다.)

GItHub 주소 : https://github.com/JaeSeoKim/simple-next-express-boilerplate

기본적으로 server.js 파일은 아래와 같이 만들어서 /api 에 대한 요청은 express route 파일에서 처리를 하고 나머지는 기본 next.js 에서 처리가 가능하도록 했습니다.

import express from 'express'
import next from 'next'

import apiRouter from './routes/apiRouter'

const port = parseInt(process.env.PORT, 10) || 3000
const dev = process.env.NODE_ENV !== 'production'
const app = next({ dev })
const handle = app.getRequestHandler()

app.prepare().then(() => {
  const server = express()

  server.use('/api', apiRouter)

  server.all('*', (req, res) => {
    return handle(req, res)
  })

  server.listen(port, err => {
    if (err) throw err
    console.log(`> ✨Ready on http://localhost:${port}`)
  })
})

그리고 실제로 api 개발을 할 때 Next.js 쪽의 처리가 완료가 되어야 서버가 활성화 되고 nodemon으로 변경 사항이 생겼을 때 재 로딩 시간이 너무 길어져서 express api 개발 서버 파일을 따로 만들어서 API 개발을 편리 하게 할 수 있도록 분리 했습니다.

dev:api - Only /api requests work (nodemon --exec babel-node server/server-dev-api.js)

import express from 'express'

import apiRouter from './routes/apiRouter'

const port = parseInt(process.env.PORT, 10) || 3000
const dev = process.env.NODE_ENV !== 'production'

const server = express()

server.use('/api', apiRouter)

server.listen(port, err => {
  if (err) throw err
  console.log(`> ✨Ready on http://localhost:${port}`)
})

이제 기본적인 프로젝트 구조는 아래와 같은 형태로 만들었습니다.

.
├── README.md
├── lib
│   └── utils
│       └── useRequest.js
├── next.config.js
├── nodemon.json
├── package.json
├── pages
│   ├── [id].js
│   ├── _app.js
│   ├── _document.js
│   └── index.js
├── public
│   └── robots.txt
├── server --> for Express.js (/API)
│   ├── api
│   │   └── user
│   │       └── getUser.js
│   ├── middlewares
│   ├── routes
│   │   ├── apiRouter.js
│   │   └── userRouter.js
│   ├── server-dev-api.js
│   └── server.js

server 폴더를 제외한 나머지는 기본 next.js 프로젝트 구조와 동일 하게 사용 하였습니다.

server폴더에서는 routes, middlwares, api 3가지 기능으로 분리 시켜 구조를 완성 시켰습니다.

이런 식으로 자신이 개발을 할 때 필요한 프로젝트 구조를 미리 Boilerplate 화 시켜 필요에 따라 새로 구조를 바꾸면서 사용하면 도움이 많이 될 것 같아 한번 만들어봤습니다.

Comments