URL Design in Web Applications

Leonard Garvey
April 17, 2015

or; Using CRUD as an Abstraction

When thinking about a new web application I like to try to think about the URLs that my application has. The URLs are the bits that expose my application to the outside world, they define what my application can do, so thinking about them early on means that I’m focusing on one of the most important parts of my system.

As I develop complex web systems I’ve found that creating an organised, consistent URL system has significant benefits to my codebase overall, and makes my systems easier to create and maintain. At the same time I notice that beginning developers often create overly complicated and disorganised URL structures for their applications.

While this post is primarily focused on Rails, the main lessons here apply to any web application.

Consistency of Routing

We want to be able to get an idea of what a URL route does just by looking at it. We’d also like it if new developers could understand what is happening without much learning. For example: GET /blogs/new - is the page where the form for a new blog is created. In the same app: GET /products/new is the page where the form for a new product is located. These routes should NOT be: GET /newblog or GET /addproduct.

In Rails these consistent routes are [called “resourceful” routes]. The important lessons here are that the naming structure is easy to understand, and that HTTP methods are often used to distinguish different things. For instance if you are performing an action which will save a record to the database, the POST HTTP Method is the appropriate method to use. If you’re updating an existing record then use either PUT or PATCH.

The Rails routing guide has an excellent example of these types of routes.

HTTP Verb Path Controller#Action Used for
GET /photos photos#index display a list of all photos
GET /photos/new photos#new return an HTML form for creating a new photo
POST /photos photos#create create a new photo
GET /photos/:id photos#show display a specific photo
GET /photos/:id/edit photos#edit return an HTML form for editing a photo
PATCH/PUT /photos/:id photos#update update a specific photo
DELETE /photos/:id photos#destroy delete a specific photo

We can easily come up with some anti-patterns, or examples of “bad” routes:

HTTP Verb Path Used for Notes
GET /addphoto return an HTML form for creating a new photo is inconsistent
POST /photos/:id?update=true update a specific photo doesn't nest controllers appropriately
GET /photos/:id?delete=true delete a specific photo uses GET to destroy a resource

## Non-Resourceful Routes Rails also lets a developer add a “non-resourceful” route to a controller. In my opinion these are a serious code-smell and should never be used by beginning programmers. An example of this might be:

erb GET /users/search UsersController#search

While this type of route may seem easier than creating a new controller just to handle searching, in the near future it becomes very unwieldy to mix the concerns of searching with the concerns of the user controller. Some parts of the users controller might need to be heavily authenticated, while we might allow searching to be done by guests. As we add this logic to our applications and add extra “non-standard” actions to our controllers our apps become more difficult to document, modify and maintain.

Another example of this might be:

erb POST /jobs/restart JobsController#restart

In this example we’re restarting some sort of job. This is simply being added to the JobsController which is now in danger of becoming overburdened with functionality to do with the scheduling and control of our jobs. Also it seems likely that a job restart action might need some additional auditing and logging, which would further complicate our JobsController. Instead of this what we’re actually doing is CREATING a JobRestart. By thinking about this functionality in terms of CRUD we’re easily able to see that there is a new resource here which needs a new controller. This should be rewritten as:

erb POST /jobs/:id/restarts JobRestartsController#create

My preference is to never write non-resourceful routes and my recommendation to starting developers is to never use them either. This rule has some additional benefits that might not be readily apparent. By forcing us to use “resourceful” routes we begin to think about all of our application in terms of create, read, update and delete or CRUD. This puts us in familiar teritory. I’ll run through some examples:

1.) You are building a SaaS application and want to allow a user to invite another user into the system. You might be tempted to add an invite route to the UsersController but instead you should create an InvitesController and use the create method. This requirement is to create an invite so it’s very appropriate to follow this pattern.

2.) You are building a song playlist management application and your boss wants you to make a “vote to skip” functionality. You could add SongsController#skip, but it’s far more consistent to add a new controller which handles the creation of the SkipVote model. You might call this SkipVotesController#create. If you had simply added the #skip method to the SongsController you’d be stuck if your boss later asked you to allow users (or an admin) to view the votes to skip various songs.

In some rare circumstances it will be difficult to think about your problem domain in terms of CRUD, but I believe it pays off in the long run. Naming things can be difficult, but working with legacy code where no care was made to name URLs and routes is a far worse position to be in. [called “resourceful” routes]: