Routing

No doubt, Raze can route

In this doc, we will learn the ins and outs of routing, including some conventions and performance tips. Let's get to it.

Defining a Route

In Raze, routes are composed of the following parts:

  1. HTTP method (e.g. get, post)
  2. A path string (e.g. "/api/v1/users")
  3. The "route stack" (a.k.a. route handlers)
    • The middlewares
    • The route block
The route stack can be composed of middlewares and/or the route block. So you can just use middlewares, just use a block, or use both.

Examples Routes:

# no route block, just middlewares
get "/api*", MyCustomAuthenticator.new
post "/api*", MyCustomAuthenticator.new

# no middlewares, just a route block
get "/api/v1/users" do |ctx|
  ctx.response.content_type = "application/json"
  [{"user_id" => "abc"}, {"user_id" => "xyz"}].to_json
end

# middleware AND a route block
post "/api/v1/users" MyUserCreator.new, NewUserEmailer.new do |ctx|
  "user created"
end

Checkout the middlewares doc for more on custom middlewares.

HTTP Methods

Raze supports the following HTTP methods:

Route Paths

As you can see in some of the above examples, routes strings can be a little more versatile than a plain string by using wildcards (globbing) and url parameters.

URL Params

Named parameters can be extracted by adding a : at the beginning of a word, and it will be accessible via the ctx.params hash.

get "/author/:author_id/post/:post_id" do |ctx|
  author_id = ctx.params["author_id"]
  post_id = ctx.params["post_id"]

  ctx.response.content_type = "application/json"
  {author: author_id, post: post_id}.to_json
end

# https://localhost:7777/author/sam/post/pst_1234
# => {"author":"sam", "post":"pst_1234"}

Middlewares

See the middlewares guide.

More on Route Blocks

There are certain things you should be aware of when writing your routes.

Route Blocks are the End of the Line

An important note about route blocks is that they are the end of the line as far as any request is concerned. Meaning, you cannot have two matching routes that both have a block.

For example, this will fail:

get "/hello*" do |ctx|
  do_something
end

get "/hello/raze" do |ctx|
  "hello, raze"
end

If you really want to do something before the second route, that's what middlewares are for:

get "/hello*", DoSomething.new

get "/hello/raze" do |ctx|
  "hello, raze"
end

Route Ambiguity Order

To make your routing easier to grok, Raze enforces an order of ambiguity. Raze will make sure that any matching routes are in order from most to least ambiguous.

For example, this will fail:

get "/hello/raze" do |ctx|
  "hello, raze"
end

get "/hello*", DoSomething.new

# Exception: the less specific path "/hello*" must be defined before the more specific path "/hello/raze"

Raze will raise these route exceptions before the server even runs, so no need to worry about random runtime errors.

Raze enforces the order for matching routes because it stores matching routes in a tree so that the whole handler stack is run in the correct order. However, it has the added benefit of enforcing a route order thats easy to understand.