Instead of defining middleware that checks the properties method and url manually every
time, you can use Express to progressively define routes by calling the function corresponding to the HTTP method you want to handle and then supplying the URL and the handler middleware.
The methods Express adds to the server are get, put, post, del, patch, and head,
matching HTTP verbs GET, PUT, POST, DELETE, PATCH, and HEAD, respectively. The
following are examples of route definitions with them:
app.get(‘/’, function (req, res, next) {});
app.put(‘/post/:name’, function (req, res, next) {}); app.post(‘/signup’, function (req, res, next) {}); app.del(‘/user/:id’, function (req, res, next) {}); app.patch(‘/user/:id’, function (req, res, next) {}); app.head(‘/user/:id’, function (req, res, next) {});
The first parameter is the route, and the second is the route handler. Route handlers work just like middleware.
Note that routes can take a special format to define variables within them. In the example above /user/:id, id can take different values and the route will still match: for example /user/2, /user/3, and so on. You’ll learn more about them later in the chapter.
For now, you should first define your home page route: Add the following to server.js:
app.get(‘/’, function (req, res) { res.render(‘index’);
});
The complete code for server.js should look like this so far:
/**
* Module requirements. */
C H A P T E R 9 • Express
149
var express = require(‘express’) , search = require(‘./search’)
/**
* Create app. */
var app = express.createServer();
/**
* Configuration */
app.set(‘view engine’, ‘ejs’);
app.set(‘views’, __dirname + ‘/views’); app.set(‘view options’, { layout: false });
/** * Routes */
app.get(‘/’, function (req, res) { res.render(‘index’); }); /** * Listen */ app.listen(3000);
Express adds a method called render to the response; it acts as shortcut to
1. Initializing the template engine
2. Reading the view file and passing it to the template engine
3. Getting the resulting HTML and sending it as a response
Because you specified the view engine to be ejs in the previous step, you don’t need to
reference index.ejs.
150
PA R T I I I • Web DevelopmentFigure 9-1: The route handler / renders the index view.
For the second route, call a function named search (which you define in a separate
module):
app.get(‘/search’, function (req, res, next) { search(req.query.q, function (err, tweets) { if (err) return next(err);
res.render(‘search’, { results: tweets, search: req.query.q }); });
});
Next, add the search dependency after express:
var express = require(‘express’) , search = require(‘./search’)
Notice that if an error is passed by the search function, you pass it to next. When you learn
more about error handling later in the chapter you’ll understand why, but for now assume that Express takes care of informing the user of the error.
In this route, you also call render, but you pass an object as second parameters. The contents
of that object get exposed to the view. Notice how you pass tweets and search, both of
which get referenced directly in search.ejs. You call this object the locals object
because its keys become local to the template.
SEARCH
The search module exposes a simple function to query the Twitter Search API. The file this module will reside in, search.js, goes in the same directory as server.js for this
example.
In the function call search above, you passed it the search term, and the function callback passed back an error (if any), and an array of tweets.
C H A P T E R 9 • Express
151
To write a module that does this, you start defining its dependencies. In this case, you’re only going to need superagent:
var request = require(‘superagent’)
Since the HTTP request made to the Twitter web service we make is essential to the function- ing of your application, you want to make sure the search module does proper error handling. For example, if the Twitter API is down or malfunctioning, you want to pass an error object so that ultimately the user sees an error page (for example, showing HTTP status code error 500).
/**
* Search function. *
* @param {String} search query * @param {Function} callback * @api public
*/
module.exports = function search (query, fn) {
request.get(‘http://search.twitter.com/search.json’) .data({ q: query })
.end(function (res) {
if (res.body && Array.isArray(res.body.results)) { return fn(null, res.body.results);
}
fn(new Error(‘Bad twitter response’); });
};
Similar to the other superagent examples, you want to make a GET request, sending the querystring data field q with the search term. The URL superagent hit for the search
term hello world will be something like http://search.twitter.com/search. json?q=hello+world.
In the response handler, you’re actively making sure that the request works and satisfies our expectations completely. Instead of looking at HTTP status codes and verifying you got 200 instead of something else, it’s smarter to ask the question: did I get an array of tweets as part of the response?
If you remember from Chapter 7, if superagent gets a JSON response, it will automatically decode it and place its contents as part of the res.body variable. Since the Twitter API responds with a JSON object with a key results containing an array of tweets, the following snippet from the code above is all you need for error handling:
if (res.body && Array.isArray(res.body.results)) { return fn(null, res.body.results);
152
PA R T I I I • Web DevelopmentRUN
Run the server and point your browser to http://localhost:3000 (see Figure 9-2) and
try out a search term (see Figure 9-3).
Figure 9-2: An example filling out a search term and submitting it.
Figure 9-3: The results of querying the search term.
It works! After making the search to Twitter, you got back an array of tweets. It eventually made its way to the template search.ejs, which generated the dynamic list of tweets.
After the HTML was produced by the render functionality of Express, the /search route
successfully served the complete page to the user as shown in Figure 9-3.
After this simple example, it’s time to analyze some of the Express features you used in depth, and learn new ones.
C H A P T E R 9 • Express
153
SETTINGS
One of the interesting features Express provides that proves necessary for any type of web application is the ability to manage environments and settings.
For example, during production you can make a performance enhancement and let express cache the templates so that they get served faster. However, if you enable this feature during development, you would need to restart Node every time you make a change to a template to test the result.
Express lets you set this environment by calling configure:
app.configure(‘production’, function () { app.enable(‘view cache’);
});
In this case, app.enable is the equivalent to calling app.set like you saw in the simple
Express example above for the views config flag.
app.set(‘view cache’, true);
To know whether a configuration flag is enabled you can also call app.enabled. app. disable and app.disabled are also available.
When the environment variable NODE_ENV is set to production, the callback we defined
with app.configure gets executed.
To test it, run
$ NODE_ENV=production node server
If node NODE_ENV is defined, the environment defaults to development:
app.configure(‘development’, function () { // gets called in the absence of NODE_ENV too! });
Some other useful built-in settings are
◾ case sensitive routes: Enable case-sensitive routing. By default, when you define
a route as follows:
app.get(‘/my/route’, function (req, res) {}
Express will match that route for /my/route and /MY/ROUTE. By enabling this, routes
154
PA R T I I I • Web Development◾ strict routing: When enabled trailing slashes are no longer ignored. For example,
the previous example route matches the URLs /my/route and /my/route/. If strict
routing is enabled, however, only /my/route would match, since that’s how it was
defined with app.get.
◾ jsonp callback: Enable res.send() / res.json() transparent jsonp
support. JSONP is a technique for serving cross-domain JSON that consists in wrapping the response with the callback provided by the user.
◾ When JSONP is requested, the URL would look like this: /my/route?callback= myCallback. Express can automatically detect the callback parameter and wrap the
response with the myCallback text. To enable this behavior, call app.enable(‘ jsonp callback’). Please note that this only applies when you call res.send or res.json in a route, which are described later in the chapter.