Express 3 Migration Issues

Today I migrated a web site from Express 2.5 to Express 3.0.0rc3. This is a non backward compatible version change. Express has a Migrating from 2.x to 3.x wiki page, but it doesn’t quite leave you prepared for the more time consuming incompatibilities that you’ll need to fix. I’ll enumerate some of these differences here. Note that some of these issues may be ironed out in later releases of Express.

Flash

Flash cookie support is not included in Express 3.0. Just add the connect-flash module to regain this functionality. In my implementation I was using a dynamic helper to get the flash object. Now I need to explicitly attach the flash object to req.local using res.render( '/', { flash: req.flash() } );. The req.flash() method returns an object.

EJS Templates

Express 3.0.0rc3 did not include support for layouts and partials, leaving this functionality up to the template engine itself (see issue#35). EJS does not include this support, and it’s fairly critical. Fortunately the express-partials and it’s derivative project ejs-locals fill the void. I chose to use ejs-locals because it did a better job of resolving paths to partials and layout files. In fact I used the GothAck fork off ejs-locals because it has a fix that allows a global layout file to have a fixed location.

The paths to your partials in your ejs files won’t necessarily work as-is. All paths for partials must be relative to the root ejs file that is being processed, or absolute relative to your root folder.

Let’s look at this example directory structure to understand the new issues:

1
2
3
4
5
6
7
8
9
app
+-+ views
| +-- index.ejs
| +-- layout.ejs
| +-+ product
| | +-- index.ejs
| | +-+ partials
| |   +-- p1.ejs
| |   +-- p2.ejs

Partials can be coded either as paths relative to the root ejs file that is being rendered, or relative to the root of the views folder.

Two ways to include p1 in product/index
1
2
<%- partial('partials/p1') %>
<%- partial('/product/partials/p1') %>
Two ways to include p2 in p1 when p1 is included in product/index
1
2
<%- partial('partials/p2') %>
<%- partial('/product/partials/p2') %>
This will not work when including p2 in p1
1
<%- partial('p2') %>

Regarding layout files, I’m personally only interested in a top level layout. You include this as the default layout, and set up your ejs-locals environment as follows.

Initializing ejs-locals and setting the default layout file
1
2
3
4
app.set('views', __dirname + '/views');
app.engine('ejs',require('ejs-locals'));
app.locals._layoutFile = '/layout.ejs';
app.set('view engine', 'ejs');

I suspect that some of this is going to be cleaned up, with possible direct support for ejs layout and partials in Express 3.0. Watch Issue#35 for updates.

Dynamic Helpers

I thought dynamic helpers were a clever idea: execute a function on demand, only when a template needs access to the results of that function. Dynamic helpers have been dropped in Express 3.0 in favor of using “middleware and req.locals”. The drawback is that you can no longer provide request-specific callback functionality, so you have to do your calculations up front.

The app object and response object are really the only two ways you can insert variables into a template. Properties of the app object can be set at any time, however if you set the property to point to a function, you can’t pass the request and response objects to that function. You thus cannot provide a request-specific callback function, for example to do cookie processing.

The request object is really the only way to send request-specific variables down to your template, and can’t be used to provide function callback. These variables are passed to the template via the render method.

Both the app and response object approaches are shown below.

Passing Variables to Your Templates via the Response Object

Variables can be passed down to a template as a parameter to the response object, but functions cannot. Here is an example showing how to set and access these variables.

1
2
var locals = { title: 'My page title', includes: {charting: true} };
app.render( 'mypage', locals );
Template File
1
2
3
4
<% if( locals.includes.charting ) { %>
<p>Charting is included</p>
<% } %>
<p>locals:<%- JSON.stringify(locals) %></p>

Functions cannot be passed down via the response object. That means there is no replacement for the template-initiated processing that dynamic helpers provided.

Another trick is to use middleware to add properties or methods to the request object then, on render, pass derived properties to the render method. This is how the connect-flash modules operates.

Passing Variables to Your Templates via the App Object

You can pass app-level variables to your templates by attaching properties or functions to app.settings. Although this is referred to as app.locals in the documentation, you can’t actually set app.locals.myvar to a value and see this in your template. I presume this is another case of documentation written for people who already understand the application, meaning it wasn’t written for me.

To set an app-level variable or function you either call app.set() or set a property of app.settings as shown below.

In app.js
1
2
3
app.set('tLaunch',new Date());
app.settings.appName = 'MyApp';    // Equivalent to using app.set(appName,'MyApp');
app.set('appVersion',function() { config.getAppVersion() });

To read the app-level variable in your template you reference settings.myvar. To call the function you call settings.myfunc(). Remember, you can’t pass any parameters to the function, so you can’t get request-specific data using this function.

Template File
1
2
3
<p>The time my app was launched:<%- settings.tLaunch.toString() %></p>
<p>App Name:<%- settings.appName %></p>
<p>App Version:<%- settings.appVersion() %></p>

Listening

I spent a disproportionate amount of time tracking down changes in how SSL parameters are passed to the express app object. The new syntax can be made to work by calling http.createServer(app).listen(port) or https.createServer(options,app).listen(port). This is covered in the migration document but I missed it. My bad.

Starting an HTTP server
1
2
3
4
5
6
7
var http = require('http');
var app = express();
app.get('*', function(req,res) {
      res.writeHead(200, {'Content-Type': 'text/plain'});
      res.end( "Listening on port " + port );
});
http.createServer(app).listen(port);
Starting an HTTPS server
1
2
3
4
5
6
7
8
var https = require('https');
var options = { key: key, cert: cert };
var app = express();
app.get('*', function(req,res) {
      res.writeHead(200, {'Content-Type': 'text/plain'});
      res.end( "Listening on port " + port );
});
https.createServer(options,app).listen(port);

A Final Note

Express 3.0 is still in flux and you’re likely to see some changes before it’s final. If you find yourself using a patched version of module to get around compatibility issues, fork the module on github and follow these instructions.

Comments