📜 ⬆️ ⬇️

Basic Node.JS application using express

Hello.
I was looking for an article on how to make a basic Node.JS application using express, or rather, what the basic structure should be for a project, but I did not find anything like it.
Therefore I decided to write my own, in order to explain in the same way as I how to do it and how it should look.

Details under the cut. Caution. Lots of text and code.

Before starting, I want to note that this is my first article. I may not be taking into account something, or vice versa, focusing on something more attention, I will be grateful for the amendments and clarifications on the article, as well as on the approach.

The task was the following: to make a basic application that could process requests, and display the correct pages, or the correct answers to requests.
')
So. Let's start with the modules used inside the application:

express -  ,   http- mongoose - ,     MongoDB mongodb - native-driver    MongoDB  connect-mongo -    express  session node-uuid -      (   -) async -      ,  Promise ejs-locals -  ,     nconf -       ( config.json) string -      ,      ,  html    validator -   winston -     


Each of the modules can be installed using the command:
npm install << module_name >> --save

--save is needed to save the module in dependency (package.json), to further deploy the application on other machines.

The structure of the application is obtained as follows:

  /config config.json index.js /middleware checkAuth.js errorHandler.js index.js /models user.js /public /*JS, CSS, HTML static files*/ /routes authentication.js error.js index.js main.js register.js /utils index.js log.js mongoose.js validate.js /views index.ejs manage.js package.json server.js 


Now, strictly speaking, I will explain the salt of each of the directories and its scripts.
Let's start with the most important script that initiates all our applications.

server.js


 var express = require('express'), http = require('http'), app = express(), middleware = require('./middleware')(app, express), config = require('./config'), log = require('./utils/log')(app, module); http.createServer(app).listen(config.get('port'), function(){ log.info('Express server listening on port ' + config.get('port')); }); 


In server.js, we create an epxress app , connect the middleware module in which all the necessary middleware applications are connected.
Next, create a server that will handle all incoming connections through the port that is specified in the config.

package.json


 { "name": "test_express_app", "version": "0.0.1", "private": true, "scripts": { "start": "node server.js" }, "dependencies": { "express": "~3.4.6", "mongoose": "~3.8.1", "node-uuid": "~1.4.1", "nconf": "~0.6.9", "winston": "~0.7.2", "async": "~0.2.9", "mongodb": "~1.3.22", "ejs-locals": "~1.0.2", "connect-mongo": "~0.4.0", "validator": "~2.0.0", "string": "~1.7.0" } } 


It contains all the necessary information about the project, as well as all the required packages.

manage.js


 var mongoose = require('./utils/mongoose'), async = require('async'), User = require('./models/user'), log = require('./utils/log')(null, module), config = require('./config'); function openConnection(cb) { mongoose.connection.on('open', function () { log.info('connected to database ' + config.get('db:name')); cb(); }); } function dropDatabase(cb) { var db = mongoose.connection.db; db.dropDatabase(function () { log.info('dropped database ' + config.get('db:name')); cb(); }); } function createBaseUser(cb) { var admin = new User({ username: 'admin', password: config.get('project:admin:password'), email: config.get('project:admin:email'), role: 1 }); admin.save(function () { log.info('created database ' + config.get('db:name')); log.info('created base admin user'); cb(); }); } function ensureIndexes(cb) { async.each(Object.keys(mongoose.models), function (model, callback) { mongoose.models[model].ensureIndexes(callback); }, function () { log.info('indexes ensured completely'); cb(); }); } function closeConnection() { mongoose.disconnect(); log.info('disconnected'); } async.series( [ openConnection, dropDatabase, createBaseUser, ensureIndexes ], closeConnection ); 


It is necessary to initialize the database, filling in the default information with which the server will operate.

config


config.json


 { "port": 3000, "db": { "connection": "mongodb://localhost", "name": "db_name", "options": { "server": { "socketOptions": { "keepAlive": 1 } } } }, "session": { "secret": "secret_key", "key": "cid", "cookie": { "path": "/", "httpOnly": true, "maxAge": null } } } 


index.js


 var nconf = require('nconf'); var path = require('path'); nconf.argv() .env() .file({file: path.join(__dirname, 'config.json')}); module.exports = nconf; 


The config.js file contains information about database connection settings, as well as session settings.
To work with the config , the nconf package is used , which allows manipulating the settings object via getter and setter. You can also use embedded objects through the symbol::

 config.get('session:secret'); config.get('session:cookie:path'); 


