├── .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 |
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 |
--------------------------------------------------------------------------------