├── .env.example ├── .gitignore ├── README.md ├── app ├── config │ └── config.js ├── controllers │ ├── auth.controller.js │ ├── book.controller.js │ └── user.controller.js ├── middlewares │ ├── authJwt.js │ ├── index.js │ └── verifySignUp.js ├── models │ ├── book.model.js │ ├── index.js │ ├── role.model.js │ └── user.model.js └── routes │ ├── auth.routes.js │ ├── book.routes.js │ └── user.routes.js ├── package-lock.json ├── package.json └── server.js /.env.example: -------------------------------------------------------------------------------- 1 | NODE_ENV=development 2 | PORT=8080 3 | 4 | # Database 5 | DB_HOST=your-db-host 6 | DB_USER=your-db-username 7 | DB_PASS=your-db-password 8 | DB_NAME=your-db-name -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | node_modules 4 | .env -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # REST API using Node.js, Express, Sequelize and MySQL + JWT Authentication and Authorization 2 | 3 | ## Getting Started 4 | 5 | 1. Clone this repository 6 | 7 | ```bash 8 | git clone https://github.com/indraarianggi/nodejs-sequelize-mysql-api.git 9 | cd nodejs-sequelize-mysql-api 10 | ``` 11 | 12 | 2. Install the npm packages 13 | 14 | ```bash 15 | npm install 16 | ``` 17 | 18 | Also install `nodemon` globally, if you don't have it yet. 19 | 20 | ```bash 21 | npm install -g nodemon 22 | ``` 23 | 24 | 3. Congfigure environment settings 25 | 26 | Create a file with the following name and location `.env` and copy the contents from `.env.example` into it. Replace the values with your specific configuration. Don't worry, this file is in the `.gitignore` so it won't get pushed to github. 27 | 28 | ```javasscript 29 | NODE_ENV=development 30 | PORT=8080 31 | 32 | # Database 33 | DB_HOST=your-db-host 34 | DB_USER=your-db-username 35 | DB_PASS=your-db-password 36 | DB_NAME=your-db-name 37 | ``` 38 | 39 | 4. Running the app locally 40 | 41 | Run this command, which is located in npm script in `package.json` file. 42 | 43 | ```bash 44 | npm run dev 45 | ``` 46 | 47 | ## Resources 48 | 49 | 1. [Node.js Rest APIs example with Express, Sequelize & MySQL](https://bezkoder.com/node-js-express-sequelize-mysql/) 50 | 51 | 2. [Node.js – JWT Authentication & Authorization with JSONWebToken example](https://bezkoder.com/node-js-jwt-authentication-mysql/) 52 | 53 | 3. [In-depth Introduction to JWT-JSON Web Token](https://bezkoder.com/jwt-json-web-token/) 54 | 55 | 4. [Sequelize Documentation](https://sequelize.org/master/) 56 | 57 | 5. [Getting Started with Node, Express and Mysql Using Sequelize](https://medium.com/@prajramesh93/getting-started-with-node-express-and-mysql-using-sequelize-ed1225afc3e0) 58 | 59 | 6. [Hidup Mudah dengan Sequelize: ORM Untuk Aplikasi NodeJS](https://refactory.id/post/91-hidup-mudah-dengan-sequelize-orm-untuk-aplikasi-nodejs) 60 | 61 | 7. [Node.js Everywhere with Environment Variables!](https://medium.com/the-node-js-collection/making-your-node-js-work-everywhere-with-environment-variables-2da8cdf6e786) 62 | -------------------------------------------------------------------------------- /app/config/config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | NODE_ENV: process.env.NODE_ENV, 3 | PORT: process.env.PORT, 4 | 5 | /** DATABASE */ 6 | db: { 7 | DB_HOST: process.env.DB_HOST, 8 | DB_USER: process.env.DB_USER, 9 | DB_PASS: process.env.DB_PASS, 10 | DB_NAME: process.env.DB_NAME, 11 | dialect: "mysql", 12 | 13 | // pool is optional, it will be used for Sequelize connection pool configuration 14 | pool: { 15 | max: 5, 16 | min: 0, 17 | acquire: 30000, 18 | idle: 10000 19 | } 20 | }, 21 | 22 | /** AUTH KEY */ 23 | auth: { 24 | secret: "our-secret-key" 25 | } 26 | }; 27 | -------------------------------------------------------------------------------- /app/controllers/auth.controller.js: -------------------------------------------------------------------------------- 1 | const config = require("../config/config"); 2 | const jwt = require("jsonwebtoken"); 3 | const bcrypt = require("bcryptjs"); 4 | const db = require("../models"); 5 | const User = db.user; 6 | const Role = db.role; 7 | const Op = db.Op; 8 | 9 | exports.signup = (req, res) => { 10 | // Save user to database 11 | User.create({ 12 | username: req.body.username, 13 | email: req.body.email, 14 | password: bcrypt.hashSync(req.body.password, 8) 15 | }) 16 | .then(user => { 17 | if (req.body.roles) { 18 | Role.findAll({ 19 | where: { 20 | name: { 21 | [Op.or]: req.body.roles 22 | } 23 | } 24 | }).then(roles => { 25 | user.setRoles(roles).then(() => { 26 | res.send({ message: "User was registered successfully!" }); 27 | }); 28 | }); 29 | } else { 30 | // User role 1 31 | user.setRoles([1]).then(() => { 32 | res.send({ message: "User was registered successfully!" }); 33 | }); 34 | } 35 | }) 36 | .catch(err => { 37 | res.status(500).send({ message: err.message }); 38 | }); 39 | }; 40 | 41 | exports.signin = (req, res) => { 42 | User.findOne({ 43 | where: { 44 | username: req.body.username 45 | } 46 | }) 47 | .then(user => { 48 | if (!user) { 49 | return res.status(404).send({ message: "User Not found." }); 50 | } 51 | 52 | let passwordIsValid = bcrypt.compareSync( 53 | req.body.password, 54 | user.password 55 | ); 56 | 57 | if (!passwordIsValid) { 58 | return res.status(401).send({ 59 | accessToken: null, 60 | message: "Invalid Password!" 61 | }); 62 | } 63 | 64 | let token = jwt.sign({ id: user.id }, config.auth.secret, { 65 | expiresIn: 86400 // 24 hours 66 | }); 67 | 68 | let authorities = []; 69 | user.getRoles().then(roles => { 70 | for (let i = 0; i < roles.length; i++) { 71 | authorities.push("ROLE_" + roles[i].name.toUpperCase()); 72 | } 73 | 74 | res.status(200).send({ 75 | id: user.id, 76 | username: user.username, 77 | email: user.email, 78 | roles: authorities, 79 | accessToken: token 80 | }); 81 | }); 82 | }) 83 | .catch(err => { 84 | res.status(500).send({ message: err.message }); 85 | }); 86 | }; 87 | -------------------------------------------------------------------------------- /app/controllers/book.controller.js: -------------------------------------------------------------------------------- 1 | const db = require("../models"); 2 | const Book = db.books; 3 | const Op = db.Op; 4 | 5 | // Create and Save a new Book 6 | exports.create = (req, res) => { 7 | // Validate request 8 | if (!req.body.title) { 9 | res.status(400).send({ 10 | message: "Content can not be empty!" 11 | }); 12 | return; 13 | } 14 | 15 | // Create a Book 16 | const book = { 17 | title: req.body.title, 18 | author: req.body.author, 19 | published: req.body.published ? req.body.published : false 20 | }; 21 | 22 | // Save Book in database 23 | Book.create(book) 24 | .then(data => { 25 | res.send(data); 26 | }) 27 | .catch(err => { 28 | res.status(500).send({ 29 | message: err.message || "Some error occurred while creating the Book." 30 | }); 31 | }); 32 | }; 33 | 34 | // Retrieve all Books from the database. 35 | exports.findAll = (req, res) => { 36 | const title = req.query.title; 37 | var condition = title ? { title: { [Op.like]: `%${title}%` } } : null; 38 | 39 | Book.findAll({ where: condition }) 40 | .then(data => { 41 | res.send(data); 42 | }) 43 | .catch(err => { 44 | res.send(500).send({ 45 | message: err.message || "Some error accurred while retrieving books." 46 | }); 47 | }); 48 | }; 49 | 50 | // Find a single Book with an id 51 | exports.findOne = (req, res) => { 52 | const id = req.params.id; 53 | 54 | Book.findByPk(id) 55 | .then(data => { 56 | res.send(data); 57 | }) 58 | .catch(err => { 59 | res.status(500).send({ 60 | message: `Error retrieving Book with id = ${id}` 61 | }); 62 | }); 63 | }; 64 | 65 | // Update a Book by the id in the request 66 | exports.update = (req, res) => { 67 | const id = req.params.id; 68 | 69 | Book.update(req.body, { 70 | where: { id: id } 71 | }) 72 | .then(num => { 73 | if (num == 1) { 74 | res.send({ 75 | message: "Book was updated successfully." 76 | }); 77 | } else { 78 | res.send({ 79 | message: `Cannot update Book with id=${id}. Maybe Book was not found or req.body is empty!` 80 | }); 81 | } 82 | }) 83 | .catch(err => { 84 | res.status(500).send({ 85 | message: "Error updating Book with id=" + id 86 | }); 87 | }); 88 | }; 89 | 90 | // Delete a Book with the specified id in the request 91 | exports.delete = (req, res) => { 92 | const id = req.params.id; 93 | 94 | Book.destroy({ 95 | where: { id: id } 96 | }) 97 | .then(num => { 98 | if (num == 1) { 99 | res.send({ 100 | message: "Book was deleted successfully!" 101 | }); 102 | } else { 103 | res.send({ 104 | message: `Cannot delete Book with id=${id}. Maybe Book was not found!` 105 | }); 106 | } 107 | }) 108 | .catch(err => { 109 | res.status(500).send({ 110 | message: "Could not delete Book with id=" + id 111 | }); 112 | }); 113 | }; 114 | 115 | // Delete all Books from the database. 116 | exports.deleteAll = (req, res) => { 117 | Book.destroy({ 118 | where: {}, 119 | truncate: false 120 | }) 121 | .then(nums => { 122 | res.send({ message: `${nums} Books were deleted successfully!` }); 123 | }) 124 | .catch(err => { 125 | res.status(500).send({ 126 | message: err.message || "Some error occurred while removing all books." 127 | }); 128 | }); 129 | }; 130 | 131 | // Find all published Books 132 | exports.findAllPublished = (req, res) => { 133 | Book.findAll({ where: { published: true } }) 134 | .then(data => { 135 | res.send(data); 136 | }) 137 | .catch(err => { 138 | res.status(500).send({ 139 | message: err.message || "Some error occurred while retrieving books." 140 | }); 141 | }); 142 | }; 143 | -------------------------------------------------------------------------------- /app/controllers/user.controller.js: -------------------------------------------------------------------------------- 1 | exports.allAccess = (req, res) => { 2 | res.status(200).send("Public Content."); 3 | }; 4 | 5 | exports.userBoard = (req, res) => { 6 | res.status(200).send("User Content."); 7 | }; 8 | 9 | exports.adminBoard = (req, res) => { 10 | res.status(200).send("Admin Content."); 11 | }; 12 | 13 | exports.moderatorBoard = (req, res) => { 14 | res.status(200).send("Moderator Content."); 15 | }; 16 | -------------------------------------------------------------------------------- /app/middlewares/authJwt.js: -------------------------------------------------------------------------------- 1 | const jwt = require("jsonwebtoken"); 2 | const config = require("../config/config.js"); 3 | const db = require("../models"); 4 | const User = db.user; 5 | 6 | verifyToken = (req, res, next) => { 7 | let token = req.headers["x-access-token"]; 8 | 9 | if (!token) { 10 | return res.status(403).send({ 11 | message: "No token provided!" 12 | }); 13 | } 14 | 15 | jwt.verify(token, config.auth.secret, (err, decoded) => { 16 | if (err) { 17 | return res.status(401).send({ 18 | message: "Unauthorized!" 19 | }); 20 | } 21 | 22 | req.userId = decoded.id; 23 | 24 | next(); 25 | }); 26 | }; 27 | 28 | isAdmin = (req, res, next) => { 29 | User.findByPk(req.userId).then(user => { 30 | user.getRoles().then(roles => { 31 | for (let i = 0; i < roles.lenth; i++) { 32 | if (roles[i].name === "admin") { 33 | next(); 34 | return; 35 | } 36 | } 37 | 38 | res.status(403).send({ 39 | message: "Require Admin Role!" 40 | }); 41 | return; 42 | }); 43 | }); 44 | }; 45 | 46 | isModerator = (req, res, next) => { 47 | User.findByPk(req.userId).then(user => { 48 | user.getRoles().then(roles => { 49 | for (let i = 0; i < roles.length; i++) { 50 | if (roles[i].name === "moderator") { 51 | next(); 52 | return; 53 | } 54 | } 55 | 56 | res.status(403).send({ 57 | message: "Require Moderator Role!" 58 | }); 59 | }); 60 | }); 61 | }; 62 | 63 | isModeratorOrAdmin = (req, res, next) => { 64 | User.findByPk(req.userId).then(user => { 65 | user.getRoles().then(roles => { 66 | for (let i = 0; i < roles.length; i++) { 67 | if (roles[i].name === "moderator") { 68 | next(); 69 | return; 70 | } 71 | 72 | if (roles[i].name === "admin") { 73 | next(); 74 | return; 75 | } 76 | } 77 | 78 | res.status(403).send({ 79 | message: "Require Moderator or Admin Role!" 80 | }); 81 | }); 82 | }); 83 | }; 84 | 85 | const authJwt = { 86 | verifyToken: verifyToken, 87 | isAdmin: isAdmin, 88 | isModerator: isModerator, 89 | isModeratorOrAdmin: isModeratorOrAdmin 90 | }; 91 | 92 | module.exports = authJwt; 93 | -------------------------------------------------------------------------------- /app/middlewares/index.js: -------------------------------------------------------------------------------- 1 | const authJwt = require("./authJwt"); 2 | const verifySignUp = require("./verifySignUp"); 3 | 4 | module.exports = { 5 | authJwt, 6 | verifySignUp 7 | }; 8 | -------------------------------------------------------------------------------- /app/middlewares/verifySignUp.js: -------------------------------------------------------------------------------- 1 | const db = require("../models"); 2 | const ROLES = db.ROLES; 3 | const User = db.user; 4 | 5 | checkDuplicateUsernameOrEmail = (req, res, next) => { 6 | // Username 7 | User.findOne({ 8 | where: { 9 | username: req.body.username 10 | } 11 | }).then(user => { 12 | if (user) { 13 | res.status(400).send({ 14 | message: "Failed! Username is already in use!" 15 | }); 16 | return; 17 | } 18 | 19 | // Email 20 | User.findOne({ 21 | where: { 22 | email: req.body.email 23 | } 24 | }).then(user => { 25 | if (user) { 26 | res.status(400).send({ 27 | message: "Failed! Email is already in use!" 28 | }); 29 | return; 30 | } 31 | 32 | next(); 33 | }); 34 | }); 35 | }; 36 | 37 | checkRolesExisted = (req, res, next) => { 38 | if (req.body.roles) { 39 | for (let i = 0; i < req.body.roles.length; i++) { 40 | if (!ROLES.includes(req.body.roles[i])) { 41 | res.status(400).send({ 42 | message: "Failed! Role does not exist = " + req.body.roles[i] 43 | }); 44 | return; 45 | } 46 | } 47 | } 48 | 49 | next(); 50 | }; 51 | 52 | const verifySignUp = { 53 | checkDuplicateUsernameOrEmail: checkDuplicateUsernameOrEmail, 54 | checkRolesExisted: checkRolesExisted 55 | }; 56 | 57 | module.exports = verifySignUp; 58 | -------------------------------------------------------------------------------- /app/models/book.model.js: -------------------------------------------------------------------------------- 1 | module.exports = (sequelize, Sequelize, DataTypes) => { 2 | const Book = sequelize.define( 3 | "book", // Model name 4 | { 5 | // Model attributes 6 | id: { 7 | type: DataTypes.INTEGER, 8 | allowNull: false, 9 | autoIncrement: true, 10 | primaryKey: true 11 | }, 12 | title: { 13 | type: DataTypes.STRING 14 | }, 15 | author: { 16 | type: DataTypes.STRING 17 | }, 18 | published: { 19 | type: DataTypes.BOOLEAN 20 | }, 21 | created_at: { 22 | allowNull: false, 23 | type: DataTypes.DATE 24 | }, 25 | updated_at: { 26 | allowNull: false, 27 | type: DataTypes.DATE 28 | } 29 | }, 30 | { 31 | // Options 32 | timestamps: true, 33 | underscrored: true, 34 | createdAt: "created_at", 35 | updatedAt: "updated_at" 36 | } 37 | ); 38 | 39 | return Book; 40 | }; 41 | -------------------------------------------------------------------------------- /app/models/index.js: -------------------------------------------------------------------------------- 1 | const config = require("../config/config.js"); 2 | const { Sequelize, DataTypes, Op } = require("sequelize"); 3 | 4 | const sequelize = new Sequelize( 5 | config.db.DB_NAME, 6 | config.db.DB_USER, 7 | config.db.DB_PASS, 8 | { 9 | host: config.db.DB_HOST, 10 | dialect: config.db.dialect, 11 | operatorsAliases: false, 12 | 13 | poll: { 14 | max: config.db.pool.max, 15 | min: config.db.pool.min, 16 | acquire: config.db.pool.acquire, 17 | idle: config.db.pool.idle 18 | } 19 | } 20 | ); 21 | 22 | const db = {}; 23 | 24 | db.Sequelize = Sequelize; 25 | db.Op = Op; 26 | db.sequelize = sequelize; 27 | 28 | db.books = require("./book.model.js")(sequelize, Sequelize, DataTypes); 29 | db.user = require("./user.model.js")(sequelize, Sequelize, DataTypes); 30 | db.role = require("./role.model.js")(sequelize, Sequelize, DataTypes); 31 | 32 | db.role.belongsToMany(db.user, { 33 | through: "user_roles", 34 | foreignKey: "role_id", 35 | otherKey: "user_id" 36 | }); 37 | db.user.belongsToMany(db.role, { 38 | through: "user_roles", 39 | foreignKey: "user_id", 40 | otherKey: "role_id" 41 | }); 42 | 43 | db.ROLES = ["user", "admin", "moderator"]; 44 | 45 | module.exports = db; 46 | -------------------------------------------------------------------------------- /app/models/role.model.js: -------------------------------------------------------------------------------- 1 | module.exports = (sequelize, Sequelize, DataTypes) => { 2 | const Role = sequelize.define( 3 | "role", // Model name 4 | { 5 | // Attributes 6 | id: { 7 | type: DataTypes.INTEGER, 8 | primaryKey: true 9 | }, 10 | name: { 11 | type: DataTypes.STRING 12 | } 13 | }, 14 | { 15 | // Options 16 | timestamps: true, 17 | underscrored: true, 18 | createdAt: "created_at", 19 | updatedAt: "updated_at" 20 | } 21 | ); 22 | 23 | return Role; 24 | }; 25 | -------------------------------------------------------------------------------- /app/models/user.model.js: -------------------------------------------------------------------------------- 1 | module.exports = (sequelize, Sequelize, DataTypes) => { 2 | const User = sequelize.define( 3 | "user", // Model name 4 | { 5 | // Attributes 6 | id: { 7 | type: DataTypes.UUID, 8 | defaultValue: Sequelize.UUIDV4, 9 | primaryKey: true 10 | }, 11 | username: { 12 | type: DataTypes.STRING, 13 | unique: true 14 | }, 15 | email: { 16 | type: DataTypes.STRING 17 | }, 18 | password: { 19 | type: DataTypes.STRING 20 | } 21 | }, 22 | { 23 | // Options 24 | timestamps: true, 25 | underscrored: true, 26 | createdAt: "created_at", 27 | updatedAt: "updated_at" 28 | } 29 | ); 30 | 31 | return User; 32 | }; 33 | -------------------------------------------------------------------------------- /app/routes/auth.routes.js: -------------------------------------------------------------------------------- 1 | const { verifySignUp } = require("../middlewares"); 2 | const controller = require("../controllers/auth.controller"); 3 | 4 | module.exports = function(app) { 5 | app.use(function(req, res, next) { 6 | res.header( 7 | "Access-Control-Allow-Headers", 8 | "x-access-token, Origin, Content-Type, Accept" 9 | ); 10 | 11 | next(); 12 | }); 13 | 14 | app.post( 15 | "/api/auth/signup", 16 | [ 17 | verifySignUp.checkDuplicateUsernameOrEmail, 18 | verifySignUp.checkRolesExisted 19 | ], 20 | controller.signup 21 | ); 22 | 23 | app.post("/api/auth/signin", controller.signin); 24 | }; 25 | -------------------------------------------------------------------------------- /app/routes/book.routes.js: -------------------------------------------------------------------------------- 1 | module.exports = app => { 2 | const bookController = require("../controllers/book.controller.js"); 3 | 4 | const router = require("express").Router(); 5 | 6 | // Create a new Book 7 | router.post("/", bookController.create); 8 | 9 | // Retrieve all Books 10 | router.get("/", bookController.findAll); 11 | 12 | // Retrieve all published Books 13 | router.get("/published", bookController.findAllPublished); 14 | 15 | // Retrieve a single Book with id 16 | router.get("/:id", bookController.findOne); 17 | 18 | // Update a Book with id 19 | router.put("/:id", bookController.update); 20 | 21 | // Delete a Book with id 22 | router.delete("/:id", bookController.delete); 23 | 24 | // Delete all Books 25 | router.delete("/", bookController.deleteAll); 26 | 27 | app.use("/api/books", router); 28 | }; 29 | -------------------------------------------------------------------------------- /app/routes/user.routes.js: -------------------------------------------------------------------------------- 1 | const { authJwt } = require("../middlewares"); 2 | const controller = require("../controllers/user.controller"); 3 | 4 | module.exports = function(app) { 5 | app.use(function(req, res, next) { 6 | res.header( 7 | "Access-Control-Allow-Headers", 8 | "x-access-token, Origin, Content-Type, Accept" 9 | ); 10 | 11 | next(); 12 | }); 13 | 14 | app.get("/api/test/all", controller.allAccess); 15 | 16 | app.get("/api/test/user", [authJwt.verifyToken], controller.userBoard); 17 | 18 | app.get( 19 | "/api/test/mod", 20 | [authJwt.verifyToken, authJwt.isModerator], 21 | controller.moderatorBoard 22 | ); 23 | 24 | app.get( 25 | "/api/test/admin", 26 | [authJwt.verifyToken, authJwt.isAdmin], 27 | controller.adminBoard 28 | ); 29 | }; 30 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nodejs-sequelize-mysql", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "@types/node": { 8 | "version": "13.1.7", 9 | "resolved": "https://registry.npmjs.org/@types/node/-/node-13.1.7.tgz", 10 | "integrity": "sha512-HU0q9GXazqiKwviVxg9SI/+t/nAsGkvLDkIdxz+ObejG2nX6Si00TeLqHMoS+a/1tjH7a8YpKVQwtgHuMQsldg==" 11 | }, 12 | "accepts": { 13 | "version": "1.3.7", 14 | "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", 15 | "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", 16 | "requires": { 17 | "mime-types": "~2.1.24", 18 | "negotiator": "0.6.2" 19 | } 20 | }, 21 | "ansicolors": { 22 | "version": "0.3.2", 23 | "resolved": "https://registry.npmjs.org/ansicolors/-/ansicolors-0.3.2.tgz", 24 | "integrity": "sha1-ZlWX3oap/+Oqm/vmyuXG6kJrSXk=" 25 | }, 26 | "any-promise": { 27 | "version": "1.3.0", 28 | "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", 29 | "integrity": "sha1-q8av7tzqUugJzcA3au0845Y10X8=" 30 | }, 31 | "array-flatten": { 32 | "version": "1.1.1", 33 | "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", 34 | "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" 35 | }, 36 | "bcryptjs": { 37 | "version": "2.4.3", 38 | "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-2.4.3.tgz", 39 | "integrity": "sha1-mrVie5PmBiH/fNrF2pczAn3x0Ms=" 40 | }, 41 | "bluebird": { 42 | "version": "3.7.2", 43 | "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", 44 | "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==" 45 | }, 46 | "body-parser": { 47 | "version": "1.19.0", 48 | "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", 49 | "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==", 50 | "requires": { 51 | "bytes": "3.1.0", 52 | "content-type": "~1.0.4", 53 | "debug": "2.6.9", 54 | "depd": "~1.1.2", 55 | "http-errors": "1.7.2", 56 | "iconv-lite": "0.4.24", 57 | "on-finished": "~2.3.0", 58 | "qs": "6.7.0", 59 | "raw-body": "2.4.0", 60 | "type-is": "~1.6.17" 61 | } 62 | }, 63 | "buffer-equal-constant-time": { 64 | "version": "1.0.1", 65 | "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", 66 | "integrity": "sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk=" 67 | }, 68 | "bytes": { 69 | "version": "3.1.0", 70 | "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", 71 | "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==" 72 | }, 73 | "cardinal": { 74 | "version": "2.1.1", 75 | "resolved": "https://registry.npmjs.org/cardinal/-/cardinal-2.1.1.tgz", 76 | "integrity": "sha1-fMEFXYItISlU0HsIXeolHMe8VQU=", 77 | "requires": { 78 | "ansicolors": "~0.3.2", 79 | "redeyed": "~2.1.0" 80 | } 81 | }, 82 | "cls-bluebird": { 83 | "version": "2.1.0", 84 | "resolved": "https://registry.npmjs.org/cls-bluebird/-/cls-bluebird-2.1.0.tgz", 85 | "integrity": "sha1-N+8eCAqP+1XC9BZPU28ZGeeWiu4=", 86 | "requires": { 87 | "is-bluebird": "^1.0.2", 88 | "shimmer": "^1.1.0" 89 | } 90 | }, 91 | "content-disposition": { 92 | "version": "0.5.3", 93 | "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", 94 | "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==", 95 | "requires": { 96 | "safe-buffer": "5.1.2" 97 | } 98 | }, 99 | "content-type": { 100 | "version": "1.0.4", 101 | "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", 102 | "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" 103 | }, 104 | "cookie": { 105 | "version": "0.4.0", 106 | "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz", 107 | "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==" 108 | }, 109 | "cookie-signature": { 110 | "version": "1.0.6", 111 | "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", 112 | "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" 113 | }, 114 | "cors": { 115 | "version": "2.8.5", 116 | "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", 117 | "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", 118 | "requires": { 119 | "object-assign": "^4", 120 | "vary": "^1" 121 | } 122 | }, 123 | "debug": { 124 | "version": "2.6.9", 125 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 126 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 127 | "requires": { 128 | "ms": "2.0.0" 129 | } 130 | }, 131 | "denque": { 132 | "version": "1.4.1", 133 | "resolved": "https://registry.npmjs.org/denque/-/denque-1.4.1.tgz", 134 | "integrity": "sha512-OfzPuSZKGcgr96rf1oODnfjqBFmr1DVoc/TrItj3Ohe0Ah1C5WX5Baquw/9U9KovnQ88EqmJbD66rKYUQYN1tQ==" 135 | }, 136 | "depd": { 137 | "version": "1.1.2", 138 | "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", 139 | "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" 140 | }, 141 | "destroy": { 142 | "version": "1.0.4", 143 | "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", 144 | "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" 145 | }, 146 | "dotenv": { 147 | "version": "8.2.0", 148 | "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.2.0.tgz", 149 | "integrity": "sha512-8sJ78ElpbDJBHNeBzUbUVLsqKdccaa/BXF1uPTw3GrvQTBgrQrtObr2mUrE38vzYd8cEv+m/JBfDLioYcfXoaw==", 150 | "dev": true 151 | }, 152 | "dottie": { 153 | "version": "2.0.2", 154 | "resolved": "https://registry.npmjs.org/dottie/-/dottie-2.0.2.tgz", 155 | "integrity": "sha512-fmrwR04lsniq/uSr8yikThDTrM7epXHBAAjH9TbeH3rEA8tdCO7mRzB9hdmdGyJCxF8KERo9CITcm3kGuoyMhg==" 156 | }, 157 | "ecdsa-sig-formatter": { 158 | "version": "1.0.11", 159 | "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", 160 | "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", 161 | "requires": { 162 | "safe-buffer": "^5.0.1" 163 | } 164 | }, 165 | "ee-first": { 166 | "version": "1.1.1", 167 | "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", 168 | "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" 169 | }, 170 | "encodeurl": { 171 | "version": "1.0.2", 172 | "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", 173 | "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" 174 | }, 175 | "escape-html": { 176 | "version": "1.0.3", 177 | "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", 178 | "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" 179 | }, 180 | "esprima": { 181 | "version": "4.0.1", 182 | "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", 183 | "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==" 184 | }, 185 | "etag": { 186 | "version": "1.8.1", 187 | "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", 188 | "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" 189 | }, 190 | "express": { 191 | "version": "4.17.1", 192 | "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz", 193 | "integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==", 194 | "requires": { 195 | "accepts": "~1.3.7", 196 | "array-flatten": "1.1.1", 197 | "body-parser": "1.19.0", 198 | "content-disposition": "0.5.3", 199 | "content-type": "~1.0.4", 200 | "cookie": "0.4.0", 201 | "cookie-signature": "1.0.6", 202 | "debug": "2.6.9", 203 | "depd": "~1.1.2", 204 | "encodeurl": "~1.0.2", 205 | "escape-html": "~1.0.3", 206 | "etag": "~1.8.1", 207 | "finalhandler": "~1.1.2", 208 | "fresh": "0.5.2", 209 | "merge-descriptors": "1.0.1", 210 | "methods": "~1.1.2", 211 | "on-finished": "~2.3.0", 212 | "parseurl": "~1.3.3", 213 | "path-to-regexp": "0.1.7", 214 | "proxy-addr": "~2.0.5", 215 | "qs": "6.7.0", 216 | "range-parser": "~1.2.1", 217 | "safe-buffer": "5.1.2", 218 | "send": "0.17.1", 219 | "serve-static": "1.14.1", 220 | "setprototypeof": "1.1.1", 221 | "statuses": "~1.5.0", 222 | "type-is": "~1.6.18", 223 | "utils-merge": "1.0.1", 224 | "vary": "~1.1.2" 225 | } 226 | }, 227 | "finalhandler": { 228 | "version": "1.1.2", 229 | "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", 230 | "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", 231 | "requires": { 232 | "debug": "2.6.9", 233 | "encodeurl": "~1.0.2", 234 | "escape-html": "~1.0.3", 235 | "on-finished": "~2.3.0", 236 | "parseurl": "~1.3.3", 237 | "statuses": "~1.5.0", 238 | "unpipe": "~1.0.0" 239 | } 240 | }, 241 | "forwarded": { 242 | "version": "0.1.2", 243 | "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", 244 | "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=" 245 | }, 246 | "fresh": { 247 | "version": "0.5.2", 248 | "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", 249 | "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" 250 | }, 251 | "generate-function": { 252 | "version": "2.3.1", 253 | "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.3.1.tgz", 254 | "integrity": "sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==", 255 | "requires": { 256 | "is-property": "^1.0.2" 257 | } 258 | }, 259 | "http-errors": { 260 | "version": "1.7.2", 261 | "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", 262 | "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==", 263 | "requires": { 264 | "depd": "~1.1.2", 265 | "inherits": "2.0.3", 266 | "setprototypeof": "1.1.1", 267 | "statuses": ">= 1.5.0 < 2", 268 | "toidentifier": "1.0.0" 269 | } 270 | }, 271 | "iconv-lite": { 272 | "version": "0.4.24", 273 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", 274 | "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", 275 | "requires": { 276 | "safer-buffer": ">= 2.1.2 < 3" 277 | } 278 | }, 279 | "inflection": { 280 | "version": "1.12.0", 281 | "resolved": "https://registry.npmjs.org/inflection/-/inflection-1.12.0.tgz", 282 | "integrity": "sha1-ogCTVlbW9fa8TcdQLhrstwMihBY=" 283 | }, 284 | "inherits": { 285 | "version": "2.0.3", 286 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", 287 | "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" 288 | }, 289 | "ipaddr.js": { 290 | "version": "1.9.0", 291 | "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.0.tgz", 292 | "integrity": "sha512-M4Sjn6N/+O6/IXSJseKqHoFc+5FdGJ22sXqnjTpdZweHK64MzEPAyQZyEU3R/KRv2GLoa7nNtg/C2Ev6m7z+eA==" 293 | }, 294 | "is-bluebird": { 295 | "version": "1.0.2", 296 | "resolved": "https://registry.npmjs.org/is-bluebird/-/is-bluebird-1.0.2.tgz", 297 | "integrity": "sha1-CWQ5Bg9KpBGr7hkUOoTWpVNG1uI=" 298 | }, 299 | "is-property": { 300 | "version": "1.0.2", 301 | "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz", 302 | "integrity": "sha1-V/4cTkhHTt1lsJkR8msc1Ald2oQ=" 303 | }, 304 | "jsonwebtoken": { 305 | "version": "8.5.1", 306 | "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz", 307 | "integrity": "sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==", 308 | "requires": { 309 | "jws": "^3.2.2", 310 | "lodash.includes": "^4.3.0", 311 | "lodash.isboolean": "^3.0.3", 312 | "lodash.isinteger": "^4.0.4", 313 | "lodash.isnumber": "^3.0.3", 314 | "lodash.isplainobject": "^4.0.6", 315 | "lodash.isstring": "^4.0.1", 316 | "lodash.once": "^4.0.0", 317 | "ms": "^2.1.1", 318 | "semver": "^5.6.0" 319 | }, 320 | "dependencies": { 321 | "ms": { 322 | "version": "2.1.2", 323 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", 324 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" 325 | }, 326 | "semver": { 327 | "version": "5.7.1", 328 | "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", 329 | "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" 330 | } 331 | } 332 | }, 333 | "jwa": { 334 | "version": "1.4.1", 335 | "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", 336 | "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", 337 | "requires": { 338 | "buffer-equal-constant-time": "1.0.1", 339 | "ecdsa-sig-formatter": "1.0.11", 340 | "safe-buffer": "^5.0.1" 341 | } 342 | }, 343 | "jws": { 344 | "version": "3.2.2", 345 | "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", 346 | "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", 347 | "requires": { 348 | "jwa": "^1.4.1", 349 | "safe-buffer": "^5.0.1" 350 | } 351 | }, 352 | "lodash": { 353 | "version": "4.17.15", 354 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", 355 | "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==" 356 | }, 357 | "lodash.includes": { 358 | "version": "4.3.0", 359 | "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", 360 | "integrity": "sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8=" 361 | }, 362 | "lodash.isboolean": { 363 | "version": "3.0.3", 364 | "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", 365 | "integrity": "sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY=" 366 | }, 367 | "lodash.isinteger": { 368 | "version": "4.0.4", 369 | "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", 370 | "integrity": "sha1-YZwK89A/iwTDH1iChAt3sRzWg0M=" 371 | }, 372 | "lodash.isnumber": { 373 | "version": "3.0.3", 374 | "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", 375 | "integrity": "sha1-POdoEMWSjQM1IwGsKHMX8RwLH/w=" 376 | }, 377 | "lodash.isplainobject": { 378 | "version": "4.0.6", 379 | "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", 380 | "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=" 381 | }, 382 | "lodash.isstring": { 383 | "version": "4.0.1", 384 | "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", 385 | "integrity": "sha1-1SfftUVuynzJu5XV2ur4i6VKVFE=" 386 | }, 387 | "lodash.once": { 388 | "version": "4.1.1", 389 | "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", 390 | "integrity": "sha1-DdOXEhPHxW34gJd9UEyI+0cal6w=" 391 | }, 392 | "long": { 393 | "version": "4.0.0", 394 | "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", 395 | "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==" 396 | }, 397 | "lru-cache": { 398 | "version": "5.1.1", 399 | "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", 400 | "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", 401 | "requires": { 402 | "yallist": "^3.0.2" 403 | } 404 | }, 405 | "media-typer": { 406 | "version": "0.3.0", 407 | "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", 408 | "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" 409 | }, 410 | "merge-descriptors": { 411 | "version": "1.0.1", 412 | "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", 413 | "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" 414 | }, 415 | "methods": { 416 | "version": "1.1.2", 417 | "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", 418 | "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" 419 | }, 420 | "mime": { 421 | "version": "1.6.0", 422 | "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", 423 | "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" 424 | }, 425 | "mime-db": { 426 | "version": "1.43.0", 427 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.43.0.tgz", 428 | "integrity": "sha512-+5dsGEEovYbT8UY9yD7eE4XTc4UwJ1jBYlgaQQF38ENsKR3wj/8q8RFZrF9WIZpB2V1ArTVFUva8sAul1NzRzQ==" 429 | }, 430 | "mime-types": { 431 | "version": "2.1.26", 432 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.26.tgz", 433 | "integrity": "sha512-01paPWYgLrkqAyrlDorC1uDwl2p3qZT7yl806vW7DvDoxwXi46jsjFbg+WdwotBIk6/MbEhO/dh5aZ5sNj/dWQ==", 434 | "requires": { 435 | "mime-db": "1.43.0" 436 | } 437 | }, 438 | "moment": { 439 | "version": "2.24.0", 440 | "resolved": "https://registry.npmjs.org/moment/-/moment-2.24.0.tgz", 441 | "integrity": "sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg==" 442 | }, 443 | "moment-timezone": { 444 | "version": "0.5.27", 445 | "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.27.tgz", 446 | "integrity": "sha512-EIKQs7h5sAsjhPCqN6ggx6cEbs94GK050254TIJySD1bzoM5JTYDwAU1IoVOeTOL6Gm27kYJ51/uuvq1kIlrbw==", 447 | "requires": { 448 | "moment": ">= 2.9.0" 449 | } 450 | }, 451 | "ms": { 452 | "version": "2.0.0", 453 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 454 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" 455 | }, 456 | "mysql2": { 457 | "version": "2.1.0", 458 | "resolved": "https://registry.npmjs.org/mysql2/-/mysql2-2.1.0.tgz", 459 | "integrity": "sha512-9kGVyi930rG2KaHrz3sHwtc6K+GY9d8wWk1XRSYxQiunvGcn4DwuZxOwmK11ftuhhwrYDwGx9Ta4VBwznJn36A==", 460 | "requires": { 461 | "cardinal": "^2.1.1", 462 | "denque": "^1.4.1", 463 | "generate-function": "^2.3.1", 464 | "iconv-lite": "^0.5.0", 465 | "long": "^4.0.0", 466 | "lru-cache": "^5.1.1", 467 | "named-placeholders": "^1.1.2", 468 | "seq-queue": "^0.0.5", 469 | "sqlstring": "^2.3.1" 470 | }, 471 | "dependencies": { 472 | "iconv-lite": { 473 | "version": "0.5.0", 474 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.5.0.tgz", 475 | "integrity": "sha512-NnEhI9hIEKHOzJ4f697DMz9IQEXr/MMJ5w64vN2/4Ai+wRnvV7SBrL0KLoRlwaKVghOc7LQ5YkPLuX146b6Ydw==", 476 | "requires": { 477 | "safer-buffer": ">= 2.1.2 < 3" 478 | } 479 | } 480 | } 481 | }, 482 | "named-placeholders": { 483 | "version": "1.1.2", 484 | "resolved": "https://registry.npmjs.org/named-placeholders/-/named-placeholders-1.1.2.tgz", 485 | "integrity": "sha512-wiFWqxoLL3PGVReSZpjLVxyJ1bRqe+KKJVbr4hGs1KWfTZTQyezHFBbuKj9hsizHyGV2ne7EMjHdxEGAybD5SA==", 486 | "requires": { 487 | "lru-cache": "^4.1.3" 488 | }, 489 | "dependencies": { 490 | "lru-cache": { 491 | "version": "4.1.5", 492 | "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", 493 | "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", 494 | "requires": { 495 | "pseudomap": "^1.0.2", 496 | "yallist": "^2.1.2" 497 | } 498 | }, 499 | "yallist": { 500 | "version": "2.1.2", 501 | "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", 502 | "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=" 503 | } 504 | } 505 | }, 506 | "negotiator": { 507 | "version": "0.6.2", 508 | "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", 509 | "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==" 510 | }, 511 | "object-assign": { 512 | "version": "4.1.1", 513 | "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", 514 | "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" 515 | }, 516 | "on-finished": { 517 | "version": "2.3.0", 518 | "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", 519 | "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", 520 | "requires": { 521 | "ee-first": "1.1.1" 522 | } 523 | }, 524 | "parseurl": { 525 | "version": "1.3.3", 526 | "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", 527 | "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" 528 | }, 529 | "path-to-regexp": { 530 | "version": "0.1.7", 531 | "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", 532 | "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" 533 | }, 534 | "proxy-addr": { 535 | "version": "2.0.5", 536 | "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.5.tgz", 537 | "integrity": "sha512-t/7RxHXPH6cJtP0pRG6smSr9QJidhB+3kXu0KgXnbGYMgzEnUxRQ4/LDdfOwZEMyIh3/xHb8PX3t+lfL9z+YVQ==", 538 | "requires": { 539 | "forwarded": "~0.1.2", 540 | "ipaddr.js": "1.9.0" 541 | } 542 | }, 543 | "pseudomap": { 544 | "version": "1.0.2", 545 | "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", 546 | "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=" 547 | }, 548 | "qs": { 549 | "version": "6.7.0", 550 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", 551 | "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==" 552 | }, 553 | "range-parser": { 554 | "version": "1.2.1", 555 | "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", 556 | "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" 557 | }, 558 | "raw-body": { 559 | "version": "2.4.0", 560 | "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz", 561 | "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==", 562 | "requires": { 563 | "bytes": "3.1.0", 564 | "http-errors": "1.7.2", 565 | "iconv-lite": "0.4.24", 566 | "unpipe": "1.0.0" 567 | } 568 | }, 569 | "redeyed": { 570 | "version": "2.1.1", 571 | "resolved": "https://registry.npmjs.org/redeyed/-/redeyed-2.1.1.tgz", 572 | "integrity": "sha1-iYS1gV2ZyyIEacme7v/jiRPmzAs=", 573 | "requires": { 574 | "esprima": "~4.0.0" 575 | } 576 | }, 577 | "retry-as-promised": { 578 | "version": "3.2.0", 579 | "resolved": "https://registry.npmjs.org/retry-as-promised/-/retry-as-promised-3.2.0.tgz", 580 | "integrity": "sha512-CybGs60B7oYU/qSQ6kuaFmRd9sTZ6oXSc0toqePvV74Ac6/IFZSI1ReFQmtCN+uvW1Mtqdwpvt/LGOiCBAY2Mg==", 581 | "requires": { 582 | "any-promise": "^1.3.0" 583 | } 584 | }, 585 | "safe-buffer": { 586 | "version": "5.1.2", 587 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", 588 | "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" 589 | }, 590 | "safer-buffer": { 591 | "version": "2.1.2", 592 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", 593 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" 594 | }, 595 | "semver": { 596 | "version": "6.3.0", 597 | "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", 598 | "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" 599 | }, 600 | "send": { 601 | "version": "0.17.1", 602 | "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz", 603 | "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==", 604 | "requires": { 605 | "debug": "2.6.9", 606 | "depd": "~1.1.2", 607 | "destroy": "~1.0.4", 608 | "encodeurl": "~1.0.2", 609 | "escape-html": "~1.0.3", 610 | "etag": "~1.8.1", 611 | "fresh": "0.5.2", 612 | "http-errors": "~1.7.2", 613 | "mime": "1.6.0", 614 | "ms": "2.1.1", 615 | "on-finished": "~2.3.0", 616 | "range-parser": "~1.2.1", 617 | "statuses": "~1.5.0" 618 | }, 619 | "dependencies": { 620 | "ms": { 621 | "version": "2.1.1", 622 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", 623 | "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" 624 | } 625 | } 626 | }, 627 | "seq-queue": { 628 | "version": "0.0.5", 629 | "resolved": "https://registry.npmjs.org/seq-queue/-/seq-queue-0.0.5.tgz", 630 | "integrity": "sha1-1WgS4cAXpuTnw+Ojeh2m143TyT4=" 631 | }, 632 | "sequelize": { 633 | "version": "5.21.3", 634 | "resolved": "https://registry.npmjs.org/sequelize/-/sequelize-5.21.3.tgz", 635 | "integrity": "sha512-ptdeAxwTY0zbj7AK8m+SH3z52uHVrt/qmOTSIGo/kyfnSp3h5HeKlywkJf5GEk09kuRrPHfWARVSXH1W3IGU7g==", 636 | "requires": { 637 | "bluebird": "^3.5.0", 638 | "cls-bluebird": "^2.1.0", 639 | "debug": "^4.1.1", 640 | "dottie": "^2.0.0", 641 | "inflection": "1.12.0", 642 | "lodash": "^4.17.15", 643 | "moment": "^2.24.0", 644 | "moment-timezone": "^0.5.21", 645 | "retry-as-promised": "^3.2.0", 646 | "semver": "^6.3.0", 647 | "sequelize-pool": "^2.3.0", 648 | "toposort-class": "^1.0.1", 649 | "uuid": "^3.3.3", 650 | "validator": "^10.11.0", 651 | "wkx": "^0.4.8" 652 | }, 653 | "dependencies": { 654 | "debug": { 655 | "version": "4.1.1", 656 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", 657 | "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", 658 | "requires": { 659 | "ms": "^2.1.1" 660 | } 661 | }, 662 | "ms": { 663 | "version": "2.1.2", 664 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", 665 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" 666 | } 667 | } 668 | }, 669 | "sequelize-pool": { 670 | "version": "2.3.0", 671 | "resolved": "https://registry.npmjs.org/sequelize-pool/-/sequelize-pool-2.3.0.tgz", 672 | "integrity": "sha512-Ibz08vnXvkZ8LJTiUOxRcj1Ckdn7qafNZ2t59jYHMX1VIebTAOYefWdRYFt6z6+hy52WGthAHAoLc9hvk3onqA==" 673 | }, 674 | "serve-static": { 675 | "version": "1.14.1", 676 | "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz", 677 | "integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==", 678 | "requires": { 679 | "encodeurl": "~1.0.2", 680 | "escape-html": "~1.0.3", 681 | "parseurl": "~1.3.3", 682 | "send": "0.17.1" 683 | } 684 | }, 685 | "setprototypeof": { 686 | "version": "1.1.1", 687 | "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", 688 | "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==" 689 | }, 690 | "shimmer": { 691 | "version": "1.2.1", 692 | "resolved": "https://registry.npmjs.org/shimmer/-/shimmer-1.2.1.tgz", 693 | "integrity": "sha512-sQTKC1Re/rM6XyFM6fIAGHRPVGvyXfgzIDvzoq608vM+jeyVD0Tu1E6Np0Kc2zAIFWIj963V2800iF/9LPieQw==" 694 | }, 695 | "sqlstring": { 696 | "version": "2.3.1", 697 | "resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.1.tgz", 698 | "integrity": "sha1-R1OT/56RR5rqYtyvDKPRSYOn+0A=" 699 | }, 700 | "statuses": { 701 | "version": "1.5.0", 702 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", 703 | "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" 704 | }, 705 | "toidentifier": { 706 | "version": "1.0.0", 707 | "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", 708 | "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==" 709 | }, 710 | "toposort-class": { 711 | "version": "1.0.1", 712 | "resolved": "https://registry.npmjs.org/toposort-class/-/toposort-class-1.0.1.tgz", 713 | "integrity": "sha1-f/0feMi+KMO6Rc1OGj9e4ZO9mYg=" 714 | }, 715 | "type-is": { 716 | "version": "1.6.18", 717 | "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", 718 | "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", 719 | "requires": { 720 | "media-typer": "0.3.0", 721 | "mime-types": "~2.1.24" 722 | } 723 | }, 724 | "unpipe": { 725 | "version": "1.0.0", 726 | "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", 727 | "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" 728 | }, 729 | "utils-merge": { 730 | "version": "1.0.1", 731 | "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", 732 | "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" 733 | }, 734 | "uuid": { 735 | "version": "3.3.3", 736 | "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.3.tgz", 737 | "integrity": "sha512-pW0No1RGHgzlpHJO1nsVrHKpOEIxkGg1xB+v0ZmdNH5OAeAwzAVrCnI2/6Mtx+Uys6iaylxa+D3g4j63IKKjSQ==" 738 | }, 739 | "validator": { 740 | "version": "10.11.0", 741 | "resolved": "https://registry.npmjs.org/validator/-/validator-10.11.0.tgz", 742 | "integrity": "sha512-X/p3UZerAIsbBfN/IwahhYaBbY68EN/UQBWHtsbXGT5bfrH/p4NQzUCG1kF/rtKaNpnJ7jAu6NGTdSNtyNIXMw==" 743 | }, 744 | "vary": { 745 | "version": "1.1.2", 746 | "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", 747 | "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" 748 | }, 749 | "wkx": { 750 | "version": "0.4.8", 751 | "resolved": "https://registry.npmjs.org/wkx/-/wkx-0.4.8.tgz", 752 | "integrity": "sha512-ikPXMM9IR/gy/LwiOSqWlSL3X/J5uk9EO2hHNRXS41eTLXaUFEVw9fn/593jW/tE5tedNg8YjT5HkCa4FqQZyQ==", 753 | "requires": { 754 | "@types/node": "*" 755 | } 756 | }, 757 | "yallist": { 758 | "version": "3.1.1", 759 | "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", 760 | "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" 761 | } 762 | } 763 | } 764 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nodejs-sequelize-mysql", 3 | "version": "1.0.0", 4 | "description": "Learn REST API using Node.js, Express, Sequelize and MySQL", 5 | "main": "server.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "dev": "nodemon -r dotenv/config server.js" 9 | }, 10 | "keywords": [ 11 | "rest", 12 | "api", 13 | "nodejs", 14 | "express", 15 | "sequelize", 16 | "mysql", 17 | "jwt" 18 | ], 19 | "author": "Indra Arianggi", 20 | "license": "ISC", 21 | "dependencies": { 22 | "bcryptjs": "^2.4.3", 23 | "body-parser": "^1.19.0", 24 | "cors": "^2.8.5", 25 | "express": "^4.17.1", 26 | "jsonwebtoken": "^8.5.1", 27 | "mysql2": "^2.1.0", 28 | "sequelize": "^5.21.3" 29 | }, 30 | "devDependencies": { 31 | "dotenv": "^8.2.0" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const bodyParser = require("body-parser"); 3 | const cors = require("cors"); 4 | const config = require("./app/config/config.js"); 5 | 6 | const app = express(); 7 | 8 | const corsOptions = { 9 | origin: "http://localhost:8081" 10 | }; 11 | 12 | app.use(cors(corsOptions)); 13 | 14 | // parse requests of content-type - application/json 15 | app.use(bodyParser.json()); 16 | 17 | // parse requests of content-type - application/x-www-form-urlencoded 18 | app.use(bodyParser.urlencoded({ extended: true })); 19 | 20 | // database 21 | const db = require("./app/models"); 22 | const Role = db.role; 23 | db.sequelize.sync().then(() => { 24 | initial(); // Just use it in development, at the first time execution!. Delete it in production 25 | }); 26 | 27 | // simple route 28 | app.get("/", (req, res) => { 29 | res.json({ message: "Hi there, welcome to this tutorial." }); 30 | }); 31 | 32 | // api routes 33 | require("./app/routes/book.routes")(app); 34 | require("./app/routes/auth.routes")(app); 35 | require("./app/routes/user.routes")(app); 36 | 37 | // set port, listen for requests 38 | const PORT = config.PORT; 39 | app.listen(PORT, () => { 40 | console.log(`Server is running on port ${PORT}`); 41 | }); 42 | 43 | // Just use it in development, at the first time execution!. Delete it in production 44 | function initial() { 45 | Role.create({ 46 | id: 1, 47 | name: "user" 48 | }); 49 | 50 | Role.create({ 51 | id: 2, 52 | name: "moderator" 53 | }); 54 | 55 | Role.create({ 56 | id: 3, 57 | name: "admin" 58 | }); 59 | } 60 | --------------------------------------------------------------------------------