├── .dockerignore ├── .env.example ├── .prettierrc ├── routes ├── index.js └── api │ ├── auth.js │ └── users.js ├── Dockerfile ├── config ├── auth.js └── mongoose.js ├── .eslintrc ├── LICENSE ├── index.js ├── package.json ├── .gitignore ├── models └── User.js └── README.md /.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | BASE_URL=http://localhost:8080 2 | MONGODB_URI=mongodb://localhost:27017/nodejs-starter 3 | JWT_SECRET=secret -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 100, 3 | "tabWidth": 2, 4 | "semi": true, 5 | "singleQuote": true, 6 | "trailingComma": "es5" 7 | } 8 | -------------------------------------------------------------------------------- /routes/index.js: -------------------------------------------------------------------------------- 1 | const router = require('express').Router(); 2 | 3 | router.use('/api/auth', require('./api/auth')); 4 | router.use('/api/users', require('./api/users')); 5 | 6 | module.exports = router; 7 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:10 2 | 3 | # Create app directory 4 | WORKDIR /usr/src/app 5 | 6 | # Install app dependencies 7 | # A wildcard is used to ensure both package.json AND package-lock.json are copied 8 | # where available (npm@5+) 9 | COPY package*.json ./ 10 | 11 | RUN npm install 12 | # If you are building your code for production 13 | # RUN npm ci --only=production 14 | 15 | # Bundle app source 16 | COPY . . 17 | 18 | EXPOSE 8080 19 | CMD [ "node", "index.js" ] -------------------------------------------------------------------------------- /config/auth.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Auth middleware 3 | */ 4 | const jwt = require('jsonwebtoken'); 5 | const User = require('../models/User'); 6 | 7 | const auth = async (req, res, next) => { 8 | try { 9 | const token = req.header('Authorization').replace('Bearer ', ''); 10 | const decoded = jwt.verify(token, process.env.JWT_SECRET); 11 | const user = await User.findOne({ 12 | _id: decoded._id, 13 | 'tokens.token': token, 14 | }); 15 | if (!user) throw new Error(); 16 | req.token = token; 17 | req.user = user; 18 | next(); 19 | } catch (e) { 20 | res.status(401).send({ error: 'Please authenticate.' }); 21 | } 22 | }; 23 | 24 | module.exports = auth; 25 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "commonjs": true, 4 | "es6": true, 5 | "node": true 6 | }, 7 | "extends": ["airbnb", "prettier", "plugin:node/recommended"], 8 | "plugins": ["prettier"], 9 | "globals": { 10 | "Atomics": "readonly", 11 | "SharedArrayBuffer": "readonly" 12 | }, 13 | "parserOptions": { 14 | "ecmaVersion": 2018 15 | }, 16 | "rules": { 17 | "prettier/prettier": "error", 18 | "no-unused-vars": "warn", 19 | "no-console": "off", 20 | "func-names": "off", 21 | "no-process-exit": "off", 22 | "no-underscore-dangle": [2, { "allow": ["__v", "_id"] }], 23 | "object-shorthand": "off", 24 | "class-methods-use-this": "off" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /config/mongoose.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | const chalk = require('chalk'); 3 | 4 | const connectDB = async () => { 5 | try { 6 | // MongoDB setup. 7 | mongoose.set('useFindAndModify', false); 8 | mongoose.set('useCreateIndex', true); 9 | mongoose.set('useNewUrlParser', true); 10 | mongoose.set('useUnifiedTopology', true); 11 | await mongoose.connect(process.env.MONGODB_URI); 12 | } catch (e) { 13 | console.error(e.message); 14 | console.log( 15 | '%s MongoDB connection error. Please make sure MongoDB is running.', 16 | chalk.red('✗') 17 | ); 18 | // Exit process with failure 19 | process.exit(1); 20 | } 21 | }; 22 | 23 | module.exports = connectDB; 24 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 George Simos 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Author: George Simos - georgesimos.com 4 | * 5 | * License: Copyright (c) 2019 George Simos 6 | * @link https://github.com/georgesimos/nodejs-starter 7 | * 8 | */ 9 | 10 | const express = require('express'); 11 | const logger = require('morgan'); 12 | const dotenv = require('dotenv'); 13 | const expressStatusMonitor = require('express-status-monitor'); 14 | const connectDB = require('./config/mongoose'); 15 | const routes = require('./routes'); 16 | 17 | // Make all variables from our .env file available in our process 18 | dotenv.config({ path: '.env.example' }); 19 | 20 | // Init express server 21 | const app = express(); 22 | 23 | // Connect to MongoDB. 24 | connectDB(); 25 | 26 | // Middlewares & configs setup 27 | app.use(logger('dev')); 28 | app.use(express.json()); 29 | app.use(express.urlencoded({ extended: true })); 30 | 31 | app.disable('x-powered-by'); 32 | app.use(expressStatusMonitor()); 33 | app.use((req, res, next) => { 34 | res.locals.user = req.user; 35 | next(); 36 | }); 37 | 38 | // Here we define the api routes 39 | app.use(routes); 40 | 41 | const port = process.env.PORT || 8080; 42 | const address = process.env.SERVER_ADDRESS || 'localhost'; 43 | 44 | app.get('/', (req, res) => res.send('Hello World!')); 45 | 46 | app.listen(port, () => console.log(`Server running on http://${address}:${port}`)); 47 | -------------------------------------------------------------------------------- /routes/api/auth.js: -------------------------------------------------------------------------------- 1 | const router = require('express').Router(); 2 | const User = require('../../models/User'); 3 | const auth = require('../../config/auth'); 4 | 5 | /** 6 | * @route POST /auth/login 7 | * @desc Login a user 8 | * @access Public 9 | */ 10 | router.post('/login', async (req, res) => { 11 | try { 12 | const { email, password } = req.body; 13 | const user = await User.findByCredentials(email, password); 14 | const token = await user.generateAuthToken(); 15 | res.send({ user, token }); 16 | } catch (e) { 17 | res.status(400).send({ 18 | error: { message: 'You have entered an invalid email or password' }, 19 | }); 20 | } 21 | }); 22 | 23 | /** 24 | * @route POST /auth/logout 25 | * @desc Logout a user 26 | * @access Private 27 | */ 28 | router.post('/logout', auth, async (req, res) => { 29 | const { user } = req; 30 | try { 31 | user.tokens = user.tokens.filter(token => { 32 | return token.token !== req.token; 33 | }); 34 | await user.save(); 35 | res.send({ message: 'You have successfully logged out!' }); 36 | } catch (e) { 37 | res.status(400).send(e); 38 | } 39 | }); 40 | 41 | /** 42 | * @route POST /auth/logoutAll 43 | * @desc Logout a user from all devices 44 | * @access Private 45 | */ 46 | router.post('/logoutAll', auth, async (req, res) => { 47 | try { 48 | req.user.tokens = []; 49 | await req.user.save(); 50 | res.send({ message: 'You have successfully logged out!' }); 51 | } catch (e) { 52 | res.status(400).send(e); 53 | } 54 | }); 55 | module.exports = router; 56 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nodejs-starter", 3 | "version": "1.0.0", 4 | "description": "Nodejs Starter - A boilerplate for Node.js web applications", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "node index.js", 8 | "dev": "nodemon start", 9 | "test": "echo \"Error: no test specified\" && exit 1", 10 | "format": "prettier-eslint \"**/*.{js,json}\" --write", 11 | "lint": "eslint \"**/*.js\"" 12 | }, 13 | "repository": { 14 | "type": "git", 15 | "url": "git+https://github.com/georgesimos/nodejs-starter.git" 16 | }, 17 | "keywords": [], 18 | "author": "George Simos", 19 | "license": "MIT", 20 | "bugs": { 21 | "url": "https://github.com/georgesimos/nodejs-starter/issues" 22 | }, 23 | "homepage": "https://github.com/georgesimos/nodejs-starter#readme", 24 | "dependencies": { 25 | "bcryptjs": "^2.4.3", 26 | "chalk": "^3.0.0", 27 | "dotenv": "^8.2.0", 28 | "express": "^4.17.1", 29 | "express-status-monitor": "^1.2.7", 30 | "jsonwebtoken": "^8.5.1", 31 | "mongoose": "^5.8.3", 32 | "morgan": "^1.9.1" 33 | }, 34 | "devDependencies": { 35 | "eslint": "^6.1.0", 36 | "eslint-config-airbnb": "^18.0.1", 37 | "eslint-config-node": "^4.0.0", 38 | "eslint-config-prettier": "^6.8.0", 39 | "eslint-plugin-import": "^2.19.1", 40 | "eslint-plugin-jsx-a11y": "^6.2.3", 41 | "eslint-plugin-node": "^11.0.0", 42 | "eslint-plugin-prettier": "^3.1.2", 43 | "eslint-plugin-react": "^7.17.0", 44 | "eslint-plugin-react-hooks": "^1.7.0", 45 | "nodemon": "^2.0.2", 46 | "prettier": "^1.19.1" 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | *.lcov 24 | 25 | # nyc test coverage 26 | .nyc_output 27 | 28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 29 | .grunt 30 | 31 | # Bower dependency directory (https://bower.io/) 32 | bower_components 33 | 34 | # node-waf configuration 35 | .lock-wscript 36 | 37 | # Compiled binary addons (https://nodejs.org/api/addons.html) 38 | build/Release 39 | 40 | # Dependency directories 41 | node_modules/ 42 | jspm_packages/ 43 | 44 | # TypeScript v1 declaration files 45 | typings/ 46 | 47 | # TypeScript cache 48 | *.tsbuildinfo 49 | 50 | # Optional npm cache directory 51 | .npm 52 | 53 | # Optional eslint cache 54 | .eslintcache 55 | 56 | # Microbundle cache 57 | .rpt2_cache/ 58 | .rts2_cache_cjs/ 59 | .rts2_cache_es/ 60 | .rts2_cache_umd/ 61 | 62 | # Optional REPL history 63 | .node_repl_history 64 | 65 | # Output of 'npm pack' 66 | *.tgz 67 | 68 | # Yarn Integrity file 69 | .yarn-integrity 70 | 71 | # dotenv environment variables file 72 | .env 73 | .env.test 74 | 75 | # parcel-bundler cache (https://parceljs.org/) 76 | .cache 77 | 78 | # Next.js build output 79 | .next 80 | 81 | # Nuxt.js build / generate output 82 | .nuxt 83 | dist 84 | 85 | # Gatsby files 86 | .cache/ 87 | # Comment in the public line in if your project uses Gatsby and *not* Next.js 88 | # https://nextjs.org/blog/next-9-1#public-directory-support 89 | # public 90 | 91 | # vuepress build output 92 | .vuepress/dist 93 | 94 | # Serverless directories 95 | .serverless/ 96 | 97 | # FuseBox cache 98 | .fusebox/ 99 | 100 | # DynamoDB Local files 101 | .dynamodb/ 102 | 103 | # TernJS port file 104 | .tern-port 105 | -------------------------------------------------------------------------------- /models/User.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | const bcrypt = require('bcryptjs'); 3 | const jwt = require('jsonwebtoken'); 4 | 5 | const { Schema } = mongoose; 6 | 7 | const userSchema = Schema( 8 | { 9 | name: String, 10 | email: { type: String, unique: true }, 11 | password: String, 12 | role: { 13 | type: String, 14 | default: 'guest', 15 | enum: ['guest', 'admin', 'superadmin'], 16 | }, 17 | tokens: [{ token: { type: String, required: true } }], 18 | }, 19 | { timestamps: true } 20 | ); 21 | 22 | /** 23 | * Password hash middleware. 24 | */ 25 | userSchema.pre('save', async function(next) { 26 | const user = this; 27 | if (user.isModified('password')) { 28 | const salt = await bcrypt.genSalt(10); 29 | user.password = await bcrypt.hash(user.password, salt); 30 | } 31 | next(); 32 | }); 33 | 34 | /** 35 | * Hide properties of Mongoose User object. 36 | */ 37 | userSchema.methods.toJSON = function() { 38 | const user = this; 39 | const userObject = user.toObject(); 40 | if (!userObject.role === 'superadmin') { 41 | delete userObject.updatedAt; 42 | delete userObject.__v; 43 | } 44 | delete userObject.password; 45 | delete userObject.tokens; 46 | 47 | return userObject; 48 | }; 49 | 50 | /** 51 | * Helper method for generating Auth Token 52 | */ 53 | userSchema.methods.generateAuthToken = async function() { 54 | const user = this; 55 | const token = jwt.sign({ _id: user._id.toString() }, process.env.JWT_SECRET, { expiresIn: '1h' }); 56 | user.tokens = user.tokens.concat({ token }); 57 | await user.save(); 58 | return token; 59 | }; 60 | 61 | /** 62 | * Helper static method for finding user by credentials 63 | */ 64 | userSchema.statics.findByCredentials = async function(email, password) { 65 | const User = this; 66 | const user = await User.findOne({ email }); 67 | if (!user) throw new Error('Unable to login'); 68 | 69 | const isMatch = await bcrypt.compare(password, user.password); 70 | if (!isMatch) throw new Error('Unable to login'); 71 | 72 | return user; 73 | }; 74 | 75 | const User = mongoose.model('User', userSchema); 76 | 77 | module.exports = User; 78 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | nodejs-starter 3 |

