[Coldbox 7] How to Enforce Complete Module Hierarchy in Routing?

I’m working with the standard ColdBox RESTful API pattern where we nest version modules within an API module. My file structure looks like this:

/modules_app/
  /api/
    ModuleConfig.cfc
    /modules_app/
      /v1/
        ModuleConfig.cfc
        /handlers/
        /models/

With this setup, I expect API calls to follow this route pattern: https://mysite.com/api/v1/users

The Problem: ColdBox allows direct access to the nested v1 module, bypassing the parent api module entirely. This means both of these routes work:

  • https://mysite.com/api/v1/users ✓ (desired)
  • https://mysite.com/v1/users ✗ (should not work)

My Question: Is there a built-in way to enforce the complete module hierarchy in routing? I want to ensure that v1 can only be accessed through its parent api module path.

Current Workaround: The only solution I’ve found is adding explicit blocking routes in the main application’s Router.cfc:

// Block direct access to version modules
route( "v1/" ).to( "errors.onMissingPage" );
route( "v2/" ).to( "errors.onMissingPage" );

Is there a cleaner way to handle this at the module configuration level, or is this route-blocking approach the recommended solution?

Notes: inheritEntryPoint in the ModuleConfig.cfc is set to true. I would have thought this would also add some type of enforcement to the entry point so it would require the parent entry point in order for it to work.

Created issue: Jira

You could add a preHandler to the V1 handler(s) and check that prc.currentRoutedURL starts with api/. This would keep the error handling within each api version module and not in the root router and handle it as you wish.

	// executes before any action
	function preHandler( event, rc, prc, action, eventArguments ){
		if( prc.currentRoutedURL.left(4) != "api/" ) throw( type="invalidEvent", message="Invalid Event" );
	}
1 Like

I answered too quickly! After more thought, while my previous option works, a better option may be to set the V1 modules settings below.

I would follow that up with some testing of course.

// Module Entry Point
this.entryPoint         = "api/v1";
// Inherit Entry Point
this.inheritEntryPoint  = false;

Thanks for the responses @MikeR! I appreciate you taking the time to help out.

I will have to experiment with your second approach by manually changing the entrypoint to include the parent module’s name. The only thing that bothers me a bit about it is that the child must know about the parent, which I try to avoid when thinking about module hierarchy. That said, it’s a very creative workaround, nicely done!

I still believe that inheritEntryPoint should strictly enforce the module hierarchy in the router, which is why I created a new issue in Jira. This would enable a module to become a dependency of any sub module without having to know the parent’s entry point.

It is an interesting issue. I believe this is a case where ColdBox trying to make things “just work” with conventions based routing is at odds with strict routing like what you are looking for.

// Conventions-Based Routing
route( ":handler/:action?" ).end();

I believe if you remove the conventions based routing default entry in the root coldbox app router.cfc you will get the results you are looking for, however you would need to be much more specific with your router entires.

Are you using the ColdBox Route Visualizer to help troubleshoot routing?

You ask a good question, but I don’t think the conventions-based routing applies when routing to modules. Remember, the complete routing pattern looks like this module:handler:action. My limited understanding of the framework core suggests that Coldbox looks for a module with a matching name, regardless of the hierarchy, and therein lies the problem.

If the module is configured with inheritEntryPoint set to true, then the developer expects the path of the module to include the parent module name as well. Coldbox should enforce that.

I do use the route visualizer on many of my apps. It’s a powerful tool. I love it!

By the way, I’m super strict when it comes to routing. Maybe I’m too strict, but that’s how my brain works. Here’s an example of my router.cfc for a universal login module I’m putting together for Forgebox:

        // logouts
        route( "/logout/:anything", "errors.onMissingPage" );
        route( "/logout", "logout.index" );

        // password reset enable
        route( "/reset/:anything" ).to( "errors.onMissingPage" );
        route( "/reset/",  "reset.index" );

        // password reset update
        route( "/update/:anything" ).to( "errors.onMissingPage" );
        route( "/update/",  "update.index" );
        
        // catch all for missing pages
        route( "/:anything", "errors.onMissingPage" );
        
        // default: login
        route( "/", "main.index" );
        
       

Note how I write catch-all’s for every single route so you don’t get people accessing resources by making up weird paths.

Let me put it this way: let’s say you install an open source CMS from ForgeBox and that CMS has an entry point called “admin”. You would expect to access the handlers for this module by pointing your browser to /admin/, right?

Now let’s assume you want to add some additional functionality to the CMS by adding submodules, like maybe some blogging functionality. Those submodules depend on the CMS, so they have inheritEntryPoint=true. This means the URL scheme would look something like /admin/posts/.

Here’s the key point: it doesn’t make sense for you to be able to access this submodule by pointing your browser directly to /posts/. You want to make it clear that this is part of the CMS, and you want ColdBox to enforce the full entry point path like /admin/posts/.

Does that make sense? Hopefully this example makes the issue clearer.

I understand what your saying. My point was that ColdBox is pretty good at “figure it out and make it work” and I think that’s what the “strict” routing you are looking for is butting heads with.

I agree, I think if you set this.inheritEntryPoint to true the routing should be handled as if you set this.entryPoint to api/v1 like one of my suggestions.

It seems like what is happening now is that when these are set ColdBox does implicitly create routes that inherit the entry point, but the module itself still has an entry point of just v1 so when conventions based routing entries kick in for a route that starts with v1 it finds your module and routes to that.

I could be wrong, but i’m pretty sure by default all module routers do have conventions based routes added.

What I do know for sure is that from time to time routing proves to be quite the beast to tame.

This doesn’t contribute directly to the topic at hand, but the “ColdBox is pretty good at figure it out and make it work” comment reminded me how hard it is to lean ColdBox. I look for patterns to figure out how things work. ColdBox makes that very difficult. Things often seem to work randomly to me. I think this “ColdBox figures things out” causes more trouble than anything. I have to think it makes documenting how ColdBox works much more complicated.

@kurtlook
Thanks for sharing your experience! I appreciate when people express their struggles with learning new frameworks. It helps all of us remember that we’re here to support each other, and everyone goes through these challenges. It also helps me remember that I’m not the only one pulling my hair out sometimes when trying to solve a problem.

What you’re bumping into sounds like ColdBox’s conventions, which are incredibly powerful and save us from writing tons of boilerplate code, but they can definitely feel like “magic” when you’re trying to understand what’s happening under the hood. It’s hard to troubleshoot or modify something when you’re not sure how it’s working in the first place. When I first started with ColdBox, I felt the same way, like things were happening “magically” without me understanding why. Even now I struggle with things like conceptualizing how interceptors fit into the architecture of an application. I often forget that interception points are firing constantly in the background.

One thing that helped me was being more explicit about certain things, even when conventions would handle them automatically. For example, even though ColdBox has conventions for finding views, I always explicitly call event.setView('whatever') because I prefer being more strict and clear about what should happen. It might be a bit more verbose, but it makes the code more readable and predictable for me.

If you’re more of a visual learner like I am, I’d highly recommend checking out some of the tutorials on CFCasts. The Hero to Superhero series is fantastic, and I still go back to those videos when I need to figure out how to implement something new. Sometimes seeing the conventions in action helps bridge that gap between “magic” and understanding.

I think the main takeaway from all this is not to give up. The more you use it, the faster things will “click” and you will become more comfortable being a Coldbox ninja.