middleware


 module.exports = function (app, express) { var ejs = require('ejs-locals'), path = require('path'), config = require('../config'), mongoose = require('../utils/mongoose'), MongoStore = require('connect-mongo')(express), router = require('../routes'), errorHandler = require('./errorHandler')(app, express), checkAuth = require('./checkAuth'); /** * Page Rendering * */ app.engine('html', ejs); app.engine('ejs', ejs); app.set('views', path.join(__dirname, '../views')); app.set('view engine', 'ejs'); /** * Favicon * */ app.use(express.favicon('public/images/favicon.ico')); /** * Logger * */ if (app.get('env') == 'development') { app.use(express.logger('dev')); } /** * Session * */ app.use(express.bodyParser()); app.use(express.cookieParser()); app.use(express.session({ secret: config.get('session:secret'), key: config.get('session:key'), cookie: config.get('session:cookie'), store: new MongoStore({mongoose_connection: mongoose.connection}) })); /** * Authorization Access * */ app.use(checkAuth); /** * Routing * */ app.use(app.router); router(app); /** * Public directory * */ app.use(express.static(path.join(__dirname, '../public'))); app.use("/public", express.static(path.join(__dirname, '../public'))); /** * Error handing * */ app.use(errorHandler); }; 


Thus, we will connect all middleware without clogging up the main part of the server code, perhaps, it will have to be expanded as the application is written.

I also want to note - errorHandler middleware is intended for the own handling of server errors, and the output of the error page

errorHandler


 var config = require('../config'); var sendHttpError = function (error, res) { res.status(error.status); if (res.req.xhr) { res.json(error); } else { res.render('error', { error: { status: error.status, message: error.message, stack: config.get('debug') ? error.stack : '' }, project: config.get('project') }); } }; module.exports = function (app, express) { var log = require('../utils/log')(app, module), HttpError = require('../error').HttpError; return function(err, req, res, next) { if (typeof err === 'number') { err = new HttpError(err); } if (err instanceof HttpError) { sendHttpError(err, res); } else { if (app.get('env') === 'development') { express.errorHandler()(err, req, res, next); } else { log.error(err); err = new HttpError(500); sendHttpError(err, res); } } }; }; 


I would also like to note middleware checkAuth
 var HttpError = require('../error').HttpError; module.exports = function (req, res, next) { if (!req.session.user) { return next(new HttpError(401, "You are not authorized!")); } next(); }; 

Which will check requests for the presence of the session and, in the case of its absence, will throw an error. It can be used as a global middleware or you can specify the method where it will be used:

 app.get('/user-info', checkAuth, function (req, res, next) { //do your staff }); 


models


With Mongoose, we will create our own models for working with data. An example of a model might look like this:

 var crypto = require('crypto'), mongoose = require('../utils/mongoose'), Schema = mongoose.Schema, async = require('async'); var User = new Schema({ username: { type: String, unique: true, required: true }, hashedPassword: { type: String, required: true }, salt: { type: String, required: true } }); User.methods.encryptPassword = function (password) { return crypto.createHmac('sha1', this.salt).update(password).digest('hex'); }; User.virtual('password') .set(function (password) { this._plainPassword = password; this.salt = Math.random() + ''; this.hashedPassword = this.encryptPassword(password); }) .get(function () { return this._plainPassword; }); User.methods.checkPassword = function (password) { return this.encryptPassword(password) === this.hashedPassword; }; module.exports = mongoose.model('User', User); 


public


This directory will contain all scripts and css files that are accessible from the outside. This option is implemented using the following setting:

 /** * Public directory * */ app.use(express.static(path.join(__dirname, '../public'))); app.use("/public", express.static(path.join(__dirname, '../public'))); 


routes


Perhaps the most interesting. In this directory, we declare a module that will be responsible for routing. index.js file

 var main = require('./main'), register = require('./register'), authentication = require('./authentication'), error = require('./error'); module.exports = function (app) { app.get('/', main.home); app.post('/register', register.requestRegistration); app.get('/users', authentication.users); app.get('/users/:id', authentication.user); app.get('*', error['404']); }; 


Here we simply declare our routes, and simply delegate the execution to other modules. For example, route "/" :
 /** * Method: GET * URI: / * */ exports.home = function(req, res, next) { res.render('index'); }; 


Actually, that's all. In this case, how the base application will work. To support the session, we enable the corresponding middleware . All business logic associated with the user is transferred to models / user.js , in particular validation and registration, for example.

PS:
In writing this article was used information from screencasts I. Kantor. Screencast link.
Also used information from courses on MongoDB

Source: https://habr.com/ru/post/207930/


All Articles