Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Lightweight Node.js server with lots of features to bootstrap your project.

License

sonttran/server

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

36 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Vpop

Multi-purpose, lightweight, easy to expand, shipped with lots of features Node.js server to bootstrap your project. Technologies used: Node.js, ExpressJS, MongoDB, Mongoose, JSON Web Token, Node Mailer, PM2, socket.io, PassportJS

Table of content

Features

Install

  • Make sure you have Node.js installed before running the following commands
git clone https://github.com/sonttran/server.git
cd server
npm install

Configure and start your server

  • All configurations are made in ONE file config.json
  • This configuration is for PM2 to daemonize your server process. It's highly recommended to get familiar with PM2 before continuing.
  • Install MongoDB and have your connection string ready
  • Register Facebook app if you want to integrate Facebook login (optional)
  • Install PM2 to your local and server machine
sudo npm install pm2 -g
pm2 install pm2-server-monit
  • Have your email account for system mail (if not, user related API will result errors)
  • Complete information in config.json file
{
    apps : [
        {
            name                    : "server", // name of process in PM2 manager console
            script                  : "./server.js", // file to bootstrap
            exec_mode               : "cluster", // for scaling
            instances               : 1, // # of processes at start
            watch                   : ["core","server.js"], // files to watch for auto restart
            env_local               : { // your "local" environment in `pm2 start config.json --env local`
                "NODE_ENV"          : "local", // e.g. process.env.NODE_ENV = "local"
                "PORT"              : 3000, // server port
                "HOST"              : '0.0.0.0', // server host
                "DB_URL"            : 'mongodb://localhost:27017/app', // connection string to MongoDB
                "JWT_SECRET"        : 'Breaking Dawn', // json web token secret
                "SESSION_TIMER"     : "7 days", // time config for user session
                "FORGOT_PASS_TIMER" : "24h", // time config for password recovery token
                "FB_CLIENT_ID"      : "111100106689192", // Facebook ID for login with Facebook
                "FB_CLIENT_SEC"     : "111111111101330ab2e7672142d06040", // Facebook secret
                "FB_CALLBACK_URL"   : "http://localhost:3000/api/v1/loginWithFacebookCb", // callback
                "PUBLIC"            : "/Users/sontran/Doc/server/public/", // path to your public folder 
                "FILES"             : "/Users/sontran/FILES/server", // path to store your server files
                "MAIL_NO_REPLY"     : {service: "gmail", // config server mail
                                       auth: {
                                           user: "[email protected]",
                                           pass: "password"
                                       }
                                      },
                "ERR_MAIL"          : {service: "gmail", // config system mail
                                       auth: {
                                           user: "[email protected]",
                                           pass: "password"
                                       }
                                      },
                "SERVER_ADMIN_EMAIL": '[email protected]', // email to receive system alerts
                "SERVER_NAME"       : "Local Server", // name of your server in email
            },
            env_production          : { // your "production" environment in remote server with `pm2 start config.json --env production`
                "NODE_ENV"          : "production", 
                "PORT"              : 5600,
                "HOST"              : '127.0.0.1',
                "DB_URL"            : 'mongodb://localhost:27017/production',
                "JWT_SECRET"        : '111111',
                "SESSION_TIMER"     : "30 days",
                "FORGOT_PASS_TIMER" : "24h",
                "FB_CLIENT_ID"      : "12345678901234",
                "FB_CLIENT_SEC"     : "123456789023456789234567893456",
                "FB_CALLBACK_URL"   : "https://example.com/api/v1/loginWithFacebookCb",
                "PUBLIC"            : "/home/producttion/yourapp/current/public",
                "FILES"             : "/home/producttion/FILES/",
                "MAIL_NO_REPLY"     : {service: "gmail",
                                       auth: {
                                           user: "[email protected]",
                                           pass: "1234567890"
                                       }
                                      },
                "ERR_MAIL"          : {service: "gmail",
                                       auth: {
                                           user: "[email protected]",
                                           pass: "1234567890"
                                       }
                                      },
                "SERVER_ADMIN_EMAIL": '[email protected]',
                "SERVER_NAME"       : "Production Server",
            },
        }
    ],
    "deploy"                        : { // for staging server deployment
        "staging"                   : { // "staging" in `pm2 deploy config.json staging setup`
            "user"                  : "username", // server user
            "host"                  : ["57.57.57.57"], // server IP
            "ref"                   : "origin/master",
            "repo"                  : "[email protected]:yourcompany.com/yourapp.git", // git link
            "key"                   : "/path/to/key.pem", // path to server pem key in your local
            "path"                  : "/path/to/app", // path to app in your REMOTE server
            "ssh_options"           : "StrictHostKeyChecking=no",
            "pre-deploy-local"      : "echo 'This is a local executed command'",
            "post-deploy"           : "npm install && pm2 startOrRestart config.json --env staging",
            "env"  : {
                "NODE_ENV"          : "production",
            }
        },
        "production"                : { // for production server deployment
            "user"                  : "username",
            "host"                  : ["229.229.229.229"],
            "ref"                   : "origin/master",
            "repo"                  : "[email protected]:yourcompany.com/yourapp.git",
            "key"                   : "/path/to/key.pem",
            "path"                  : "/path/to/app",
            "ssh_options"           : "StrictHostKeyChecking=no",
            "pre-deploy-local"      : "echo 'This is a local executed command'",
            "post-deploy"           : "npm install && pm2 startOrRestart config.json --env production",
            "env"  : {
                "NODE_ENV"          : "production",
            }
        },
    }
}
Start and tail your server locally after all configuration completed
pm2 start config.json --env local
pm2 logs server
Deploy your server in remote server
  • Get your file folder and put it config file "FILES" : "/home/producttion/FILES/"
  • Clone your repo to server
