How to build an admin section in CFWheels

November 23, 2010 · Chris Peters

CFWheels routes allow for you to split your admin section up into as many controllers as you need.

Every now and then, someone asks how to build an admin section into their CFWheels application. If they want for it to be in an admin folder, does that mean that you can only have an admin controller that stores everything needed?

Fortunately, one approach allows for you to split your admin section up into as many controllers as you need using CFWheels routes. Let’s discuss how.

Adding routes for the admin section

CFWheels is pretty darn flexible in allowing you to set up your own URL structures. In fact, CFWheels’s URL routing feature is the main reason why I picked up the framework years ago.

That said, one approach to doing an admin section is to set up some route patterns to set up an admin folder that doesn’t necessarily exist as a controller (not directly, at least). Let’s say that behind an admin login, we’re going to have a users section for managing administrators and an events section that allows administrators to add and edit events on a calendar.

We would set up some routes like this in config/routes.cfm:

<cfscript>
// Admin users
addRoute(name="adminUsers", pattern="admin/users/[action]/[key]", controller="adminUsers", action="index");
addRoute(name="adminUsers", pattern="admin/users/[action]", controller="adminUsers", action="index");
addRoute(name="adminUsers", pattern="admin/users", controller="adminUsers", action="index");
// Admin events
addRoute(name="adminEvents", pattern="admin/events/[action]/[key]", controller="adminEvents", action="index");
addRoute(name="adminEvents", pattern="admin/events/[action]", controller="adminEvents", action="index");
addRoute(name="adminEvents", pattern="admin/events", controller="adminEvents", action="index");
// Admin home
addRoute(name="admin", pattern="admin", controller="admin", action="index");
// Site home page
addRoute(name="home", pattern="", controller="main", action="home");
</cfscript>
view raw routes.cfm hosted with ❤ by GitHub

When defining routes, it’s important to list the most specific route first. The routes defined under adminUsers and adminEvents are listed in that order so that CFWheels will match the most specific first and keep looking through the list if the client is requesting something less specific. The home route is listed last because it is least specific.

We can also name related routes with the same name so we can reference it similarly in calls to linkTo(), startFormTag(), and so on. CFWheels will match the route you’re looking for based on which parameters are passed in.

So we can pass different arguments to the same route name like this and get different types of URLs for each:

<cfoutput>
<!--- Links to /admin/events --->
#linkTo(text="List Events", route="adminEvents")#
<!--- Links to /admin/events/new --->
#linkTo(text="Add an Event", route="adminEvents", action="new")#
<!--- Links to /admin/events/view/78 (for example) --->
#linkTo(text="Next Event", route="adminEvents", action="view", key=nextEventId)#
<!--- Bonus: Always link to the home page like this!
Never link to controller="main", action="home" because you'll get duplicate
content at `/` and at `/main/home` --->
#linkTo(text="Home", route="home")#
</cfoutput>

Setting up admin security in the controller layer

Because controllers in CFWheels are defined by CFCs, you can do some nice stuff with inheritance to allow certain behaviors and settings for only a subset of your controllers. For instance, our admin section needs to lock out users behind a login form.

We could put all of the admin authentication filters in the base controller at controllers/Controller.cfc because all controllers extend it, but there is actually a cleaner way to define it just for the controllers that we need it in.

Let’s create a controller at controllers/AdminController.cfc and implement something like this:

component extends="Controller" {
//-----------------------------------------------------
// Public
function init() {
filters(through="authenticate");
}
//-----------------------------------------------------
// Filters
private function authenticate() {
if(!StructKeyExists(session, "user") {
redirectTo(controller="sessions", action="new");
}
else {
loggedInUser = model("adminUser").findByKey(session.user.id);
}
}
}

Now through inheritance, we can have the adminUsers and adminEvents controllers extend AdminController instead of just Controller (which will also included anyway because AdminController extends Controller).

Our adminUsers controller at controllers/AdminUsers.cfc would then look something like this:

component extends="AdminController" {
//-----------------------------------------------------
// Public
function init() {
// Be sure to call the parent constructor if you override `init()`
super.init();
}
function index() {
adminUsers = model("adminUser").findAll(order="lastName,firstName");
}
}
view raw AdminUsers.cfc hosted with ❤ by GitHub

Because the adminUsers controller extends AdminController, no one will be able to access the index action until they are logged in. This is enforced by authenticate filter defined in the parent AdminController.

Caveat with overriding init()

With this approach, you need to be careful when overriding the constructor in your child controller. If you override init(), you need to remember to call super.init() within in order to keep the authentication logic running.

Some may argue that you should only define the authenticate() method in AdminController and manually define the filters in each child controller because of this tendency to forget calling super.init(). I’m fine with that or what I have illustrated above, but remember that it’s just as easy to forget to include the filter either way. So in other words, pick your poison and be sure to pay attention to the details.

Some final notes

I hope that this helps you solve a fairly common design problem in your MVC application. There are a lot of ways that this pattern can be applied when you have a group of controllers that all require some related functionality. I highly recommend trying to name controllers similarly in these situations so that it’s easy to glance at your controllers folder and understand what’s going on.

I might add that this is only one approach too—and it’s the simplest approach. Another approach is to develop your admin section as a completely separate application in its own subfolder or on a different subdomain. (Remember that full CFWheels URL rewriting will still work if your separate admin application is only one subfolder deep.)

The approach of separating your admin into a separate application can get complex pretty fast, but it is something worth pursuing if you find that your admin section tends to have different business logic than your public website.

About Chris Peters

With over 20 years of experience, I help plan, execute, and optimize digital experiences.

Leave a comment