Running your Ghost blog and Express app on Heroku
Running a Ghost blog as part of a website is a quite common scenario, and I was surprised when I encountered a problem when deploying to Heroku.
The problem was that, by default, Heroku gives you a single port in an environment variable process.env.PORT
. If you're running, for example, Express app to serve your website and Ghost for your blog then you'll get an error that port is in use.
What can be done?
There are several solutions:
- you might get around the single app/dyno restriction using heroku-buildpack-runit
- you could use two separate Heroku applications
- use Ghost as a middleware of your parent/root app
Solutions 1 & 2 were not suitable for me.
Custom build pack increases complexity and to maintain the site I should have good understanding of runit. It felt a bit overkill for the problem.
Several dynos would also increase complexity and makes maintenance cumbersome. I am not 100% sure how I would split my single repository to work differently on each application, I guess environment variables would be the solution. Overall this solution would be too complex.
I went with option three.
Ghost + Middleware
As I am writing this, using Ghost as a middleware is not yet officially supported. There are several discussions on a GitHub issues #4429 and #827. After investigating, I picked Mr. Josh Vanderwillik's solution and modified it slightly.
var ghost = require('ghost'),
express = require('express'),
hbs = require('express-hbs'),
path = require('path'),
parentApp = express();
function processBuffer( buffer, app ){
while( buffer.length ){
var request = buffer.pop();
app( request[0], request[1] );
}
}
function makeGhostMiddleware(cb, options) {
var requestBuffer = [];
var app = false;
ghost( options ).then( function( ghost ){
app = ghost.rootApp;
processBuffer( requestBuffer, app );
cb();
});
return function handleRequest(req, res){
if(!app) {
requestBuffer.unshift( [req, res] );
} else {
app( req, res );
}
};
}
parentApp.set('port', (process.env.PORT || 2368));
parentApp.engine('hbs', hbs.express4({}));
parentApp.set('view engine', 'hbs');
parentApp.set('views', path.join(process.cwd(), 'views'));
parentApp.use(express.static(path.join(process.cwd(), 'public')));
parentApp.get('/', function (req, res) {
/* example route for things outside of blog */
});
parentApp.use( '/blog', makeGhostMiddleware(function(ghostServer) {
require('./helpers')();
}, {
config: path.join(process.cwd(), 'config.js')
}));
parentApp.listen(parentApp.get('port'), function() {
console.log('Node app is running on port', parentApp.get('port'));
});
There is also a proof of concept npm package called Ghost as middleware, but I think it should not be used as it will make life more difficult in all long run. I have explained this in a blog post.
How does the solution work?
When a request comes to a blog, it will call function makeGhostMiddleware
. Inside the function, it will start Ghost and also return the actual middleware that handles each request.
The request handler checks if Ghost isn't yet running then it will buffer the request to an array. When Ghost starts, it will go thru the array and process each individually. While the blogging platform is running buffering isn't needed, each requests Ghost processes immediately. Nice!
I hope this will help you get your site and blog running as a middleware!