1. The purpose of the Crails Router

The Crails router recognizes URLs and dispatches them to a controller's action, or to custom lambdas.

1.1 Connecting URLs to code

When your Crails application receives an incoming request for:

GET /patients/17

it asks the router to match it to a controller action. If the first matching route is:

  match_action("GET", "/patients/:id", PatientController, show);

the request is dispatched to the PatientController controller's show method with { "id": "17" } in params.

1.2 Configuring the Crails Router

The routes for your application or engine live in the file app/routes.cpp and typically look like this:

#include "config/router.hpp"
#include "controllers/brands.hpp"
#include "controllers/products.hpp"

ApplicationRouter::ApplicationRouter()
{
  match_action("GET", "brands", Brands, index);
  scope("brands/:brand_id", [&]()
  {
    match_action("GET", "", Brands, show);
    resource_actions("products", Products);
  });
}

1.3 Using the router without controllers

For some simple web application, you might want to strip the controller layer altogether and directly handle queries using functions or lambdas. Here's an example of route matching using lambdas:

void hello_world(Crails::Request& request, std::function<void()> callback)
{
  request.response.set_headers("Content-Type", "text/plain");
  request.response.set_response(
    Crails::HttpStatus::ok,
    "Hello world !"
  );
  callback();
}

ApplicationRouter::ApplicationRouter()
{
  match("GET", "hello_world", hello_world);
}
The callback parameter can be used to handle queries asynchronously. You may call it at the end of your route handler, like in the example above, or pass it to another thread and call it later. Note that you must keep around a reference to the Request object as a shared pointer, so that it doesn't get deleted. Use request.shared_from_this() to that end.

2. Resource Routing

Resource routing allows you to quickly declare all of the common routes for a given resourceful controller. A single call to resources can declare all of the necessary routes for your index, show, new, edit, create, update, and destroy actions.

2.1 Resources on the web

Browsers request pages from Crails by making a request for a URL using a specific HTTP method, such as GET, POST, PATCH, PUT, and DELETE. Each method is a request to perform an operation on the resource. A resource route maps a number of related requests to actions in a single controller.

When your Rails application receives an incoming request for:

DELETE /photos/17

it asks the router to map it to a controller action. If the first matching route is:

resource_actions("photos", PhotosController);

Crails would dispatch that request to the destroy method on PhotosController with { id: "17" } in params.

2.2 CRUD, Verbs and Actions

In Rails, a resourceful route provides a mapping between HTTP verbs and URLs to controller actions. By convention, each action also maps to a specific CRUD operation in a database. A single entry in the routing file, such as:

resource_actions("photos", PhotosController);

creates seven different routes in your application, all mapping to the PhotosController:

HTTP Verb Path Controller::Action Used for
GET /photos PhotosController::index display a list of all photos
GET /photos/new PhotosController::new_ return an HTML form for creating a new photo
POST /photos PhotosController::create create a new photo
GET /photos/:id PhotosController::show display a specific photo
GET /photos/:id/edit PhotosController::edit return an HTML form for editing a photo
PUT /photos/:id PhotosController::update update a specific photo
DELETE /photos/:id PhotosController::destroy delete a specific photo
Because the router uses the HTTP verb and URL to match inbound requests, four URLs map to seven different actions.
Crails routes are matched in the order they are specified, so if you have a resource_actions("photos", PhotosController) above a match_action("GET", "photos/poll", PhotosController, poll), the show action's route for the resource_action line will be matched before the match_action line. To fix this, move the match_action line above the resource_actions line so that it is matched first.

2.3 CRUD-only

resource_actions is better fitted for HTML applications. When developing a web API, you will probably be only interested in CRUD actions. In which case, you should use crud_actions:

  crud_actions("photos", PhotosController);

This helper works the same as resource_actions, but strips away the edit and new_ routes, which you won't need in an API.

2.4 Scopes and Routing

You may wish to organize groups of controllers under a scope. Most commonly, you might group a number of administrative controllers under an admin scope.

scope("admin", [this]()
{
  resource_actions("articles", Admin::ArticlesController);
  resource_actions("comments", Admin::CommentsController);
});

This will create a number of routes for each of the articles and comments controller. For Admin::ArticlesController, Rails will create:

HTTP Verb Path Controller::Action
GET /admin/articles Admin::ArticlesController::index
GET /admin/articles/new Admin::ArticlesController::new_
POST /admin/articles Admin::ArticlesController::create
GET /admin/articles/:id Admin::ArticlesController::show
GET /admin/articles/:id/edit Admin::ArticlesController::edit
PUT /admin/articles/:id Admin::ArticlesController::update
DELETE /admin/articles/:id Admin::ArticlesController::destroy

3. Non-Resourceful Routes

3.1 Bound parameters

When you set up a regular route, you supply a series of symbols that Crails maps to parts of an incoming HTTP request. For example, consider this route:

match_action("GET", "/photos(/:id)?", PhotosController, display);

If an incoming request of /photos/1 is processed by this route (because it hasn't matched any previous route in the file), then the result will be to invoke the display method of the PhotosController, and to make the final parameter "1" available as params["id"]. This route will also route the incoming request of /photos to PhotosController::display, since :id is an optional parameter, denoted by question mark after the parentheses section.

3.2 Dynamic segments

You can set up as many dynamic segments within a regular route as you like. Any segment will be available to the action as part of params. If you set up this route:

match_action("GET", "/photos/:id/:user_id", PhotosController, show);

An incoming path of /photos/1/2 will be dispatched to the show action of the PhotosController. params["id"] will be "1", and params["user_id"] will be "2".

3.3 Static segments

You can specify static segments when creating a route by not prepending a colon to a segment:

match_action("GET", "/photos/:id/with_user/:user_id", PhotosController, show);

This route would respond to paths such as /photos/1/with_user/2. In this case, params would be { id: "1", user_id: "2" }.

3.4 The Query String

The params will also include any parameters from the query string. For example, with this route:

  match_action("GET", "/photos/:id", PhotosController, show);

An incoming path of /photos/1?user_id=2 will be dispatched to the show method of PhotosController. params will be { id: "1", user_id: "2" }.

3.5 HTTP Verb Constraints

In general, you should use the GET, POST, PUT, PATCH, and DELETE methods to constrain a route to a particular verb. You can also left the verb parameter unspecified to match every verbs at once:

  match_action("", "/photos", PhotosController, show);