When I generate a webserver with express-generator, I get this folder structure :
- bin/www
- views/…
- app.js
- package.json
- …
bin/www
call app.js
like that :
var app = require('../app');
// ...
var server = http.createServer(app);
server.listen(port);
app.js
create the app like that :
var express = require('express')
var mongoose = require('mongoose')
mongoose.connect(process.env.DATABASE_URL).then(
() => {
debug('Database is connected')
},
err => {
debug('An error has occured with the database connection')
process.exit(1)
}
)
var app = express()
// Midllewares
app.use(/* some middleware 1 */)
app.use(/* some middleware 2 */)
app.use(/* some middleware 3 */)
app.use(/* some middleware ... */)
// Routes
app.get('/', function(req, res, next) {
res.json({'message': 'Welcome to my website'})
})
app.get('/users', function(req, res, next) {
Users.find({}).exec(function(err, users) {
if (err) {
res.json({'message': 'An error occured'})
return
}
res.json('users': users)
})
})
// ... others routes ...
module.exports = app
ok, this is the webserver boilerplate from express-generator. But if I want to start my app by the good way, I must call process.send('ready')
when my app is ready. (“ready” mean that all services are ready to use: database, redis, scheduler…) (call process.send('ready')
when your app is ready is a best practice to know that your webserver app si ready. This signal can be used by process management or other system)
The probleme is that in bin/www
, the app is started (server.listen()
is called) without insurance that the database connection is established. In other word, without the insurance that the webserver app is ready to listen to the traffic.
I read that start the server in bin/www
is a best practice
The above example is not complete, we can considere that we have an app with multiple services that we must start before accept requests (services examples: redis, job scheduler, database connection, ftp connection to another server…)
I already check some popular and advanced boilerplate of Node.js app :
- https://github.com/sahat/hackathon-starter
- https://github.com/kriasoft/nodejs-api-starter
- https://github.com/madhums/node-express-mongoose
- https://github.com/icebob/vue-express-mongo-boilerplate
- https://github.com/talyssonoc/node-api-boilerplate
But none of them take care of the ready state of the app before calling server.listen(port)
which make the webserver starting to listen to the incoming requests. That surprises me a lot and I don’t understand why
Code example of a webserver app with multiple services that we must wait for before accept incomings requests:
bin/www
:
var app = require('../app');
// ...
var server = http.createServer(app);
server.listen(port);
app.js
:
var express = require('express')
var mongoose = require('mongoose')
// **************
// Service 1 : database
mongoose.connect(process.env.DATABASE_URL).then(
() => {
debug('Database is connected')
},
err => {
debug('An error has occured with the database connection')
process.exit(1)
}
)
// **************
// **************
// Service 2
// Simulate a service that take 10 seconds to initialized
var myWeatherService = null
setTimeout(function() {
myWeatherService.getWeatherForTown = function(town, callback) {
weather = 'sun'
callback(null, weather)
}
}, 10*1000)
// **************
// **************
// Other services...
// **************
var app = express()
// Midllewares
app.use(/* some middleware 1 */)
app.use(/* some middleware 2 */)
app.use(/* some middleware 3 */)
app.use(/* some middleware ... */)
// Routes
app.get('/', function(req, res, next) {
res.json({'message': 'Welcome to my website'})
})
app.get('/users', function(req, res, next) {
Users.find({}).exec(function(err, users) {
if (err) {
res.json({'message': 'An error occured'})
return
}
res.json({'users': users})
})
})
app.get('/getParisWeather', function(req, res, next) {
Users.getWeatherForTown('Paris', function(err, weather) {
if (err) {
res.json({'message': 'An error occured'})
return
}
res.json({'town': 'Paris', weatcher: weather})
})
})
// ... others routes ...
module.exports = app
If I start my app, and then I call localhost:port/getParisWeather
before the myWeatherService
is initialized, I will get an error
I already think about a solution: move each service declaration in bin/www and let in app.js only code that concern the declaration of the express app
:
bin/www
:
var app = require('../app');
var mongoose = require('mongoose')
var server = null;
Promise.resolve()
.then(function () {
return new Promise(function (resolve, reject) {
// start service 1
console.log('Service 1 is ready')
resolve()
})
})
.then(function () {
return new Promise(function (resolve, reject) {
// start service 2
console.log('Service 2 is ready')
resolve()
})
})
.then(function () {
return new Promise(function (resolve, reject) {
// start other services...
console.log('Others services is ready')
resolve()
})
})
.then(function () {
return new Promise(function (resolve, reject) {
server = http.createServer(app);
server.listen(port);
console.log('Server start listenning')
})
})
.then(function () {
next()
})
.catch(next)
.finally(function () {
})
.done()
app.js
:
var express = require('express')
var app = express()
// Midllewares
app.use(/* some middleware 1 */)
app.use(/* some middleware 2 */)
app.use(/* some middleware 3 */)
app.use(/* some middleware ... */)
// Routes
app.get('/', function(req, res, next) {
res.json({'message': 'Welcome to my website'})
})
app.get('/users', function(req, res, next) {
Users.find({}).exec(function(err, users) {
if (err) {
res.json({'message': 'An error occured'})
return
}
res.json({'users': users})
})
})
app.get('/getParisWeather', function(req, res, next) {
Users.getWeatherForTown('Paris', function(err, weather) {
if (err) {
res.json({'message': 'An error occured'})
return
}
res.json({'town': 'Paris', weatcher: weather})
})
})
// ... others routes ...
module.exports = app
But I know that put logic in bin/www is not a good practice, it must only contains the server start lines…
So, my question is, how we must start a webserver app to respect the bests practices // what is the bests practices to ?
I know that I can put everything in only one file and start the webserver at the end of this file, this is not my question. What I ask is how to do it in the good way and in the best practices
2
Answers
The answer really depends on what your ecosystem is like. If you know all of the services that your app will use, then you might try checking them in an expressjs middleware function that is called before the routing code. You can use a set of promises to keep track of service readiness and a boolean to tell whether all services are ready. If all services are ready, then the middleware function can call
next()
, but if not, then you might return an HTML page that tells the user the site is undergoing maintenance or isn’t ready and they should try back later. I can see you encapsulating all those promises in a middleware function that manages whether or not they are ready as to not clutter your app.js or bin/www files.Update:
If you want to prevent the server from listening until the services are ready, then you’ll need to setup your own infrastructure in the same process or use something like supervisord to manage the processes. For example, you can setup a “startup” process that checks for the services. Once the services are ready, your startup process can fork and start the node server or create a child process that runs the server. You don’t need to have any of the service checking logic in your node app; the assumption is that if it is started by the other process, then the services are already up and running. You can introduce a high-level process management system like supervisord, or keep it all in nodejs and use the child_process module. This approach will help to keep the “startup” code separate from the “run/app” code.
Consider a simple express api which returns ‘OK’ on port 3000.
This becomes ready to accept connections as soon as the app is fired up. Now let’s sleep for 5 seconds to fake the database getting ready, and fire a ‘ready’ event manually afterwards. We start listening for connections when the event is caught.
Test if by going to http://localhost:3000. You’ll see ‘OK’ only after 5 seconds.
Here, you don’t have to emit the ‘ready’ event on
process
object itself. A custom EventEmitter object will do the job as well. Theprocess
object inherits from EventEmitter and is available globally. So it’s a convenient way of listening to any global events.