├── .babelrc ├── .dockerignore ├── .env ├── .github └── dependabot.yml ├── .gitignore ├── LICENSE ├── README.md ├── cicd ├── Dockerfile ├── Dockerfile-pm2 ├── build-pm2.sh └── build.sh ├── ecosystem.config.js ├── lib └── utils │ └── useRequest.js ├── next.config.js ├── nodemon.json ├── package-lock.json ├── package.json ├── pages ├── [id].js ├── _app.js ├── _document.js └── index.js ├── public └── robots.txt ├── server-register.js └── server ├── api └── user │ └── getUser.js ├── routes ├── apiRouter.js └── userRouter.js ├── server-dev-api.js └── server.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["next/babel"] 3 | } 4 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | .git 2 | .next 3 | *Dockerfile* 4 | node_modules 5 | .env -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | PORT=3000 -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: npm 4 | directory: "/" 5 | schedule: 6 | interval: weekly 7 | open-pull-requests-limit: 10 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | 21 | # debug 22 | npm-debug.log* 23 | yarn-debug.log* 24 | yarn-error.log* 25 | 26 | # local env files 27 | .env.local 28 | .env.development.local 29 | .env.test.local 30 | .env.production.local 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2020 JaeSeo Kim 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Simple Next.js + Custom Express Server Boilerplate 2 | 3 | > TypeScript Version: [simple-type-next-express-boilerplate](https://github.com/JaeSeoKim/simple-type-next-express-boilerplate) 4 | 5 | `/api` -> `Express Server` 6 | 7 | `*` -> `Next RequestHandler` 8 | 9 | --- 10 | 11 | ### Script 12 | 13 | - dev - Next Dev (`nodemon --exec babel-node server/server.js`) 14 | - dev:api - Only `/api` requests work (`nodemon --exec babel-node server/server-dev-api.js`) 15 | - build - Next Build (`next build`) 16 | - start - Next Start (`cross-env NODE_ENV=production babel-node server/server.js`) 17 | - pm2 - Run with pm2 Cluster Mode (`pm2 start ecosystem.config.js --env production`) 18 | - pm2:dev - Run with pm2 Cluster Mode(dev) (`pm2 start ecosystem.config.js`) 19 | 20 | --- 21 | 22 | ### Docker 23 | 24 | Default Tag: `simple-next-express` 25 | 26 | Test Run Command: `docker run -p 3000:3000 simple-next-express` 27 | 28 | - build - `./cicd/build.sh` 29 | - build(with pm2) - `./cicd/build-pm2.sh` 30 | 31 | --- 32 | 33 | ### File Tree 34 | 35 | ``` 36 | . 37 | ├── LICENSE 38 | ├── README.md 39 | ├── cicd 40 | │ ├── Dockerfile 41 | │ ├── Dockerfile-pm2 42 | │ ├── build-pm2.sh 43 | │ └── build.sh 44 | ├── ecosystem.config.js 45 | ├── lib 46 | │ └── utils 47 | │ └── useRequest.js 48 | ├── next.config.js 49 | ├── nodemon.json 50 | ├── package-lock.json 51 | ├── package.json 52 | ├── pages 53 | │ ├── [id].js 54 | │ ├── _app.js 55 | │ ├── _document.js 56 | │ └── index.js 57 | ├── public 58 | │ └── robots.txt 59 | ├── server 60 | │ ├── api 61 | │ │ └── user 62 | │ │ └── getUser.js 63 | │ ├── middlewares 64 | │ ├── routes 65 | │ │ ├── apiRouter.js 66 | │ │ └── userRouter.js 67 | │ ├── server-dev-api.js 68 | │ └── server.js 69 | └── server-register.js 70 | ``` 71 | 72 | ### ToDo 73 | 74 | - ETC... 75 | -------------------------------------------------------------------------------- /cicd/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:14.4.0 2 | MAINTAINER Jaeseo Kim devjaeseo@gmail.com 3 | 4 | # Environment 5 | ENV NODE_ENV production 6 | 7 | # Create app directory 8 | RUN mkdir -p /usr/src/app 9 | WORKDIR /usr/src/app 10 | 11 | # Installing dependencies 12 | COPY package*.json /usr/src/app/ 13 | RUN npm install --production 14 | 15 | # Copying source files 16 | COPY . /usr/src/app 17 | 18 | # Building app 19 | RUN npm run build 20 | 21 | # Running the app 22 | CMD ["node", "server-register.js"] -------------------------------------------------------------------------------- /cicd/Dockerfile-pm2: -------------------------------------------------------------------------------- 1 | FROM node:14.4.0 2 | MAINTAINER Jaeseo Kim devjaeseo@gmail.com 3 | 4 | # Environment 5 | ENV NODE_ENV production 6 | 7 | # Create app directory 8 | RUN mkdir -p /usr/src/app 9 | WORKDIR /usr/src/app 10 | 11 | # Installing dependencies 12 | COPY package*.json /usr/src/app/ 13 | RUN npm install --production 14 | RUN npm install pm2 -g 15 | 16 | # Copying source files 17 | COPY . /usr/src/app 18 | 19 | # Building app 20 | RUN npm run build 21 | 22 | # Running the app 23 | CMD ["pm2-runtime", "ecosystem.config.js"] -------------------------------------------------------------------------------- /cicd/build-pm2.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | docker build --tag simple-next-express -f ./cicd/Dockerfile-pm2 . -------------------------------------------------------------------------------- /cicd/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | docker build --tag simple-next-express -f ./cicd/Dockerfile . -------------------------------------------------------------------------------- /ecosystem.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | apps: [ 3 | { 4 | name: 'server', 5 | script: './server-register.js', 6 | instances: 2, 7 | exec_mode: 'cluster', 8 | wait_ready: true, 9 | listen_timeout: 50000, 10 | kill_timeout: 5000, 11 | env: { 12 | PM2: 'PM2', 13 | NODE_ENV: 'development' 14 | }, 15 | env_production: { 16 | PM2: 'PM2', 17 | NODE_ENV: 'production' 18 | } 19 | } 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /lib/utils/useRequest.js: -------------------------------------------------------------------------------- 1 | import useSWR from 'swr' 2 | import axios from 'axios' 3 | 4 | export default function useRequest (request, { initialData, ...config } = {}) { 5 | const { data: response, error, isValidating, mutate } = useSWR( 6 | request && JSON.stringify(request), 7 | () => axios(request || {}), 8 | { 9 | ...config, 10 | initialData: initialData 11 | } 12 | ) 13 | 14 | return { 15 | data: response && response.data, 16 | response, 17 | error, 18 | isValidating, 19 | mutate 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /next.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: {} 3 | // useFileSystemPublicRoutes: false 4 | } 5 | -------------------------------------------------------------------------------- /nodemon.json: -------------------------------------------------------------------------------- 1 | { 2 | "verbose": true, 3 | "ignore": ["node_modules", ".next"], 4 | "watch": ["server/*"], 5 | "ext": "js json" 6 | } 7 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "simple-next-express-boilerplate", 3 | "description": "Simple Next.js + Custom Express Server Boilerplate", 4 | "author": "JaeSeoKim", 5 | "version": "1.0.0", 6 | "license": "MIT", 7 | "scripts": { 8 | "dev": "nodemon --exec babel-node server/server.js", 9 | "dev:api": "nodemon --exec babel-node server/server-dev-api.js", 10 | "build": "next build", 11 | "start": "cross-env NODE_ENV=production babel-node server/server.js", 12 | "pm2": "pm2 start ecosystem.config.js --env production", 13 | "pm2:dev": "pm2 start ecosystem.config.js" 14 | }, 15 | "dependencies": { 16 | "@babel/node": "^7.10.5", 17 | "@babel/node": "^7.10.1", 18 | "@babel/register": "^7.11.5", 19 | "axios": "^0.20.0", 20 | "body-parser": "^1.19.0", 21 | "cookie-parser": "^1.4.5", 22 | "cross-env": "^7.0.2", 23 | "dotenv": "^8.2.0", 24 | "express": "^4.17.1", 25 | "next": "latest", 26 | "pm2": "^4.4.1", 27 | "react": "^16.13.1", 28 | "react-dom": "^16.13.1", 29 | "swr": "^0.3.3" 30 | }, 31 | "devDependencies": { 32 | "nodemon": "^2.0.4" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /pages/[id].js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import useReqeust from '../lib/utils/useRequest' 3 | const page = ({ id }) => { 4 | const { data, error } = useReqeust({ 5 | url: `/api/user/${id}`, 6 | method: 'GET' 7 | }) 8 | 9 | if (error) { 10 | return
Error...
11 | } 12 | 13 | if (!data) { 14 | return
Loading
15 | } 16 | return ( 17 |
18 | {id}-{data} 19 |
20 | ) 21 | } 22 | 23 | page.getInitialProps = async ({ req, res, query: { id } }) => { 24 | return { id } 25 | } 26 | 27 | export default page 28 | -------------------------------------------------------------------------------- /pages/_app.js: -------------------------------------------------------------------------------- 1 | export default function App ({ Component, pageProps }) { 2 | return 3 | } 4 | -------------------------------------------------------------------------------- /pages/_document.js: -------------------------------------------------------------------------------- 1 | import Document, { Html, Head, Main, NextScript } from 'next/document' 2 | 3 | class MyDocument extends Document { 4 | static async getInitialProps (ctx) { 5 | const initialProps = await Document.getInitialProps(ctx) 6 | return { ...initialProps } 7 | } 8 | 9 | render () { 10 | return ( 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | ) 19 | } 20 | } 21 | 22 | export default MyDocument 23 | -------------------------------------------------------------------------------- /pages/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Link from 'next/link' 3 | 4 | const home = props => { 5 | return ( 6 |
7 |

Home

8 | 9 | user - jaeseo 10 | 11 |
12 | 13 | user - unknown? 14 | 15 |
16 | ) 17 | } 18 | 19 | export default home 20 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Allow: /* 3 | 4 | Disallow: /api/* -------------------------------------------------------------------------------- /server-register.js: -------------------------------------------------------------------------------- 1 | require('@babel/register') 2 | require('./server/server') 3 | -------------------------------------------------------------------------------- /server/api/user/getUser.js: -------------------------------------------------------------------------------- 1 | export default async (req, res) => { 2 | const userData = { 3 | jaeseo: 'https://github.com/JaeSeoKim' 4 | } 5 | setTimeout(() => { 6 | if (userData[req.params.id] === undefined) { 7 | res.send('Not Found!') 8 | } else { 9 | res.send(userData[req.params.id]) 10 | } 11 | }, 1000) 12 | } 13 | -------------------------------------------------------------------------------- /server/routes/apiRouter.js: -------------------------------------------------------------------------------- 1 | import express from 'express' 2 | import cookieParser from 'cookie-parser' 3 | import bodyParser from 'body-parser' 4 | 5 | import userRouter from './userRouter' 6 | 7 | const router = express.Router() 8 | 9 | // middleware 10 | router.use(bodyParser.json()) 11 | router.use(bodyParser.urlencoded({ extended: true })) 12 | router.use(cookieParser()) 13 | 14 | // routes 15 | router.use('/user', userRouter) 16 | 17 | export default router 18 | -------------------------------------------------------------------------------- /server/routes/userRouter.js: -------------------------------------------------------------------------------- 1 | import express from 'express' 2 | import getUser from '../api/user/getUser' 3 | const router = express.Router() 4 | 5 | router.get('/:id', getUser) 6 | 7 | export default router 8 | -------------------------------------------------------------------------------- /server/server-dev-api.js: -------------------------------------------------------------------------------- 1 | import express from 'express' 2 | 3 | import apiRouter from './routes/apiRouter' 4 | 5 | const dev = process.env.NODE_ENV !== 'production' 6 | if (dev) { 7 | require('dotenv').config() 8 | } 9 | const port = parseInt(process.env.PORT, 10) || 3000 10 | 11 | const server = express() 12 | 13 | server.use('/api', apiRouter) 14 | 15 | server.listen(port, err => { 16 | if (err) throw err 17 | console.log(`> ✨Ready on http://localhost:${port}`) 18 | }) 19 | -------------------------------------------------------------------------------- /server/server.js: -------------------------------------------------------------------------------- 1 | import express from 'express' 2 | import next from 'next' 3 | import apiRouter from './routes/apiRouter' 4 | 5 | const dev = process.env.NODE_ENV !== 'production' 6 | if (dev) { 7 | require('dotenv').config() 8 | } 9 | 10 | const port = parseInt(process.env.PORT, 10) || 3000 11 | const app = next({ dev }) 12 | const handle = app.getRequestHandler() 13 | 14 | let isDisableKeepAlive = false 15 | 16 | app.prepare().then(() => { 17 | const server = express() 18 | 19 | server.use((_, res, next) => { 20 | if (isDisableKeepAlive) { 21 | res.set('Connection', 'close') 22 | } 23 | next() 24 | }) 25 | 26 | server.use('/api', apiRouter) 27 | 28 | server.all('*', (req, res) => { 29 | return handle(req, res) 30 | }) 31 | 32 | const appServer = server.listen(port, err => { 33 | if (err) throw err 34 | if (process.env.PM2 === 'PM2') process.send('ready') // for pm2 35 | console.log(`> ✨Ready on http://localhost:${port}`) 36 | }) 37 | 38 | process.on('SIGINT', () => { 39 | isDisableKeepAlive = true 40 | appServer.close(err => { 41 | console.log('> 😢 Server closed') 42 | process.exit(err ? 1 : 0) 43 | }) 44 | }) 45 | }) 46 | --------------------------------------------------------------------------------