Using Routes
The convention for URLs in Wheels works for most situations and helps to promote an easy-to-maintain code base. With routes, you have the flexibility to break this convention when needed.
The Convention for URLs
To write clear MVC applications with ColdFusion on Wheels, we recommend sticking to conventions as much as possible. As you may already know, the convention for URLs is as follows:
http://www.domain.com/news/story/5
With this convention, the URL above tells Wheels to invoke the story action in the news controller. It also passes a parameter to the action called id, with a value of 5.
Creating Your Own Routes
But let's say that you wanted a simpler URL for your site's user profiles. What if you wanted to have a profile action in a controller called user with this URL?
http://www.domain.com/user/johndoe
Fear not, this is possible in Wheels.
Adding a New Route
Routes are configured in the config/routes.cfm file. This is where we'll add our new user profile route.
Routes are added to Wheels using the addRoute() function. Here is how we would set up our new route using addRoute():
<cfset
addRoute(
name="userProfile", pattern="user/[username]",
controller="user", action="profile"
)
>
This call to addRoute() instructs Wheels to create a route named userProfile that passes a username parameter to the profile action in the user controller. This route will be invoked by any URL that starts with a top level folder of user. In most cases, a pattern in a route should begin with a unique top level folder.
As you can see, any new parameters that you want to introduce to a new route should be surrounded by square brackets [].
With this, you can create URL patterns with any level of complexity that you wish.
Using controller and action Within Your Route
As you saw above, we specifically told Wheels which controller and action that the userProfile route should call. You also have the option of making the controller and action calls dynamic by including them in the pattern instead. (This is actually how Wheels sets up the default routes internally.)
You still have to pass in the controller and action arguments to the addRoute() function, but if [controller] and [action] also exist in the pattern, these arguments will only serve to tell Wheels what the default controller and action should be when they are not passed in through the URL.
Consider this line of code:
<cfset
addRoute(
name="adminUser", pattern="admin/user/[action]",
controller="adminUser", action="index"
)
>
With this pattern, a URL that begins with admin/user/ will always call the adminUser controller. But what action to call on that controller is determined dynamically by the URL.
But because we have [action] within the pattern, we don't always necessarily need for the route to point to controllers/AdminUser.cfc's index() method. The action="index" parameter instructs Wheels to call the index action when one is not otherwise specified in the URL.
So now this route pattern will, for example, match for these URLs:
| URL | Controller | Action |
|---|---|---|
| http://www.domain.com/admin/user/edit | adminUser |
edit |
| http://www.domain.com/admin/user/add | adminUser |
add |
| http://www.domain.com/admin/user/delete | adminUser |
delete |
Although probably less useful, the same concept can be applied to the controller variable as well.
Linking to Your New Route
Now if you wanted to create a link to that user profile action we discussed earlier in the chapter, you would use Wheels's linkTo() function like so:
#linkTo(route="userProfile", username="johndoe")#
As you can see, linkTo() accepts a route argument, which changes the function's expectations on which other arguments are passed. Because our userProfile route expects a username parameter, we would need to pass that.
You can read more about creating links in the chapter called Linking Pages.
Order of Precedence
With the potential of your application requiring many custom routes, you may wonder which order that Wheels considers these new routes. The answer is that Wheels gives precedence to the first listed custom route in your config/routes.cfm file.
Wheels will look through each custom route in order to see if there is a match. If not, it defaults to the default route mentioned at the beginning of this chapter under "The Convention for URLs".
Example of Precedence
Let's pretend that our config/routes.cfm file looks like this:
<cfset addRoute(name="newsAdmin", pattern="admin/news/[action]"), controller="newsAdmin")>
<cfset addRoute(name="searchAdmin", pattern="admin/search/[action]", controller="searchAdmin")>
<cfset addRoute(name="adminRoot", pattern="admin/[action]", controller="admin")>
Wheels would make sure that the URL didn't begin with admin/news or admin/search before calling the third route listed, adminRoot.
If the URL didn't begin with admin at all, Wheels would use its internal default route, matching the usual pattern of [controller]/[action]/[key].
Making a Catch-All Route
Sometimes you need a catch-all route in Wheels to support highly dynamic websites (like a content management system, for example), where all requests that are not matched by an existing route get passed to a controller/action that can deal with it.
Let's say you want to have both http://localhost/welcome-to-the-site and http://localhost/terms-of-use handled by the same controller and action. Here's what you can do to achieve this.
First, add a new route to config/routes.cfm that catches all pages like this:
<cfset addRoute(name="catchAll", pattern="[title]", controller="page", action="index")>
Now when you type in http://localhost/welcome-to-the-site, this route will be triggered and the index action will be called on the page controller with params.title set to welcome-to-the-site.
The problem with this is that this will break any of your normal controllers though, so you'll need to add them specifically before this route. (Remember the order of precedence explained above.)
You'll end up with a config/routes.cfm file looking something like this:
<cfset addRoute(name="main", pattern="main/[action]", controller="main")>
<cfset addRoute(name="admin", pattern="admin/[action]", controller="admin")>
<cfset addRoute(name="catchAll", pattern="[title]", controller="page", action="index")>
<cfset addRoute(name="home", pattern="", controller="main", action="index")>
main and admin are your normal controllers. By adding them to the top of the routes file, Wheels looks for them first.

Comments
Read and submit questions, clarifications, and corrections about this chapter.
[Add Comment]
Have you guys thought about implementing restful routing within the framework?
- James
# Posted by James Gibson | 12/7/2008
We've thought about doing things like RESTful routing in future versions but really want to have the basics rock solid before hitting version 1.0.
Future versions will feature a plugins capability. This will create the opportunity for the community to contribute plugins for stuff like that. (Hint hint. ;))
# Posted by Chris Peters | 12/7/2008
>>http://www.domain.com/news/story/5
>>With this convention, the URL above tells Wheels to invoke the story action in the news controller. It also passes a parameter to the action called id, with a value of 5.
Actually, it passes the parameter "key", not "id". It will only map to "id" in a findbykey() if id is the primary key. Correct?
# Posted by stylo~ | 12/20/2009