pm2 deploy config.json staging setup
  • Get your app public folder and put it in config file "PUBLIC" : "/home/producttion/yourapp/current/public"
  • Start your server remotely with
pm2 deploy config.json staging update
Link your remote server with PM2 console for monitoring
  • Create your free account at PM2 app
  • Follow simple linking instructions on PM2 app. The result will look like this PM2 console

Usage

  • /core/db-structure contains all Mongoose schemas
  • /core/palmot.js contains all ExpressJS middlewares
  • /core/v1-api.js contains all APIs
  • /core/routes.js contains all definitions of APIs and webpage routes
  • /views contains all Handlebars templates

Take less than 60 seconds to add a new API

  • In /core/v1-api.json add your api and register it
this.permission = { // register api and its permission to list

    // API name  API permission

    myNewAPI     : ['master', 'admin', 'user', 'public'], // all user type in list can access this API
    // ...
}

this.myNewAPI = function(req, res, cb) {
    cb(null, {myNewAPI : 'add successful!'}); // done
}
  • Try your newly added API. Open your web browser, go to
http://localhost:300/api/v1/myNewAPI
  • API reponse
{
    "SUCCESS": "api called: 'myNewAPI'",
    "myNewAPI": "add successful!"
}
  • API return errors (you can pass Node.js Error Object. Non-production environment will give more insight about error)
this.myNewAPI = function(req, res, cb) {
    // ... your code
    cb({message: 'something went wrong!' }, null); // done
}
  • Error response from API call http://localhost:300/api/v1/myNewAPI
{
    "ERROR": "api called: 'myNewAPI'",
    "errorMessage": "something went wrong!"
}
    cb(err, null) // error callback
    cb(null, {}) // success callback
  • Note: API accepts all http methods (GET, POST, PUT, DELETE, ...)

Built-in user role-based access control for API request

  • When user created, user permission is defined in user schema /core/db-structure/user.js
    userPermission : { type : String, default : 'user' },
  • When user logins successfully, a token is issued and stored in user's browser (API login)
  • When creating an API in /core/v1-api.js, make sure to list all user permission to that api.
this.permission = { // register api and its permission to list
    api1 : ['master', 'admin', 'user', 'public'], // every body can access
    api2 : ['master', 'admin', 'user'],           // registered user and up
    api3 : ['master', 'admin'],                   // web admin and up
    api4 : ['master'],                            // only system master 
}
  • See checkAPIpermission middlewares in /core/palmot.js for more details

Built-in user role-based access control for webpage request

  • All routes definitions and access permissions are defined in /core/routes.js
  • Register your routes (APIs and webpages)
this.pageRoutes = { // lv1 page routes
    // route                                hbs                     page permission
    '/'                                 : ['home',                 'master','admin','user','public'],
    '/user-dashboard'                   : ['userPage',             'master','admin','user'],
    '/admin-dashboard'                  : ['adminPage',            'master','admin'],
    '/web-master-dashboard'             : ['masterPage',           'master'],
    '/login'                            : ['login',                'master','admin','user','public'],
    '/verify-email'                     : ['verifyEmail',          'master','admin','user','public'],
    '/update-profile'                   : ['updateProfile',        'master','admin','user','public'],
};
  • Define pattern of your routes
this.apiRoutes = /^\/api\/v1\/([a-zA-Z0-9]+)$/; // /lv1/lv2/lv3 api routes
this.fileRoutes = /^\/file\//; // /lv1/lv2 file routes
  • Check for route permission with checkRoutePermission middleware
  • Finally, load all routes to ExpressJS
this.loadRoutes = function(app) {
    app.all('/', palmot.getUser, palmot.renderPage);
    app.all('/:lv1', palmot.getUser, palmot.renderPage);
    app.all('/:lv1/:lv2/:lv3', palmot.checkAPIpermission, palmot.callAPI);
}
  • Notes: 100% of routes are controled. If you don't define your routes and loads it, server will redirect to Not Found page.

