furljs
Furl is a configuration manager for Express 4. It has a number of features that help you build and maintain larger websites:
- Centralization of URL routing information.
- Generation of internal URLs from function references.
- Enforcement of prerequisites of request handlers.
Usage Scenario
Dumpling World is a typical e-commerce website. It has a main page, a catalog page listing products on offer, a product details page, and a user profile page. The application's main script is as follows:
'use strict'; var http = ;var furl = ; var shopFront = ;var productCatalog = ;var userProfile = ; var server = http; var parameters = 'view engine': 'jade'; var routes = '/': shopFrontshowWelcomePage '/products': productCatalogshowProductList '/products/:id': productCatalogshowProduct '/users/:id/personal': GET: userProfileshowPersonalInformation POST: userProfilesavePersonalInformation ; furl ;
To keep the codebase manageable, we store request handlers in multiple source files: shopFront.js, productCatalog.js, and userProfile.js.
We specify the URL routes in an object. The base URL is handled by showWelcomePage
in shopFront.js. /products is handled by
showProductList
in productCatalog.js, and so forth. By default, the HTTP method is assumed to be GET. When a URL can be accessed
through both GET and POST, we specify the separate handlers in a object with the method as keys (case insensitive).
The application's parameters are also kept in an object. Here, we specify Jade as the template engine.
We pass the parameter table to furl.set()
and the routing table to furl.route()
. Then we call furl.install()
to apply the
configuration information to the HTTP server. The return value of install()
will be an Express instance.
The welcome page in our example is very simple. It basically contains a link to the catalog:
html head title Dumpling World body h1 #{message} a(href=catelogUrl) Check out our products!
shopFront.js looks like this:
'use strict'; showWelcomePage; var furl = ; var productCatalog = ; { res;}
The first thing of note is how we define the request handler as named function and not an anonymous function assigned to module.exports
.
The reason we do this is because we'll be using function references to obtain URLs. This will happen fairly frequently. It's more
convenient when the functions can be referenced by name in the local scope.
The second thing of note is how the request handler is attached to module.exports
prior to any call to require()
. This arrangement
reduces the chance of mishaps when modules require each other. Suppose productCatalog.js requires shopFront.js. If we had placed the
initialization of module.exports
after require('./productCatalog')
, then productCatalog.js will receive a partially initialized
copy of shopFront with no exported functions.
Before the request handler renders the page, it calls furl.find()
to get the URL of the product catalog. We could have, of couse, simply
hard-coded it as "/products". Letting Furl generate the URL dynamically gives us more flexibility though. In the future, we can freely
tinker with our URL scheme without fear of introducing broken links. Because the URLs are generated from the routing table, they'll always
be correct. If we make a mistake, Furl will let us know immediately by throwing an exception. It saves us from having to click on a link
to detect if it's broken.
The fact that we have to require a module before linking to a page it handles is also good from a coding standpoint, as it more
accurately reflects relationships between modules. In the example, shopFront.showWelcomePage()
clearly depends on
productCatalog.showProductList()
from a functional standpoint. The explicit reference to latter in the code of the former allows a
code analyser to correctly detect that dependency.
Now, let us look at productCatalog.js:
'use strict'; showProduct showProductList; var furl = ; var database = ; { database;} { var id = ; database;}
The file contains two request handlers. showProductList()
fetches all available products from the database and renders a list.
showProduct()
fetches a single product and shows its details.
As before, the URL to the product details page is dynamically generated. Instead of passing a string to Jade, showProductList()
provides
a closure that returns a URL for a given product. The route to showProduct()
is /products/:id. Furl knows that it requires id
as a
parameter. If we fail to provide it (as the second argument to furl.find()
), Furl will throw an exception.
Parameter Validation
showProduct()
expects the parameter id
. The handler will only work properly if it's the target of a route containing :id. This
prerequisit is not explicitly stated though. Suppose we decide at some point that we decide to employ more specific parameter names. Our
routing table becomes:
var routes = '/': shopFrontshowWelcomePage '/products': productCatalogshowProductList '/products/:productId': productCatalogshowProduct '/users/:userId/personal': GET: userProfileshowPersonalInformation POST: userProfilesavePersonalInformation ;
showProduct()
now ceases to work, because req.params.id is no longer set by Express. We won't know this though until we visit the
page. This is undesirable. In general, we want our mistakes to be revealed as soon as possible so we can quickly fix them. Furl lets us
specify a request handler's requirements by attaching an expectation object to the function:
{ var id = ; database;}showProductexpectation = params: 'id'
When furl.install()
is called, Furl will ensure that the expectations of all request handlers are met. When we changed the route to
showProduct()
to /product/:productId, it no longer meets the handler's expectation. The following error is the result:
Route '/product/:productId' does not meet the expectation of productCatalog.showProduct
Unmet conditions:
params: id
Suggestions:
Change :productId in the path to :id
In this case, we actually want the parameter to be called productId. So we adjust the function and its expectation instead. After we've
done that, the application will start up correctly. As soon as we reach the product list though, we'll get an exception because
furl.find()
now needs { productId: p.id }
and not { id: p.id }
. We have to fix that as well.
Besides availability of parameters, Furl can enforce other types of prerequsites. We'll examine these later.
In the example above, we could have replaced ['id'] with 'id':
showProductexpectation = params: 'id'
Furl will automatically convert scalar values to as single-element arrays.
Parameter Filters
When a URL route contains a parameter like :productId, chances are pretty good that we'll need to load the product from the database.
Wouldn't it be great if it happens automatically? Express has just such a feature: app.param()
. Through Furl, it's accessible through
furl.filter()
.
We modify the main script of our dumpling shop to this:
'use strict'; var express = ;var furl = ; var shopFront = ;var productCatalog = ;var userProfile = ;var contextSetter = ; var app = ;var server = app; var parameters = 'view engine': 'jade'; var filters = 'productId': contextSetterloadProduct; var routes = '/': shopFrontshowWelcomePage '/products': productCatalogshowProductList '/products/:productId': productCatalogshowProduct '/users/:userId/personal': GET: userProfileshowPersonalInformation POST: userProfilesavePersonalInformation ; furl;furl;furl;furl;
furl.filter()
accepts an object in which each property is a reference to a filter function. In the example, we're keeping our function
in a separate file (contextSetter.js). It contains the following:
{ database;}loadProductfulfillment = context: 'product';
Any route that contains :productId will trigger the handler. It will fetch the product object from the database and stores it in
req.context
. A fulfillment object is attached to the function. It tells Furl that whenever the handler is invoked, the condition
"product in context" is met.
In productCatalog.js, we simplify the request handler and adjust its expectation:
{ res;}showProductexpectation = context: 'product'
Looking at our updated code, we should be happy that our request handler no longer has to deal with the complexity of reading data from
the database asynchronously. It would be nice if we can do the same for showProductList()
as well. That handler doesn't require a
parameter though, so we can't use a parameter filter. Instead, we have to use a path filter:
var filters = 'productId': contextSetterloadProduct '/products': contextSetterloadProducts;
When furl.filter()
encounters a name containing a "non-word" character (not alphanumeric or underscore; \W in RegExp),
it'll treat it as a path. The hander is invoked whenever the URL matches the path. Its code looks as follows:
{ database;}loadProductsfulfillment = context: 'products';
Path filters make use of Express's app.all()
functionality.
We simplify showProductList()
as we had done before with showProduct
:
{ res;}showProductListexpectation = context: 'products'
Now our request handlers only deal with a page's presentational aspect. Separation of concerns--we like it.
Let us imagine for a moment that a new developer has joined our team. He's not too familiar with the codebase. He's tasked with creating a page that shows the product list somewhat differently. He looks at the existing code and copy-and-pastes a chunk of it to start out with:
{ res;}showProductListForMobileDeviceexpectation = context: 'products'
Our new developer add an entry to the routing table, but neglects to add a filter for the new page. When he starts the application, it'll fail with the following error:
Route '/product/specials' does not meet the expectation of productCatalog.showSpecialProductList
Unmet conditions:
context: products
Suggestions:
Use contextSetter.loadProducts as a filter or middleware
Parameter Substitution
Parameter substitution is the flip-side of parameter filtering. Whereas a parameter filter takes a productId and produces a product object,
a parameter substitution rule take a product object and yields the condition productId = . It lets us pass an array
containing the object to furl.find()
. Furl will then figure out what parameters to use to properly reference the object. Instead of
{ return furl;}
we can use
{ return furl;}
The feature is especially useful when multiple variables are needed to form a URL. Imagine if the URL to our product page contains the
product category: /products/:category/:productId. Having to continually type { category: p.category, productId: p.id }
would get
quite tiresome.
Subsitutions are defined using furl.subsitute()
. It accept an object as an argument. Each key should be the class name of an object
(i.e. name of the constructor). The value should be a function that takes an object and returns the correct parameters. For our example:
var substitutions = { return 'productId': pid ; };