In this post I will show you how coldbox can help you creating resourceful routes, how cbswagger shows me that I don’t want the defaults resource() routing method, and how easy it is to create your own method!

If you want to create a REST API in coldbox, you often need to create a lot of routes for this API. So let’s say you want to create endpoints to list, view, update, create and delete a User resource. Following the coldbox manual, I need to implement the following routes:

VerbRouteEventRoute namePurpose
GET/usersusers.indexusers.indexlist users
GET/users/newusers.newusers.netreturn HTML form for creating a new user
POST/usersusers.createusers.createCreate a new user
GET/users/:idusers.showusers.showDisplay a specific user
GET/users/:id/editusers.editusers.editreturn an HTML form for editing a user
PUT/PATCH/users/:idusers.updateusers.updateUpdate a specific user
DELETE/users/:idusers.deleteusers.deleteDelete a specific user
REST ful actions

In this table I marked two entries in red, because I never create HTML forms from my coldbox app (I am using a VueJS frontend for this). But it still means I have to create multiple routes for each resource, and that will add up quickly when managing a larger API. In my case I would have to create the following

route( "users/:id" )
  .as( "usersById" )
  .withAction( {
    GET    : "show",
    PUT    : "update",
    DELETE : "delete"
  } )
  .toHandler( "users" );
route( "users" )
  .as( "userList" )
  .withAction( {
    GET    : "index",
    POST   : "create"
  } )
  .toHandler( "users" );

Of course coldbox can help here, because it has resourceful routes. It has a very handy resource() method which generates most of this routes for you by just adding this code to your router config:

route("users")

We have our handlers in special packages and we don’t want these HTML forms, but with some slight modifications it is still easy to create these routes

.resources(resource="users", handler="auth.Users", except="new,edit")

The resources() function has some more parameters to specify your resourceID name of a module, so it is quite flexible.

Although it might be annoying to always exclude something you never use, I’ve been using the resources() function for quite some time. But a few months ago that changed. Let me explain why.

The resources() functions is generating both a PUT and a PATCH verb for the users.update action (and all other update actions as well). I didn’t mind too much, although these verbs have different meanings. Some people think these verbs are synonyms, but that’s not true. A PUT request should be used if you want to replace the FULL resource by new data. A PATCH request should only be used if you need a partial replace, so only some fields are updated. As such I think you should be able to separate them and send them to different events, which is not possible with the current function. This was OK for us, because we used PUT and ignored PATCH in most cases. That is, until we started using cbswagger.

cbswagger is a module to generate OpenAPI documentation. In some later post I will explain how you can use it. But for now: by using cbswagger these PATCH routes suddenly showed up in al documentation, although we didn’t want people to use PATCH. This happens because the PATCH routes are registered in our router by the resource() function, and cbswagger is creating openAPI docs by reading router info.

In the image above it doesn’t show up as a huge problem but we really didn’t want 80 unwanted PATCH verbs in our formal API documentation. Actually preventing it was quite easy: just create a custom method in our router.cfc and use that instead of the default resource() method.

function customResource( 
  required string resource, 
  required string handler
) {
  route( "#arguments.resource#/:id" )
    .withAction( {
      GET    : "show",
      PUT    : "update",
      DELETE : "delete"
    } )
    .toHandler( handler );
  route( resource )
    .withAction( { 
      GET : "index", 
      POST : "create" 
    } ).toHandler( handler );
}

Creating this function was so easy that I was wondering why I’ve not done this a lot earlier. If you want you can enhance this function a little, by naming your routes for example or incorporating some module parameters.

As always: coldbox can make your life a lot easier, and if you don’t like it, it is flexible enough to create your own solution!