├── .babelrc ├── .gitignore ├── preview.gif ├── src ├── config │ └── index.js ├── router.js ├── index.js ├── models │ └── user.js ├── controllers │ └── authentication.js └── services │ └── passport.js ├── dist ├── config │ └── index.js ├── router.js ├── index.js ├── models │ └── user.js ├── controllers │ └── authentication.js └── services │ └── passport.js ├── .eslintrc ├── readme.md └── package.json /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015"] 3 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /.idea 3 | .DS_Store 4 | npm-debug.log -------------------------------------------------------------------------------- /preview.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DimiMikadze/es6-node-starter/HEAD/preview.gif -------------------------------------------------------------------------------- /src/config/index.js: -------------------------------------------------------------------------------- 1 | export const dbConfig = { 2 | secret: 'Secret', 3 | db: 'mongodb://localhost:auth/auth', 4 | }; 5 | -------------------------------------------------------------------------------- /dist/config/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | var dbConfig = exports.dbConfig = { 7 | secret: 'Secret' 8 | }; -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "airbnb", 3 | "rules": { 4 | "consistent-return": 0, 5 | "no-shadow": 0, 6 | "no-console": 0, 7 | "no-unused-vars": 0, 8 | "func-names": 0 9 | } 10 | } -------------------------------------------------------------------------------- /src/router.js: -------------------------------------------------------------------------------- 1 | import { signin, signup } from './controllers/authentication'; 2 | import passport from 'passport'; 3 | 4 | const passportService = require('./services/passport'); 5 | 6 | const requireAuth = passport.authenticate('jwt', { session: false }); 7 | const requireSignin = passport.authenticate('local', { session: false }); 8 | 9 | const router = (app) => { 10 | app.get('/', requireAuth, (req, res) => { 11 | res.send({ message: 'Success!' }); 12 | }); 13 | app.post('/signin', requireSignin, signin); 14 | app.post('/signup', signup); 15 | }; 16 | 17 | export default router; 18 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import express from 'express'; 2 | import http from 'http'; 3 | import bodyParser from 'body-parser'; 4 | import morgan from 'morgan'; 5 | import mongoose from 'mongoose'; 6 | import cors from 'cors'; 7 | import compression from 'compression'; 8 | import router from './router'; 9 | import { dbConfig } from './config'; 10 | 11 | const app = express(); 12 | 13 | // DB setup 14 | mongoose.connect(dbConfig.db); 15 | 16 | // App setup 17 | app.use(compression()); 18 | app.use(morgan('combined')); 19 | app.use(cors()); 20 | app.use(bodyParser.json({ type: '*/*' })); 21 | router(app); 22 | 23 | // Server setup 24 | const port = process.env.PORT || 3000; 25 | const server = http.createServer(app); 26 | server.listen(port); 27 | console.log('server listening on:', port); 28 | -------------------------------------------------------------------------------- /dist/router.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | var _authentication = require('./controllers/authentication'); 8 | 9 | var _passport = require('passport'); 10 | 11 | var _passport2 = _interopRequireDefault(_passport); 12 | 13 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 14 | 15 | var passportService = require('./services/passport'); 16 | 17 | var requireAuth = _passport2.default.authenticate('jwt', { session: false }); 18 | var requireSignin = _passport2.default.authenticate('local', { session: false }); 19 | 20 | var router = function router(app) { 21 | app.get('/', requireAuth, function (req, res) { 22 | res.send({ message: 'Super secret code' }); 23 | }); 24 | app.post('/signin', requireSignin, _authentication.signin); 25 | app.post('/signup', _authentication.signup); 26 | }; 27 | 28 | exports.default = router; -------------------------------------------------------------------------------- /src/models/user.js: -------------------------------------------------------------------------------- 1 | import mongoose from 'mongoose'; 2 | import bcrypt from 'bcrypt-nodejs'; 3 | 4 | const Schema = mongoose.Schema; 5 | 6 | // Define our model 7 | const userSchema = new Schema({ 8 | email: { type: String, unique: true, lowercase: true }, 9 | password: String, 10 | }); 11 | 12 | // On save hook, encrypt password 13 | // Before saving a model, run this function 14 | userSchema.pre('save', function (next) { 15 | // get access to the user model 16 | const user = this; 17 | 18 | // generate a salt then run callback 19 | bcrypt.genSalt(10, (err, salt) => { 20 | if (err) { return next(err); } 21 | 22 | // hash (encrypt) our password using the salt 23 | bcrypt.hash(user.password, salt, null, (err, hash) => { 24 | if (err) { return next(err); } 25 | 26 | // overwrite plain text password with encrypted password 27 | user.password = hash; 28 | next(); 29 | }); 30 | }); 31 | }); 32 | 33 | userSchema.methods.comparePassword = function (candidatePassword, callback) { 34 | bcrypt.compare(candidatePassword, this.password, (err, isMatch) => { 35 | if (err) { return callback(err); } 36 | 37 | callback(null, isMatch); 38 | }); 39 | }; 40 | 41 | // Create the model class 42 | const ModelClass = mongoose.model('user', userSchema); 43 | 44 | // Export the model 45 | export default ModelClass; 46 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # ES6/7 Node starter kit with built in Rest API authentication 2 | 3 | ## Features 4 | 5 | - Babel configuration 6 | - Nodemon for automatically restart the dev server 7 | - Production environment configuration 8 | - Built in rest API authentication 9 | - MongoDB configuration 10 | - Linting with Airbnb eslint configuration 11 | 12 | ## Preview 13 | 14 | ![Preview](preview.gif?raw=true "Preview") 15 | 16 | ## Getting Started 17 | 18 | Clone Repo 19 | 20 | ```` 21 | git clone https://github.com/DimitriMikadze/es6-node-starter.git 22 | ```` 23 | 24 | npm install dependencies 25 | 26 | ```` 27 | cd es6-node-starter 28 | 29 | npm install 30 | ```` 31 | 32 | ### Start MongoDB 33 | 34 | ```` 35 | mongod 36 | ```` 37 | 38 | ### Start development server 39 | 40 | ```` 41 | npm run dev 42 | ```` 43 | 44 | ### Linting 45 | 46 | For linting i'm using Eslint with Airbnb Eslint configuration 47 | 48 | ```` 49 | npm run lint 50 | ```` 51 | 52 | ### Production 53 | 54 | Build for production 55 | 56 | ```` 57 | npm run build 58 | ```` 59 | 60 | Start production server 61 | 62 | ```` 63 | npm run start 64 | ```` 65 | 66 | Note: I'm using pm2 for production server, you should install it on server via 'npm install pm2 -g'. 67 | if you don't want to use pm2, just change pm2 with node in package.json file in scripts section. 68 | 69 | ### Contributing 70 | 71 | contributions are welcome! 72 | 73 | ### License 74 | 75 | MIT -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "es6-node-starter", 3 | "version": "1.0.0", 4 | "description": "ES6 Node Starter Kit with built in rest api authentication", 5 | "scripts": { 6 | "dev": "nodemon --exec babel-node src/index.js", 7 | "build": "rm -rf dist && babel src --out-dir dist", 8 | "start": "PORT=8080 pm2 start dist/index.js", 9 | "lint": "eslint src" 10 | }, 11 | "keywords": [ 12 | "ES6", 13 | "ES7", 14 | "ExpressJS", 15 | "MongoDB", 16 | "Authentication", 17 | "Starter kit", 18 | "Airbnb Eslint", 19 | "Nodemon", 20 | "Babel" 21 | ], 22 | "repository": { 23 | "type": "git", 24 | "url": "git+https://github.com/DimitriMikadze/es6-node-starter" 25 | }, 26 | "author": "Dimtiri Mikadze", 27 | "license": "MIT", 28 | "devDependencies": { 29 | "babel-cli": "^6.9.0", 30 | "babel-preset-es2015": "^6.9.0", 31 | "eslint": "^2.11.1", 32 | "eslint-config-airbnb": "^9.0.1", 33 | "eslint-plugin-import": "^1.8.1", 34 | "eslint-plugin-jsx-a11y": "^1.3.0", 35 | "eslint-plugin-react": "^5.1.1", 36 | "nodemon": "^1.9.2" 37 | }, 38 | "dependencies": { 39 | "bcrypt-nodejs": "0.0.3", 40 | "body-parser": "^1.15.1", 41 | "compression": "^1.6.2", 42 | "cors": "^2.7.1", 43 | "express": "^4.13.4", 44 | "jwt-simple": "^0.5.0", 45 | "mongoose": "^4.4.16", 46 | "morgan": "^1.7.0", 47 | "passport": "^0.3.2", 48 | "passport-jwt": "^2.0.0", 49 | "passport-local": "^1.0.0" 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/controllers/authentication.js: -------------------------------------------------------------------------------- 1 | import jwt from 'jwt-simple'; 2 | import User from '../models/user'; 3 | import { dbConfig } from '../config'; 4 | 5 | const tokenForUser = (user) => { 6 | const timestamp = new Date().getTime(); 7 | 8 | return jwt.encode({ sub: user.id, iat: timestamp }, dbConfig.secret); 9 | }; 10 | 11 | export const signin = (req, res) => { 12 | // User has already had their email and password auth'd 13 | // We just need to give them a token 14 | res.send({ token: tokenForUser(req.user) }); 15 | }; 16 | 17 | export const signup = (req, res, next) => { 18 | const email = req.body.email; 19 | const password = req.body.password; 20 | 21 | if (!email || !password) { 22 | return res.status(422).send({ error: 'You must provide email and password' }); 23 | } 24 | 25 | // see if a user with the given email exists 26 | User.findOne({ email }, (err, existingUser) => { 27 | if (err) { return next(err); } 28 | 29 | // if a user with email does exists, return an error 30 | if (existingUser) { 31 | return res.status(422).send({ error: 'Email is in use' }); 32 | } 33 | 34 | // if a user with email does not exist, create and save user record 35 | const user = new User({ 36 | email, 37 | password, 38 | }); 39 | 40 | user.save((err) => { 41 | if (err) { return next(err); } 42 | 43 | // respond to request indicating the user was created 44 | res.json({ token: tokenForUser(user) }); 45 | }); 46 | }); 47 | }; 48 | -------------------------------------------------------------------------------- /dist/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var _express = require('express'); 4 | 5 | var _express2 = _interopRequireDefault(_express); 6 | 7 | var _http = require('http'); 8 | 9 | var _http2 = _interopRequireDefault(_http); 10 | 11 | var _bodyParser = require('body-parser'); 12 | 13 | var _bodyParser2 = _interopRequireDefault(_bodyParser); 14 | 15 | var _morgan = require('morgan'); 16 | 17 | var _morgan2 = _interopRequireDefault(_morgan); 18 | 19 | var _mongoose = require('mongoose'); 20 | 21 | var _mongoose2 = _interopRequireDefault(_mongoose); 22 | 23 | var _cors = require('cors'); 24 | 25 | var _cors2 = _interopRequireDefault(_cors); 26 | 27 | var _compression = require('compression'); 28 | 29 | var _compression2 = _interopRequireDefault(_compression); 30 | 31 | var _router = require('./router'); 32 | 33 | var _router2 = _interopRequireDefault(_router); 34 | 35 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 36 | 37 | var app = (0, _express2.default)(); 38 | 39 | // DB setup 40 | _mongoose2.default.connect('mongodb://localhost:auth/auth'); 41 | 42 | // App setup 43 | app.use((0, _compression2.default)()); 44 | app.use((0, _morgan2.default)('combined')); 45 | app.use((0, _cors2.default)()); 46 | app.use(_bodyParser2.default.json({ type: '*/*' })); 47 | (0, _router2.default)(app); 48 | 49 | // Server setup 50 | var port = process.env.PORT || 3000; 51 | var server = _http2.default.createServer(app); 52 | server.listen(port); 53 | console.log('server listening on:', port); -------------------------------------------------------------------------------- /dist/models/user.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | var _mongoose = require('mongoose'); 8 | 9 | var _mongoose2 = _interopRequireDefault(_mongoose); 10 | 11 | var _bcryptNodejs = require('bcrypt-nodejs'); 12 | 13 | var _bcryptNodejs2 = _interopRequireDefault(_bcryptNodejs); 14 | 15 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 16 | 17 | var Schema = _mongoose2.default.Schema; 18 | 19 | // Define our model 20 | var userSchema = new Schema({ 21 | email: { type: String, unique: true, lowercase: true }, 22 | password: String 23 | }); 24 | 25 | // On save hook, encrypt password 26 | // Before saving a model, run this function 27 | userSchema.pre('save', function (next) { 28 | // get access to the user model 29 | var user = this; 30 | 31 | // generate a salt then run callback 32 | _bcryptNodejs2.default.genSalt(10, function (err, salt) { 33 | if (err) { 34 | return next(err); 35 | } 36 | 37 | // hash (encrypt) our password using the salt 38 | _bcryptNodejs2.default.hash(user.password, salt, null, function (err, hash) { 39 | if (err) { 40 | return next(err); 41 | } 42 | 43 | // overwrite plain text password with encrypted password 44 | user.password = hash; 45 | next(); 46 | }); 47 | }); 48 | }); 49 | 50 | userSchema.methods.comparePassword = function (candidatePassword, callback) { 51 | _bcryptNodejs2.default.compare(candidatePassword, this.password, function (err, isMatch) { 52 | if (err) { 53 | return callback(err); 54 | } 55 | 56 | callback(null, isMatch); 57 | }); 58 | }; 59 | 60 | // Create the model class 61 | var ModelClass = _mongoose2.default.model('user', userSchema); 62 | 63 | // Export the model 64 | exports.default = ModelClass; -------------------------------------------------------------------------------- /src/services/passport.js: -------------------------------------------------------------------------------- 1 | import passport from 'passport'; 2 | import User from '../models/user'; 3 | import { dbConfig } from '../config'; 4 | import LocalStrategy from 'passport-local'; 5 | 6 | const JwtStrategy = require('passport-jwt').Strategy; 7 | const ExtractJwt = require('passport-jwt').ExtractJwt; 8 | 9 | // Create local strategy 10 | const localOptions = { usernameField: 'email' }; 11 | const localLogin = new LocalStrategy(localOptions, (email, password, done) => { 12 | // Verify this email and password, call done with the user 13 | // if it is the correct email and password 14 | // otherwise, call done with false 15 | User.findOne({ email }, (err, user) => { 16 | if (err) { return done(err); } 17 | 18 | if (!user) { return done(null, false); } 19 | 20 | // compare passwords - is 'password' equal to user.password ? 21 | user.comparePassword(password, (err, isMatch) => { 22 | if (err) { return done(err); } 23 | 24 | if (!isMatch) { return done(null, false); } 25 | 26 | return done(null, user); 27 | }); 28 | }); 29 | }); 30 | 31 | // Setup options for JWT strategy 32 | const jwtOptions = { 33 | jwtFromRequest: ExtractJwt.fromHeader('authorization'), 34 | secretOrKey: dbConfig.secret, 35 | }; 36 | 37 | // Create JWT strategy 38 | const jwtLogin = new JwtStrategy(jwtOptions, (payload, done) => { 39 | // See if the user ID in the payload exists in our database 40 | // If it does, call 'done' with that user 41 | // otherwise, call done without a user object 42 | User.findById(payload.sub, (err, user) => { 43 | console.log('hi'); 44 | if (err) { return done(err, false); } 45 | 46 | if (user) { 47 | done(null, user); 48 | } else { 49 | done(null, false); 50 | } 51 | }); 52 | }); 53 | 54 | // Tell passport to use this strategy 55 | passport.use(jwtLogin); 56 | passport.use(localLogin); 57 | -------------------------------------------------------------------------------- /dist/controllers/authentication.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.signup = exports.signin = undefined; 7 | 8 | var _jwtSimple = require('jwt-simple'); 9 | 10 | var _jwtSimple2 = _interopRequireDefault(_jwtSimple); 11 | 12 | var _user = require('../models/user'); 13 | 14 | var _user2 = _interopRequireDefault(_user); 15 | 16 | var _config = require('../config'); 17 | 18 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 19 | 20 | var tokenForUser = function tokenForUser(user) { 21 | var timestamp = new Date().getTime(); 22 | 23 | return _jwtSimple2.default.encode({ sub: user.id, iat: timestamp }, _config.dbConfig.secret); 24 | }; /* eslint-disable consistent-return */ 25 | 26 | 27 | var signin = exports.signin = function signin(req, res) { 28 | // User has already had their email and password auth'd 29 | // We just need to give them a token 30 | res.send({ token: tokenForUser(req.user) }); 31 | }; 32 | 33 | var signup = exports.signup = function signup(req, res, next) { 34 | var email = req.body.email; 35 | var password = req.body.password; 36 | 37 | if (!email || !password) { 38 | return res.status(422).send({ error: 'You must provide email and password' }); 39 | } 40 | 41 | // see if a user with the given email exists 42 | _user2.default.findOne({ email: email }, function (err, existingUser) { 43 | if (err) { 44 | return next(err); 45 | } 46 | 47 | // if a user with email does exists, return an error 48 | if (existingUser) { 49 | return res.status(422).send({ error: 'Email is in use' }); 50 | } 51 | 52 | // if a user with email does not exist, create and save user record 53 | var user = new _user2.default({ 54 | email: email, 55 | password: password 56 | }); 57 | 58 | user.save(function (err) { 59 | if (err) { 60 | return next(err); 61 | } 62 | 63 | // respond to request indicating the user was created 64 | res.json({ token: tokenForUser(user) }); 65 | }); 66 | }); 67 | }; -------------------------------------------------------------------------------- /dist/services/passport.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var _passport = require('passport'); 4 | 5 | var _passport2 = _interopRequireDefault(_passport); 6 | 7 | var _user = require('../models/user'); 8 | 9 | var _user2 = _interopRequireDefault(_user); 10 | 11 | var _config = require('../config'); 12 | 13 | var _passportLocal = require('passport-local'); 14 | 15 | var _passportLocal2 = _interopRequireDefault(_passportLocal); 16 | 17 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 18 | 19 | var JwtStrategy = require('passport-jwt').Strategy; 20 | var ExtractJwt = require('passport-jwt').ExtractJwt; 21 | 22 | // Create local strategy 23 | var localOptions = { usernameField: 'email' }; 24 | var localLogin = new _passportLocal2.default(localOptions, function (email, password, done) { 25 | // Verify this email and password, call done with the user 26 | // if it is the correct email and password 27 | // otherwise, call done with false 28 | _user2.default.findOne({ email: email }, function (err, user) { 29 | if (err) { 30 | return done(err); 31 | } 32 | 33 | if (!user) { 34 | return done(null, false); 35 | } 36 | 37 | // compare passwords - is 'password' equal to user.password ? 38 | user.comparePassword(password, function (err, isMatch) { 39 | if (err) { 40 | return done(err); 41 | } 42 | 43 | if (!isMatch) { 44 | return done(null, false); 45 | } 46 | 47 | return done(null, user); 48 | }); 49 | }); 50 | }); 51 | 52 | // Setup options for JWT strategy 53 | var jwtOptions = { 54 | jwtFromRequest: ExtractJwt.fromHeader('authorization'), 55 | secretOrKey: _config.dbConfig.secret 56 | }; 57 | 58 | // Create JWT strategy 59 | var jwtLogin = new JwtStrategy(jwtOptions, function (payload, done) { 60 | // See if the user ID in the payload exists in our database 61 | // If it does, call 'done' with that user 62 | // otherwise, call done without a user object 63 | _user2.default.findById(payload.sub, function (err, user) { 64 | if (err) { 65 | return done(err, false); 66 | } 67 | 68 | if (user) { 69 | done(null, user); 70 | } else { 71 | done(null, false); 72 | } 73 | }); 74 | }); 75 | 76 | // Tell passport to use this strategy 77 | _passport2.default.use(jwtLogin); 78 | _passport2.default.use(localLogin); --------------------------------------------------------------------------------