Shipped with basic user management: registration, email verification, ...

  • Server is shipped with user management APIs and its permissions. Ready to use to bootstrap your application
    createUser              : ['master', 'admin', 'user', 'public'],
    getUser                 : ['master', 'admin', 'user'],
    updateUser              : ['master', 'admin'],
    delUser                 : ['master', 'admin', 'user'],
    login                   : ['master', 'admin', 'user', 'public'],
    logout                  : ['master', 'admin', 'user'],
    changeName              : ['master', 'admin', 'user'],
    changeAvatar            : ['master', 'admin', 'user'],
    changePassword          : ['master', 'admin', 'user'],
    sendResetPassLink       : ['master', 'admin', 'user', 'public'],
    resetPassword           : ['master', 'admin', 'user', 'public'],
    sendVerificationEmail   : ['master', 'admin', 'user'],
    verifyEmail             : ['master', 'admin', 'user', 'public'],
    loginWithFacebook       : ['master', 'admin', 'user', 'public'],
    loginWithFacebookCb     : ['master', 'admin', 'user', 'public'],
    updateProfile           : ['master', 'admin', 'user'],

Shipped with mailing capability (send mail to user, system admin, ...)

  • Server is integated with Node mailer by default. Example reportError API to system admin
this.reportError = function(err) {
    var now = moment();
    var errDate = now.format('YYYY/MM/DD HH:mm:ss Z');
    var errAt = err.stack.match(/\/.+?(?=\))/)[0];
    var mail = {
        from        : `${process.env.SERVER_NAME} <${errReportMail.auth.user}>`, // sender email
        to          : `${process.env.SERVER_ADMIN_EMAIL}`, // list of receivers
        subject     : `Exception thrown`,
        html        : `<html><body>
            <p><b>Date</b>          : ${errDate}</p>
            <p><b>Name</b>          : ${err.name}</p>
            <p><b>Message</b>       : ${err.message}</p>
            <p><b>At</b>            : ${errAt}</p>
            <p><b>Full error</b>    : ${err.stack}</p>
            </body></html>` // html body
    };
    transporter.sendMail(mail, function(err, info) { if(err) { _this.reportError(err) }});
    var errToSave = {
        dateDis     : errDate,
        date        : Date.now(),
        name        : err.name,
        message     : err.message,
        at          : errAt,
    }
    system.findById('error').then(retError => {
        if(retError) {
            retError.err.push(errToSave);
            retError.save();
        } else {
            system.create({name: 'error', _id:'error',err:[errToSave]}).catch(err => palmot.log(err));
        }
    }).catch(err => _this.reportError(err));
}

Shipped with file upload API

  • File upload capability uses Multer. Modify storage or upload object in /core/v1-api.js for Multer settings if needed. File upload example
this.changeAvatar = function(req, res, cb) {
    upload(req, res, function(err) {
        if(err) { cb(err, null) } else if(req.files && req.files.length) {
            user.findByIdAndUpdate(req.decoded.username, {$set : {
                avatar      : '/file/' + req.files[0].filename
            }}, {new: false}).then(retUser => {
                cb(null, {});
            }).catch(err => { cb(err, null) });
        } else { cb(null, {err : 'no file sent'}) };
    });
}

Shipped with integrated Handlebars engine for HTML rendering

  • Configuration for Handlebars engine for Express is in /server.js file. It's highly recommended to familiar yourself with Handlebars engine for Express before moving on.
  • Main layout file /views/layouts/main.hbs
  • /view/partials folder contains your .hbs partials such as webpage footer, navbar
  • Webpage templates are in /views folders such as home.hbs, login.hbs
  • Steps to add new page:
  • Register routes in /core/routes.js
  • Register route to renderPage (internal used) API in /core/v1-api.js
  • Note: the idea to pass the rendering steps like above is to centralize all APIs to a single file.

Integrated socket.io real time engine (turn it on/off)

  • To turn socket.io on
  • Uncomment socket.io line in /server.js
    var io  = require('socket.io').listen(server);
  • Uncomment socket.io line in /core/palmot.js (there are two of them)
    if(api.permission.useIO.indexOf(apiName) > -1) { req.io = _this.io };
    if(api.permission.useIO.indexOf(apiName) > -1) { req.io = _this.io };
  • Create your socket.io API in /core/v1-api.js
this.io = function(req, res, cb) {
    var io = req.io;
    io.sockets.emit('broadcast', req.query.mess);
    cb(null, {broadcasted : req.query.mess});
}
  • Recognize the API as a socket.io API (this is to pass socket.io object to req.io)
this.permission = { // register api and its permission to list

    // API name                         API permission

    // socket.io
    useIO                               : ['io'], // api uses realtime engine
        
}
  • Register user permission for it as normal
this.permission = { // register api and its permission to list

    // API name                         API permission

    // socket.io
    
    io                                  : ['master'], // only system master can use this API
}

Easy to scale with PM2 (for small to mid-level size project)

pm2 scale [app-name] 10 // forked app to 10 instances
  • On PM2 web console (not free feature)

About

Lightweight Node.js server with lots of features to bootstrap your project.

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Contributors 2

  •  
  •