├── .babelrc ├── .gitignore ├── README.md ├── app ├── controllers │ ├── root.js │ └── users │ │ ├── base.js │ │ ├── id-parameter-address.js │ │ ├── id-parameter.js │ │ └── index.js ├── db.js ├── helpers │ ├── error-handler.js │ ├── httpcodes.json │ ├── middlewares.js │ └── validate.js ├── models │ ├── users-addresses.js │ └── users.js └── routes.js ├── env.example ├── index.js ├── knexfile.js ├── migrations └── 20170208202452_setup.js ├── package.json └── public └── index.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["env"] 3 | } 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .env 2 | node_modules 3 | npm-debug.log 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # NodeJS MySQL Boilerplate 2 | 3 | This app defines a very neat and modular structure to start you next nodejs project. 4 | 5 | Using: MySQL, NodeJS, Express, Bookshelf, Knex, Json Web Token(JWT) 6 | 7 | Using ES6, with Babel (http://babeljs.io/) 8 | 9 | 10 | ## Pre-requisites: 11 | 1. NodeJS (https://nodejs.org/en/) 12 | 2. Globally installed nodemon (https://nodemon.io/) 13 | 14 | 15 | ## Steps to run: 16 | ``` 17 | git clone git@gitlab.com:raghavgarg1257/nodejs-mysql-boilerplate.git 18 | cd nodejs-mysql-boilerplate 19 | cp env.example .env 20 | nano .env #now edit credentials according to your machine (mandatory for db connection) 21 | npm install 22 | ``` 23 | Now to migrate Database 24 | ``` 25 | npm install knex -g 26 | 27 | # To create the tables 28 | npm run migrate-up 29 | 30 | # To drop the tables 31 | npm run migrate-down 32 | ``` 33 | Now to start the server 34 | ``` 35 | npm start 36 | ``` 37 | The app will be started on the mentioned port which will be printed in the console upon starting the server like: `http://localhost:8080`. 38 | 39 | 40 | ## Available routes 41 | ``` 42 | -> GET / : (open*) Just show the message 43 | -> POST / : (open*) Another message. 44 | 45 | -> GET /users : (open*) Show all the users in the app 46 | -> POST /users [name, phone, email] : (open*) Add new user (generate jwt token) 47 | 48 | -> GET /users:id : (protected*) Get the user info by id 49 | -> PUT /users:id [name, phone, email](optional) : (protected*) Update the user info by id 50 | -> DELETE /users:id : (protected*) Delete the user by id 51 | 52 | -> GET /users:id/address : (protected*) Show all the address got the user by id 53 | -> POST /users:id/address [line1, line2, state, pincode, landmark] : (protected*) Add new address to the user by id 54 | 55 | # guide 56 | open* - means route is un-protected, anyone can access the route 57 | protected* - means a valid jwt token has to be used to access the route in header "Authorization" with value "Bearer {token}" 58 | ``` 59 | -------------------------------------------------------------------------------- /app/controllers/root.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | import HTTP from "../helpers/httpcodes"; 4 | 5 | module.exports = (router, middlewares) => { 6 | 7 | router.route('/') 8 | // .all(middlewares.authenticate) // will be run for all type of request on '/' 9 | 10 | .get( (req, res, next) => { 11 | res.status(HTTP.OK).json("Welcome to my world!"); 12 | } ) 13 | 14 | .post( (req, res, next) => { 15 | res.status(HTTP.OK).json("So, you know there are more than one type of request"); 16 | } ); 17 | 18 | }; 19 | -------------------------------------------------------------------------------- /app/controllers/users/base.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | import CheckIt from "checkit"; 4 | import Jwt from "jsonwebtoken"; 5 | 6 | import HTTP from "../../helpers/httpcodes"; 7 | import { ModelError, ISE } from "../../helpers/error-handler"; 8 | 9 | // models used 10 | import Users from "../../models/users"; 11 | 12 | class Base { 13 | 14 | all (req, res, next) { next() } 15 | 16 | // GET request 17 | get (req, res) { 18 | Users.fetchAll() 19 | .then( all_users => { 20 | if (all_users.length) { 21 | res.status(HTTP.OK).json({ 22 | message: "Users found", 23 | data: all_users.toJSON() 24 | }); 25 | } 26 | else { 27 | res.status(HTTP.NOT_FOUND).json({ message: "No User found", data:error.message }) 28 | } 29 | }) 30 | .catch((error) => { ISE(error, res) }); 31 | } 32 | 33 | // POST request 34 | post (req, res) { 35 | 36 | const user = new Users({ 37 | Phone: req.body.phone, 38 | Name: req.body.name, 39 | Email: req.body.email 40 | }); 41 | 42 | user.save() 43 | .then( model => { 44 | 45 | const token = Jwt.sign( 46 | { id: model.StringID }, 47 | new Buffer(process.env.JWT_SECRET, "base64"), 48 | { algorithm: 'HS512', expiresIn: '1d' } 49 | ); 50 | 51 | res.status(HTTP.OK).json({ 52 | message: "User successfully created", 53 | data: { 54 | id: model.StringID, 55 | token: token 56 | } 57 | }); 58 | 59 | }) 60 | .catch((error) => { ModelError(error, res) }); 61 | 62 | } 63 | 64 | 65 | } 66 | 67 | export default Base; 68 | -------------------------------------------------------------------------------- /app/controllers/users/id-parameter-address.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | import CheckIt from "checkit"; 4 | 5 | import HTTP from "../../helpers/httpcodes"; 6 | import { isID } from '../../helpers/validate'; 7 | import { ModelError, ISE } from "../../helpers/error-handler"; 8 | 9 | // models used 10 | import Users from "../../models/users"; 11 | import Users_Addresses from "../../models/users-addresses"; 12 | 13 | class IdParameterAddress { 14 | 15 | all (req, res, next) { next() } 16 | 17 | // GET request 18 | get (req, res) { 19 | 20 | if (!isID(req.params.id)) res.status(HTTP.BAD_REQUEST).json({ message: "Invalid ID" }); 21 | 22 | Users.where({"ID": new Buffer(req.params.id, "hex")}).fetch({ withRelated: ['addresses'] }) 23 | .then( user => { 24 | if (user) { 25 | res.status(HTTP.OK).json({ 26 | message: "User address found", 27 | data: user.toJSON() 28 | }); 29 | } 30 | else { 31 | res.status(HTTP.NOT_FOUND).json({ message: "user couldn't be found" }); 32 | } 33 | }) 34 | .catch((error) => { ISE(error, res) }); 35 | 36 | } 37 | 38 | // POST request 39 | post (req, res) { 40 | 41 | if (!isID(req.params.id)) res.status(HTTP.BAD_REQUEST).json({ message: "Invalid ID" }); 42 | 43 | Users.where({"ID": new Buffer(req.params.id, "hex")}).fetch() 44 | .then( user => { 45 | if (user) { 46 | 47 | const user_address = new Users_Addresses({ 48 | User_ID: new Buffer(req.params.id, 'hex'), 49 | Line_1: req.body.line1, 50 | Line_2: req.body.line2, 51 | State: req.body.state, 52 | Pincode: req.body.pincode, 53 | Landmark: req.body.landmark 54 | }); 55 | 56 | user_address.save() 57 | .then( model => { 58 | res.status(HTTP.OK).json({ 59 | message: "User's Address successfully created", 60 | data: model.StringID 61 | }); 62 | }) 63 | .catch((error) => { ModelError(error, res) }); 64 | 65 | } 66 | else { 67 | res.status(HTTP.NOT_FOUND).json({ message: "user couldn't be found" }); 68 | } 69 | }) 70 | .catch((error) => { ISE(error, res) }); 71 | 72 | } 73 | 74 | 75 | } 76 | 77 | export default IdParameterAddress; 78 | -------------------------------------------------------------------------------- /app/controllers/users/id-parameter.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | import CheckIt from "checkit"; 4 | 5 | import HTTP from "../../helpers/httpcodes"; 6 | import { isID } from '../../helpers/validate'; 7 | import { ModelError, ISE } from "../../helpers/error-handler"; 8 | 9 | // models used 10 | import Users from "../../models/users"; 11 | 12 | class IdParameter { 13 | 14 | all (req, res, next) { next() } 15 | 16 | // GET request 17 | get (req, res) { 18 | 19 | if (!isID(req.params.id)) res.status(HTTP.BAD_REQUEST).json({ message: "Invalid ID" }); 20 | 21 | Users.where({"ID": new Buffer(req.params.id, "hex")}).fetch() 22 | .then( user => { 23 | if (user) { 24 | res.status(HTTP.OK).json({ 25 | message: "Users found", 26 | data: user.toJSON() 27 | }); 28 | } 29 | else { 30 | res.status(HTTP.NOT_FOUND).json({ message: "user couldn't be found" }); 31 | } 32 | }) 33 | .catch((error) => { ISE(error, res) }); 34 | 35 | } 36 | 37 | // PUT request 38 | put (req, res) { 39 | 40 | if (!isID(req.params.id)) res.status(HTTP.BAD_REQUEST).json({ message: "Invalid ID" }); 41 | 42 | Users.where({'ID': new Buffer(req.params.id, "hex")}).fetch() 43 | .then( user => { 44 | if (user) { 45 | 46 | let updateObj = {}; 47 | if (req.body.name) updateObj.Name = req.body.name; 48 | if (req.body.phone) updateObj.Phone = req.body.phone; 49 | if (req.body.email) updateObj.Email = req.body.email; 50 | 51 | if (Object.keys(updateObj).length === 0) { 52 | res.status(HTTP.BAD_REQUEST).json({ message: "No parameter given"}); 53 | } 54 | else { 55 | Users.where({'ID': new Buffer(req.params.id, "hex")}) 56 | .save(updateObj, { patch:true }) // why using patch: so to update selective fields 57 | .then( model => { 58 | res.status(HTTP.OK).json({ 59 | message: "User successfully updated", 60 | data: req.params.id 61 | }); 62 | }) 63 | .catch((error) => { ModelError(error, res) }); 64 | } 65 | 66 | } 67 | else { 68 | res.status(HTTP.NOT_FOUND).json({ message: "user couldn't be found" }); 69 | } 70 | }) 71 | .catch((error) => { ISE(error, res) }); 72 | 73 | } 74 | 75 | // DELETE request 76 | delete (req, res) { 77 | 78 | if (!isID(req.params.id)) res.status(HTTP.BAD_REQUEST).json({ message: "Invalid ID" }); 79 | 80 | Users.where({"ID": new Buffer(req.params.id, "hex")}).fetch() 81 | .then( user => { 82 | if (user) { 83 | 84 | Users.where({'ID': new Buffer(req.params.id, "hex")}) 85 | .destroy() 86 | .then( model => { 87 | res.status(HTTP.OK).json({ 88 | status: 1, 89 | message: "User successfully removed", 90 | }); 91 | }) 92 | .catch((error) => { ISE(error, res) }); 93 | 94 | } 95 | else { 96 | res.status(HTTP.NOT_FOUND).json({ message: "user couldn't be found" }); 97 | } 98 | }) 99 | .catch((error) => { ISE(error, res) }); 100 | } 101 | 102 | 103 | } 104 | 105 | export default IdParameter; 106 | -------------------------------------------------------------------------------- /app/controllers/users/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | import Base from "./base"; 4 | import IdParameter from "./id-parameter"; 5 | import IdParameterAddress from "./id-parameter-address"; 6 | 7 | module.exports = (router, middlewares) => { 8 | 9 | // middleware for this route only 10 | router.use('/users', (req, res, next) => next() ); 11 | 12 | // all the routes related to '/users' 13 | 14 | const base = new Base(); 15 | router.route('/users') 16 | .all(base.all) // open route 17 | .get(base.get) // fetch all users 18 | .post(base.post); // create new user 19 | 20 | // always place route with parameter at the end so that above routes become valid 21 | const idParameter = new IdParameter(); 22 | router.route('/users/:id') 23 | .all(middlewares.authenticate) // protected route 24 | .get(idParameter.get) // fetch single user by id 25 | .put(idParameter.put) // update user by id 26 | .delete(idParameter.delete); // delete user by id 27 | 28 | const address = new IdParameterAddress(); 29 | router.route('/users/:id/address') 30 | .all(middlewares.authenticate) // protected route 31 | .get(address.get) // fetch all address of user by id 32 | .post(address.post); // create new address for the user by id 33 | 34 | }; 35 | -------------------------------------------------------------------------------- /app/db.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | import _knex from "knex"; 4 | import _bookshelf from "bookshelf"; 5 | import dotenv from "dotenv"; 6 | 7 | dotenv.config(); 8 | 9 | const knex = _knex({ 10 | client: "mysql", 11 | connection: { 12 | host: "127.0.0.1", 13 | database: process.env.DB, 14 | user: process.env.DB_USERNAME, 15 | password: process.env.DB_PASSWORD 16 | }, 17 | debug: true 18 | }); 19 | 20 | const Bookshelf = _bookshelf(knex); 21 | 22 | // to resolve circular dependencies with relations 23 | Bookshelf.plugin('registry'); 24 | 25 | export default Bookshelf; 26 | -------------------------------------------------------------------------------- /app/helpers/error-handler.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | import CheckIt from "checkit"; 4 | 5 | import HTTP from "./httpcodes"; 6 | 7 | export function ISE(error, res) { 8 | // console.log(error); // uncomment to see whole error 9 | return res.status(HTTP.INTERNAL_SERVER_ERROR).json({ message: "Something is wrong.", data:error.message }) 10 | } 11 | 12 | export function ModelError(error, res) { 13 | // console.log(error); // uncomment to see whole error 14 | if (error instanceof CheckIt.Error) { 15 | return res.status(HTTP.BAD_REQUEST) 16 | .json({ 17 | message: "Not valid data, user couldn't be created", 18 | data:error.toJSON() 19 | }); 20 | } 21 | else { 22 | return ISE(error, res); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /app/helpers/httpcodes.json: -------------------------------------------------------------------------------- 1 | { 2 | "CONTINUE": 100, 3 | "SWITCHING_PROTOCOLS": 101, 4 | "PROCESSING": 102, 5 | 6 | "OK": 200, 7 | "CREATED": 201, 8 | "ACCEPTED": 202, 9 | "NON_AUTHORITATIVE_INFORMATION": 203, 10 | "NO_CONTENT": 204, 11 | "RESET_CONTENT": 205, 12 | "PARTIAL_CONTENT": 206, 13 | "MULTI_STATUS": 207, 14 | 15 | "MULTIPLE_CHOICES": 300, 16 | "MOVED_PERMANENTLY": 301, 17 | "MOVED_TEMPORARILY": 302, 18 | "SEE_OTHER": 303, 19 | "NOT_MODIFIED": 304, 20 | "USE_PROXY": 305, 21 | "TEMPORARY_REDIRECT": 307, 22 | "PERMANENT_REDIRECT": 308, 23 | 24 | "BAD_REQUEST": 400, 25 | "UNAUTHORIZED": 401, 26 | "PAYMENT_REQUIRED": 402, 27 | "FORBIDDEN": 403, 28 | "NOT_FOUND": 404, 29 | "METHOD_NOT_ALLOWED": 405, 30 | "NOT_ACCEPTABLE": 406, 31 | "PROXY_AUTHENTICATION_REQUIRED": 407, 32 | "REQUEST_TIMEOUT": 408, 33 | "CONFLICT": 409, 34 | "GONE": 410, 35 | "LENGTH_REQUIRED": 411, 36 | "PRECONDITION_FAILED": 412, 37 | "REQUEST_TOO_LONG": 413, 38 | "REQUEST_URI_TOO_LONG": 414, 39 | "UNSUPPORTED_MEDIA_TYPE": 415, 40 | "REQUESTED_RANGE_NOT_SATISFIABLE": 416, 41 | "EXPECTATION_FAILED": 417, 42 | "INSUFFICIENT_SPACE_ON_RESOURCE": 419, 43 | "METHOD_FAILURE": 420, 44 | "UNPROCESSABLE_ENTITY": 422, 45 | "LOCKED": 423, 46 | "FAILED_DEPENDENCY": 424, 47 | "PRECONDITION_REQUIRED": 428, 48 | "TOO_MANY_REQUESTS": 429, 49 | "REQUEST_HEADER_FIELDS_TOO_LARGE": 431, 50 | 51 | "INTERNAL_SERVER_ERROR": 500, 52 | "NOT_IMPLEMENTED": 501, 53 | "BAD_GATEWAY": 502, 54 | "SERVICE_UNAVAILABLE": 503, 55 | "GATEWAY_TIMEOUT": 504, 56 | "HTTP_VERSION_NOT_SUPPORTED": 505, 57 | "INSUFFICIENT_STORAGE": 507, 58 | "NETWORK_AUTHENTICATION_REQUIRED": 511 59 | } 60 | -------------------------------------------------------------------------------- /app/helpers/middlewares.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | import Jwt from "jsonwebtoken"; 4 | 5 | // all the middleware function to reuse everywhere 6 | class Middleware { 7 | 8 | applicationBase (req, res, next) { 9 | // will run first for every route in the app 10 | next(); 11 | } 12 | 13 | controllerBase (req, res, next) { 14 | // will run for every request on the mentioned route 15 | next(); 16 | } 17 | 18 | authenticate (req, res, next) { 19 | // authenticating user with session/jwt 20 | 21 | if (req.headers.authorization && req.headers.authorization.split(' ')[0] === "Bearer") { 22 | 23 | Jwt.verify( 24 | req.headers.authorization.split(' ')[1], 25 | new Buffer(process.env.JWT_SECRET, "base64"), 26 | { algorithm: 'HS512' }, 27 | (error, decoded) => { 28 | if (error) { 29 | return res.json("Invalid Token"); 30 | } 31 | else { 32 | next(); 33 | } 34 | } 35 | ); 36 | 37 | } 38 | else { 39 | return res.json("No token found"); 40 | } 41 | 42 | } 43 | 44 | 45 | } 46 | 47 | export default Middleware; 48 | -------------------------------------------------------------------------------- /app/helpers/validate.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | import { isEmpty, isHexadecimal, isLength, matches, trim } from 'validator'; 4 | 5 | class Validate { 6 | 7 | static sanitize(val) { 8 | // we can add more sanitization rules 9 | // refer https://www.npmjs.com/package/validator#sanitizers 10 | return trim(val); 11 | } 12 | 13 | static isName(name) { 14 | if ( !isEmpty(name) && matches(name, /^[0-9a-z \']+$/i)) return true; 15 | else return false; 16 | } 17 | 18 | static isID(id) { 19 | if (isLength(id, 32) && isHexadecimal(id)) return true; 20 | else return false; 21 | } 22 | 23 | // Model: where you want to find uniqueness 24 | // attribute: with what field of model you want to uniqueness 25 | // value: user's input value 26 | // countWhat: primary key field of the model to count number of records 27 | static isExist(Model, attribute, value, countWhat) { 28 | return new Promise( (resolve, reject) => { 29 | Model.where(attribute, value).count(countWhat) 30 | .then( count => { 31 | if (count > 0) reject(); 32 | else resolve(); 33 | }).catch( err => reject(err) ); 34 | }); 35 | } 36 | 37 | } 38 | 39 | module.exports = Validate; 40 | -------------------------------------------------------------------------------- /app/models/users-addresses.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | import crypto from "crypto"; 4 | import CheckIt from "checkit"; 5 | 6 | import Bookshelf from "../db"; 7 | import { sanitize, isName, isID, isExist } from '../helpers/validate'; 8 | 9 | // related models 10 | // just importing to register it to the Bookshelf register 11 | import "./users.js"; 12 | 13 | class Users_Addresses extends Bookshelf.Model { 14 | 15 | // Initialization 16 | initialize () { 17 | 18 | // defining events for validation and other stuff 19 | this.on("creating", (model, attrs, options) => { 20 | this.attributes.ID = (this.attributes.ID) ? this.attributes.ID : crypto.randomBytes(16); 21 | this.id = this.attributes.ID; // because we are using custom id and to overwrite native properties 22 | }, this); 23 | 24 | this.on("saving", (model, attrs, options) => { 25 | 26 | // preparing the data 27 | let validateObj = {}, validateRule = {}; 28 | Object.keys(this.attributes).map( key => { 29 | // sanitizing the input 30 | this.attributes[key] = (key.includes("ID")) ? this.attributes[key] : sanitize(this.attributes[key]); 31 | 32 | validateObj[key] = (!key.includes("ID")) ? this.attributes[key] : this.attributes[key].toString("hex"); 33 | validateRule[key] = Users_Addresses.validation_rules()[key]; 34 | }); 35 | 36 | if (Object.keys(validateObj).length !== 0) { 37 | return CheckIt(validateRule).validate(validateObj); 38 | } 39 | 40 | }, this); 41 | } 42 | 43 | constructor () { 44 | super(); 45 | Bookshelf.Model.apply(this, arguments); 46 | } 47 | 48 | get tableName () { return "Users_Addresses" } // table to map with the DB 49 | 50 | get idAttribute () { return "ID" } 51 | 52 | 53 | // Relations 54 | user () { return this.belongsTo('Users', 'User_ID') } 55 | 56 | 57 | // Validation Rules 58 | static validation_rules () { return { 59 | ID: ['required', val => { 60 | if (!isID(val)) throw new Error("The ID is not valid hexadecimal"); 61 | }], 62 | 63 | User_ID: ['required', val => { 64 | if (!isID(val)) throw new Error("The ID is not valid hexadecimal"); 65 | }], 66 | 67 | Line_1: [], Line_2: [], State: [], Pincode: [], Landmark: [], 68 | }} 69 | 70 | 71 | // Helper Function 72 | get StringID () { return this.attributes.ID.toString("hex") } 73 | 74 | set StringID (string = null) { 75 | if (string === null) return false; 76 | return this.attributes.ID = new Buffer(string, "hex"); 77 | } 78 | 79 | } 80 | 81 | export default Bookshelf.model("Users_Addresses", Users_Addresses); 82 | -------------------------------------------------------------------------------- /app/models/users.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | import crypto from "crypto"; 4 | import CheckIt from "checkit"; 5 | 6 | import Bookshelf from "../db"; 7 | import { sanitize, isName, isID, isExist } from '../helpers/validate'; 8 | 9 | // related models 10 | // just importing to register it to the Bookshelf register 11 | import "./users-addresses.js"; 12 | 13 | class Users extends Bookshelf.Model { 14 | 15 | // Initialization 16 | initialize () { 17 | 18 | // defining events for validation and other stuff 19 | this.on("creating", (model, attrs, options) => { 20 | this.attributes.ID = (this.attributes.ID) ? this.attributes.ID : crypto.randomBytes(16); 21 | this.id = this.attributes.ID; // because we are using custom id and to overwrite native properties 22 | }, this); 23 | 24 | this.on("saving", (model, attrs, options) => { 25 | 26 | // preparing the data 27 | let validateObj = {}, validateRule = {}; 28 | 29 | Object.keys(this.attributes).map( key => { 30 | // sanitizing the input 31 | this.attributes[key] = (key.includes("ID")) ? this.attributes[key] : sanitize(this.attributes[key]); 32 | 33 | validateObj[key] = (!key.includes("ID")) ? this.attributes[key] : this.attributes[key].toString("hex"); 34 | validateRule[key] = Users.validation_rules()[key]; 35 | }); 36 | 37 | if (Object.keys(validateObj).length !== 0) { 38 | return CheckIt(validateRule).validate(validateObj); 39 | } 40 | 41 | }, this); 42 | } 43 | 44 | constructor () { 45 | super(); 46 | Bookshelf.Model.apply(this, arguments); 47 | } 48 | 49 | get tableName () { return "Users" } // table to map with the DB 50 | 51 | get idAttribute () { return "ID" } 52 | 53 | 54 | // Relations 55 | addresses () { return this.hasMany('Users_Addresses', 'User_ID') } 56 | 57 | 58 | // Validation Rules 59 | static validation_rules () { return { 60 | Email: ['required', 'email'], 61 | 62 | Name: ['required', val => { 63 | if (!isName(val)) throw new Error("The Name is not valid string"); 64 | }], 65 | 66 | ID: ['required', val => { 67 | if (!isID(val)) throw new Error("The ID is not valid hexadecimal"); 68 | }], 69 | 70 | Phone: ['required', 'numeric', 'exactLength:10', val => { 71 | // have to use promise to make it sync because of db query 72 | return isExist(Users, "Phone", val, "ID") 73 | .then( () => {/* let it pass, since we didn't found it in our database */}) 74 | .catch( (err) => { 75 | // console.log(err); // uncomment it to see full error 76 | if (err) throw new Error(err); 77 | else throw new Error("The phone number is already in use") 78 | }); 79 | }] 80 | }} 81 | 82 | 83 | // Helper Function 84 | get StringID () { return this.attributes.ID.toString("hex") } 85 | 86 | set StringID (string = null) { 87 | if (string === null) return false; 88 | return this.attributes.ID = new Buffer(string, "hex"); 89 | } 90 | 91 | } 92 | 93 | export default Bookshelf.model("Users", Users); 94 | -------------------------------------------------------------------------------- /app/routes.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | import Middlewares from "./helpers/middlewares"; 4 | import _root from "./controllers/root"; 5 | import _users from "./controllers/users"; 6 | 7 | module.exports = express => { 8 | 9 | // initiaizing express router 10 | const router = express.Router(); 11 | const middlewares = new Middlewares(); 12 | 13 | // middleware 14 | router.use(middlewares.applicationBase); 15 | 16 | // actual routes 17 | // router is compusary as a first arg and then we can send anything 18 | // we are sending router because its an object and object are passed by refrence 19 | _root(router, middlewares); 20 | _users(router, middlewares); 21 | 22 | // at this point router will contain all the routes and now it can be added to the express instance 23 | 24 | // return instance of router so that it can be added to the express instance 25 | return router; 26 | 27 | } 28 | -------------------------------------------------------------------------------- /env.example: -------------------------------------------------------------------------------- 1 | PORT=8080 2 | 3 | DB={db name without curly braces} 4 | DB_USERNAME={db user name without curly braces} 5 | DB_PASSWORD={db password without curly braces} 6 | 7 | JWT_SECRET=anystring 8 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | // importing our dependencies 4 | import express from "express"; 5 | import bodyParser from "body-parser"; 6 | import dotenv from "dotenv"; 7 | import logger from "morgan"; 8 | 9 | // importing our files 10 | import Bookshelf from "./app/db"; 11 | import router from "./app/routes"; 12 | 13 | 14 | // injecting environment variables 15 | dotenv.config(); 16 | 17 | 18 | // initializing server requirments 19 | const port = process.env.PORT || 3000; 20 | const app = express(); 21 | 22 | 23 | // to log every request to the console 24 | app.use(logger('dev')); 25 | 26 | 27 | // set static files (css and images, etc) location 28 | app.use(express.static('./public')); 29 | 30 | 31 | // setting up body parser 32 | app.use(bodyParser.json()); 33 | app.use(bodyParser.urlencoded({ extended: true })); 34 | 35 | 36 | // setting up routes for our app 37 | // we made router a function so that any instance can be passed from here 38 | // should be defined just before starting the server 39 | app.use('/', router(express)); 40 | 41 | 42 | // ensuring db connection and starting server 43 | Bookshelf.knex.raw(`USE \`${process.env.DB}\` `) // this way we can be sure the db is really connected 44 | .then( resp => { 45 | // start the server if only db connection is succesfull 46 | app.listen(port, () => console.log(`App started at: ${port}`) ); 47 | }) 48 | .catch( err => { 49 | // console.log(err); // uncomment it to see the actual error 50 | console.log('Unable to connect to the database; shutting down server'); 51 | process.exit(); 52 | }); 53 | -------------------------------------------------------------------------------- /knexfile.js: -------------------------------------------------------------------------------- 1 | require('dotenv').config(); 2 | 3 | module.exports = { 4 | client: 'mysql', 5 | connection: { 6 | database: process.env.DB, 7 | user: process.env.DB_USERNAME, 8 | password: process.env.DB_PASSWORD 9 | }, 10 | migrations: { 11 | tableName: 'knex_migrations' 12 | } 13 | }; 14 | -------------------------------------------------------------------------------- /migrations/20170208202452_setup.js: -------------------------------------------------------------------------------- 1 | var userCreate = 2 | "CREATE TABLE `Users` (" + 3 | "`ID` binary(16) NOT NULL," + 4 | "`Phone` bigint(10) NOT NULL," + 5 | "`Name` varchar(50) NOT NULL," + 6 | "`Email` varchar(50) NOT NULL," + 7 | "`Date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP" + 8 | ") ENGINE=InnoDB DEFAULT CHARSET=latin1;"; 9 | 10 | var userAddressCreate = 11 | "CREATE TABLE `Users_Addresses` (" + 12 | "`ID` binary(16) NOT NULL," + 13 | "`User_ID` binary(16) NOT NULL," + 14 | "`Line_1` varchar(250) NOT NULL," + 15 | "`Line_2` varchar(250) DEFAULT NULL," + 16 | "`State` varchar(250) NOT NULL," + 17 | "`Pincode` int(6) NOT NULL," + 18 | "`Landmark` varchar(250) DEFAULT NULL," + 19 | "`Date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP" + 20 | ") ENGINE=InnoDB DEFAULT CHARSET=latin1;"; 21 | 22 | 23 | var userKey = 24 | "ALTER TABLE `Users`" + 25 | "ADD PRIMARY KEY (`ID`)," + 26 | "ADD UNIQUE KEY `Phone` (`Phone`);"; 27 | 28 | var userAddressKey = 29 | "ALTER TABLE `Users_Addresses`" + 30 | "ADD PRIMARY KEY (`ID`)," + 31 | "ADD KEY `User_ID` (`User_ID`);"; 32 | 33 | 34 | var userAddressFK = 35 | "ALTER TABLE `Users_Addresses`" + 36 | "ADD CONSTRAINT `FK_Users-Addresses_Users` FOREIGN KEY (`User_ID`) REFERENCES `Users` (`ID`) ON DELETE CASCADE ON UPDATE CASCADE;"; 37 | 38 | 39 | function handleError(err) {console.log(err) } 40 | 41 | exports.up = function(knex, Promise) { 42 | return Promise.all([ 43 | // tables 44 | knex.raw(userCreate).catch(handleError), 45 | knex.raw(userAddressCreate).catch(handleError), 46 | 47 | // indexes and keys 48 | knex.raw(userKey).catch(handleError), 49 | knex.raw(userAddressKey).catch(handleError), 50 | 51 | // relations | foreign keys 52 | knex.raw(userAddressFK).catch(handleError) 53 | ]); 54 | }; 55 | 56 | exports.down = function(knex, Promise) { 57 | return Promise.all([ 58 | // dont disturb the order, if did then might get error because of fk constraints 59 | knex.schema.dropTable('Users_Addresses'), 60 | knex.schema.dropTable('Users') 61 | ]); 62 | }; 63 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nodejs-mysql-boilerplate", 3 | "version": "1.0.0", 4 | "description": "App with a very neat and modular structure to start you next nodejs project.", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "nodemon --exec babel-node --presets=es2015 index.js", 8 | "debug": "DEBUG=knex:bindings nodemon --inspect --exec babel-node --presets=es2015 index.js", 9 | "migrate-up": "DEBUG=knex:tx knex migrate:latest", 10 | "migrate-down": "DEBUG=knex:tx knex migrate:rollback" 11 | }, 12 | "author": "Raghav Garg ", 13 | "repository": { 14 | "type": "git", 15 | "url": "git://github.com/raghavgarg1257/nodejs-mysql-boilerplate" 16 | }, 17 | "license": "ISC", 18 | "dependencies": { 19 | "body-parser": "^1.15.2", 20 | "bookshelf": "^0.10.3", 21 | "checkit": "^0.7.0", 22 | "dotenv": "^4.0.0", 23 | "express": "^4.14.0", 24 | "jsonwebtoken": "^8.0.0", 25 | "knex": "^0.12.6", 26 | "mysql": "^2.13.0", 27 | "validator": "^6.2.1" 28 | }, 29 | "devDependencies": { 30 | "babel-cli": "^6.18.0", 31 | "babel-core": "^6.21.0", 32 | "babel-preset-env": "^1.1.5", 33 | "babel-preset-latest": "^6.22.0", 34 | "morgan": "^1.7.0" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /public/index.js: -------------------------------------------------------------------------------- 1 | $( () => { 2 | // static/client js 3 | }); 4 | --------------------------------------------------------------------------------