4 |

5 | MongoDB, Expressjs, Nodejs 6 |

7 | 8 | [![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/georgesimos/nodejs-starter/blob/master/LICENSE) 9 | 10 | Nodejs Starter is a boilerplate for Node.js web applications built with: 11 | 12 | - [MongoDB](https://www.mongodb.com/) - A document-oriented, No-SQL database used to store the application data. 13 | - [ExpressJS](https://expressjs.com/) - fast node.js network app framework. 14 | - [nodeJS](https://nodejs.org/) - A JavaScript runtime built on Chrome's V8 JavaScript engine 15 | - Authentication with [jsonwebtoken](https://github.com/auth0/node-jsonwebtoken) 16 | 17 | ## Features 18 | 19 | - **Authentication** using Email and Password 20 | - Add / Update / Delete Users 21 | 22 | ## Getting Started 23 | 24 | clone the repository 25 | 26 | ```sh 27 | $ git clone https://github.com/georgesimos/nodejs-starter.git myproject 28 | $ cd myproject 29 | ``` 30 | 31 | Install the dependencies and devDependencies 32 | 33 | ```sh 34 | $ npm install 35 | ``` 36 | 37 | Set environment variables 38 | 39 | ```sh 40 | cp .env.example .env 41 | ``` 42 | 43 | Start the server 44 | 45 | ```sh 46 | $ npm start 47 | $ npm run dev # with nodemon live update 48 | ``` 49 | 50 | ## Application Structure 51 | 52 | ``` 53 | app 54 | ├── config 55 | │ └── auth.js 56 | │ └── mongoose.js 57 | ├── models 58 | │ └── Users 59 | ├── routes 60 | │ └── api 61 | │ │ └── auth.js 62 | │ │ └── users.js 63 | │ └── index.js 64 | └── index.js 65 | ``` 66 | 67 | - index.js - The application entry point. This file defines our express server and connects it to MongoDB using mongoose. It also defines the api routes. 68 | - config/ - This folder contains configuration for mongoose and Auth middleware. 69 | - models/ - This folder contains the Schema definitions for our Mongoose Models. 70 | - routes/ - This folder contains the route definitions for our API. 71 | 72 | 73 | 74 | ## Plugins 75 | 76 | nodejs-starter is currently extended with the following plugins. Instructions on how to use them in your own application are linked below. 77 | 78 | ## Server 79 | 80 | | Plugin | README | 81 | | ---------------------- | ------------------------------------------------------------------------------------------------------------------------- | 82 | | bcryptjs | [plugins/bcryptjs/README.md](https://github.com/dcodeIO/bcrypt.js/blob/master/README.md) | 83 | | chalk | [plugins/chalk/README.md](https://github.com/chalk/chalk/blob/master/readme.md) | 84 | | dotenv | [plugins/dotenv/README.md](https://github.com/motdotla/dotenv/blob/master/README.md) | 85 | | express | [plugins/express/README.md](https://github.com/expressjs/express/blob/master/Readme.md) | 86 | | express-status-monitor | [plugins/express-status-monitor/README.md](https://github.com/RafalWilinski/express-status-monitor/blob/master/README.md) | 87 | | jsonwebtoken | [plugins/jsonwebtoken/README.md](https://github.com/auth0/node-jsonwebtoken/blob/master/README.md) | 88 | | mongoose | [plugins/mongoose/README.md](https://github.com/Automattic/mongoose/blob/master/README.md) | 89 | | morgan | [plugins/morgan/README.md](https://github.com/expressjs/morgan/blob/master/README.md) | 90 | | nodemon | [plugins/nodemon/README.md](https://github.com/remy/nodemon/blob/master/README.md) | 91 | 92 | ## License 93 | 94 | MIT 95 | -------------------------------------------------------------------------------- /routes/api/users.js: -------------------------------------------------------------------------------- 1 | const router = require('express').Router(); 2 | const User = require('../../models/User'); 3 | const auth = require('../../config/auth'); 4 | 5 | /** 6 | * @route POST /users 7 | * @desc Register new user 8 | * @access Public 9 | */ 10 | router.post('/', async (req, res) => { 11 | const user = new User(req.body); 12 | try { 13 | await user.save(); 14 | const token = await user.generateAuthToken(); 15 | res.status(201).send({ user, token }); 16 | } catch (e) { 17 | res.status(400).send(e); 18 | } 19 | }); 20 | 21 | /** 22 | * @route GET /users 23 | * @desc Get all users 24 | * @access Private 25 | */ 26 | router.get('/', auth, async (req, res) => { 27 | try { 28 | const users = await User.find({}); 29 | res.send(users); 30 | } catch (e) { 31 | res.status(400).send(e); 32 | } 33 | }); 34 | 35 | /** 36 | * @route GET /users/me 37 | * @desc Get logged in user details 38 | * @access Private 39 | */ 40 | router.get('/me', auth, async (req, res) => { 41 | try { 42 | res.send(req.user); 43 | } catch (e) { 44 | res.status(400).send(e); 45 | } 46 | }); 47 | 48 | /** 49 | * @route GET /users/:id 50 | * @desc Get user by id 51 | * @access Private 52 | */ 53 | router.get('/:id', auth, async (req, res) => { 54 | try { 55 | const { id } = req.params; 56 | const user = await User.findById(id); 57 | return !user ? res.sendStatus(404) : res.send(user); 58 | } catch (e) { 59 | return res.sendStatus(400); 60 | } 61 | }); 62 | 63 | /** 64 | * @route PATCH /users/me 65 | * @desc Update logged in user 66 | * @access Private 67 | */ 68 | router.patch('/me', auth, async (req, res) => { 69 | const validationErrors = []; 70 | const updates = Object.keys(req.body); 71 | const allowedUpdates = ['name', 'email', 'password', 'role']; 72 | const isValidOperation = updates.every(update => { 73 | const isValid = allowedUpdates.includes(update); 74 | if (!isValid) validationErrors.push(update); 75 | return isValid; 76 | }); 77 | 78 | if (!isValidOperation) 79 | return res.status(400).send({ error: `Invalid updates: ${validationErrors.join(',')}` }); 80 | 81 | try { 82 | const { user } = req; 83 | updates.forEach(update => { 84 | user[update] = req.body[update]; 85 | }); 86 | 87 | await user.save(); 88 | return res.send(user); 89 | } catch (e) { 90 | return res.status(400).send(e); 91 | } 92 | }); 93 | 94 | /** 95 | * @route PATCH /users/:id 96 | * @desc Update user by id 97 | * @access Private 98 | */ 99 | router.patch('/:id', auth, async (req, res) => { 100 | const validationErrors = []; 101 | const updates = Object.keys(req.body); 102 | const allowedUpdates = ['name', 'email', 'password', 'role']; 103 | const isValidOperation = updates.every(update => { 104 | const isValid = allowedUpdates.includes(update); 105 | if (!isValid) validationErrors.push(update); 106 | return isValid; 107 | }); 108 | 109 | if (!isValidOperation) 110 | return res.status(400).send({ error: `Invalid updates: ${validationErrors.join(',')}` }); 111 | 112 | try { 113 | const _id = req.params.id; 114 | const user = await User.findById(_id); 115 | if (!user) return res.sendStatus(404); 116 | updates.forEach(update => { 117 | user[update] = req.body[update]; 118 | }); 119 | await user.save(); 120 | 121 | return res.send(user); 122 | } catch (e) { 123 | return res.status(400).send(e); 124 | } 125 | }); 126 | 127 | /** 128 | * @route DELETE /users/me 129 | * @desc Delete logged in user 130 | * @access Private 131 | */ 132 | router.delete('/me', auth, async (req, res) => { 133 | try { 134 | await req.user.remove(); 135 | res.send({ message: 'User Deleted' }); 136 | } catch (e) { 137 | res.sendStatus(400); 138 | } 139 | }); 140 | 141 | /** 142 | * @route DELETE /users/:id 143 | * @desc Delete user by id 144 | * @access Private 145 | */ 146 | router.delete('/:id', auth, async (req, res) => { 147 | const _id = req.params.id; 148 | try { 149 | const user = await User.findByIdAndDelete(_id); 150 | if (!user) return res.sendStatus(404); 151 | 152 | return res.send({ message: 'User Deleted' }); 153 | } catch (e) { 154 | return res.sendStatus(400); 155 | } 156 | }); 157 | 158 | module.exports = router; 159 | --------------------------------------------------------------------------------