toro
Version, currently 0.3.012 versions
- 0.5.1latestJan 27, 2024
- 0.5.0not indexedJan 27, 2024
- 0.4.3not indexedJan 27, 2024
- 0.4.2not indexedJan 27, 2024
- 0.4.1not indexedJan 27, 2024
- 0.4.0not indexedJan 27, 2024
- 0.3.3not indexedJan 27, 2024
- 0.3.2not indexedJan 27, 2024
- 0.3.1not indexedJan 27, 2024
- 0.3.0not indexedJan 27, 2024
- 0.2.0not indexedJan 27, 2024
- 0.1.0not indexedJan 27, 2024
github.com/soveran/toro
Tree oriented routing
147 stars
0 dependents
License: MIT
Nothing has been indexed for 0.3.0 yet. The tag is recorded, its shard.yml has not been read, so the manifest and dependency list below are empty because they are unknown rather than because they are absent.
Installation
# Add this to your shard.yml
dependencies:
toro:
github: soveran/toro
version: ~> 0.3.0Then run:
shards installshard.yml
No shard.yml has been indexed for 0.3.0. You can read it on the repository.
Dependencies
Unknown: the shard.yml for this version has not been read yet.
README
This README is the one indexed from the repository at its latest ref, not from the tag for this version.
# Toro


Tree Oriented Routing
## Usage
Here's a `hello world` app that you can copy and paste to get a
sense of how Toro works:
```crystal
require "toro"
class App < Toro::Router
def routes
get do
text "hello world"
end
end
end
App.run do |server|
server.listen "0.0.0.0", 8080
end
```
Save it to a file called `hello_world.cr` and run it with
`crystal run hello_world.cr`. Then access your `hello world` application with
your browser, or simply by calling `curl http://localhost:8080/` from the
command line.
What follows is an example that showcases some basic routing features:
```crystal
require "toro"
class App < Toro::Router
# You must define the `routes` methods. It will be the
# entry point to your web application.
def routes
# The `get` matcher will execute the block when two conditions
# are met: the `REQUEST_METHOD` is equal to "GET", and there are
# no more path segments to match. In this case, as we haven't
# consumed any path segment, the only way for this block to run
# would be to have a "GET" request to "/". Check the API section
# to see all available matchers.
get do
# The text method sets the Content-Type to "text/plain", and
# prints the string to the response.
text "hello world"
end
# A `String` matcher will run the block only if its content is equal
# to the next segment in the current path. In this example, it will
# match the request if the first segment is equal to "users".
# You can always inspect the current path by looking at `path.curr`.
on "users" do
# If we get here it's because the previous matcher succeeded. It
# means we were able to consume a segment off the current path. More
# specifically, we consumed the "users" segment, and if we now
# inspect the `path.prev` string we will find its value is "/users".
#
# With the next matcher we want to capture a segment. Let's say a
# request is made to "/users/42". When we arrive at this point, this
# symbol will match the number "42" and store it in the inbox.
on :id do
# If there are no more segments in the request path and if the
# request method is "GET", this block will run.
get do
# Now, `inbox[:id]` has the value "42". The templates have access
# to the inbox and to any other variables defined here.
#
# The `html` macro expects a path to a template. It automatically
# appends the `.ecr` extension, which stands for Embedded Crystal
# and is part of the standard library. It also sets the content
# type to "text/html". For the html example to work, you need to
# create the file ./views/users/show.ecr with the following content:
#
# hello user <%= inbox[:id] %>
#
#
# Once you have created the file, uncomment the line below.
#
# html "views/users/show"
# As a placeholder, the following directive renders the same message
# as plain text. Once you have the HTML template in place, you can
# comment or remove both this comment and the `text` directive.
#
text "hello user #{inbox[:id]}"
end
end
end
# The `default` matcher always succeeds, but it doesn't mean the program's
# flow will always reach this point. Once a matcher succeeds and runs a
# block, the control is never returned. There's an implicit return at the
# end of every block, which stops the processing of the request and
# returns the response immediately.
#
# This route will match all the requests that don't have "users" as the
# first segment (because of the previous matcher), and it will pass the
# control to the `Guests` application, which has to be an instance of
# `Toro::Router`. This illustrates how you can compose your applications
# and split the logic among different routers.
default do
mount Guests
end
end
end
# This is another Toro application. You can mount apps on top of other Toro
# in order to achieve a modular design.
class Guests < Toro::Router
def routes
on "about" do
get do
text "about this site"
end
end
end
end
# Start the app on port 8080.
App.run do |server|
server.listen "0.0.0.0", 8080
end
```
Once you have this application running, try the requests below:
```shell
$ curl http://localhost:8080/
$ curl http://localhost:8080/about
$ curl http://localhost:8080/users/42
```
The routes are evaluated in a sandbox where the following methods
are available: `context`, `path`, `inbox`, `mount`, `basic_auth`,
`root`, `root?`, `default`, `on`, `get`, `put`, `head`, `post`,
`patch`, `delete`, `options`, `text`, `html`, `json`, `write` and
`render`.
## API
`context`: Environment variables for the request.
`path`: Helper object that tracks the previous and current path.
`inbox`: Hash with captures and potentially other variables local
to the request.
`mount`: Mounts a sub app.
`basic_auth`: Yields a username and password from the Authorization
header, and returns whatever the block returns or nil.
`root?`: Returns true if the path yet to be consumed is empty.
`root`: Receives a block and calls it only if `root?` is true.
`default`: Receives a block that will be executed inconditionally.
`on`: Receives a value to be matched, and a block that will be
executed only if the request is matched.
`get`: Receives a block and calls it only if `root?` and `get?` are
true.
`put`: Receives a block and calls it only if `root?` and `put?` are
true.
`head`: Receives a block and calls it only if `root?` and `head?`
are true.
`post`: Receives a block and calls it only if `root?` and `post?`
are true.
`patch`: Receives a block and calls it only if `root?` and `patch?`
are true.
`delete`: Receives a block and calls it only if `root?` and `delete?`
are true.
`options`: Receives a block and calls it only if `root?` and
`options?` are true.
## Matchers
The `on` method can receive a `String` to perform path matches; a
`Symbol` to perform path captures; and a boolean to match any true
values.
Each time `on` matches or captures a segment of the PATH, that part
of the path is consumed. The current and previous paths can be
queried by calling `prev` and `curr` on the `path` object: `path.prev`
returns the part of the path already consumed, and `path.curr`
provides the current version of the path. Any expression that
evaluates to a boolean can also be used as a matcher.
Captures
--------
When a symbol is provided, `on` will try to consume a segment of
the path. A segment is defined as any sequence of characters after
a slash and until either another slash or the end of the string.
The captured value is stored in the `inbox` hash under the key that
was provided as the argument to `on`. For example, after a call to
`on(:user_id)`, the value for the segment will be stored at
`inbox[:user_id]`.
Security
--------
There are no security features built into this routing library. A
framework using this library should implement the security layer.
Rendering
---------
The most basic way of returning a string is by calling the method
`text`. It sets the `Content-Type` header to `text/plain` and writes
the passed string to the response. A similar helper is called `html`:
it takes as an argument the path to an `ECR` template and renders
its content. A lower level `render` macro is available: it also
expects the path to a template, but it doesn't modify the headers.
There's a `json` helper method expecting a Crystal generic Object.
It will call the `to_json` serializer on the generic object. Please
note that you need to require JSON from the standard library in
order to use this helper (adding `require "json"` to your app should
suffice). The lower level `write` method writes a string to the
response object. It is used internally by `text` and `json`.
Running the server
------------------
If `App` is an instance of `Toro`, then you can start the server by
calling `App.run`. It yields an instance of `HTTP://Server` that you
can configure:
For example, you can start the server on port 80:
```crystal
App.run do |server|
server.listen "0.0.0.0", 80
end
```
The following example shows how to configure SSL certificates:
```crystal
App.run do |server|
ssl = OpenSSL::SSL::Context::Server.new
ssl.private_key = "path/to/private_key"
ssl.certificate_chain = "path/to/certificate_chain"
server.tls = ssl
server.listen "0.0.0.0", 443
end
```
Refer to Crystal's documentation for more options.
Status codes
------------
The default status code is `404`. It can be changed and queried
with the `status` method:
```crystal
status
#=> 404
status 200
status
#=> 200
```
When a request method matcher succeeds, the status code for the
request is changed to `200`.
Basic Auth
----------
The `basic_auth` method checks the `Authentication` header and, if
present, yields to the block the values for username and password.
Here's an example of how you can use it:
```crystal
class A < Toro::Router
def users(user : User)
get do
text "Hello #{user.name}"
end
end
def users(user : Nil)
get do
text "Hello guest!"
end
end
def routes
user = basic_auth do |name, pass|
User.authenticate(name, pass)
end
users(user)
end
end
```
The example overloads the `users` method so that it can deal both
with instances of `User` and with `nil`. The flow of your router
will naturally continue in one of those methods. You are free to
define any other methods like `users` in order to split the logic
of your application.
To illustrate the `basic_auth` feature we used an imaginary `User`
class that responds to the `authenticate` method and returns either
an instance of `User` or nil.
## Installation
Add this to your application's `shard.yml`:
```yaml
dependencies:
toro:
github: soveran/toro
branch: master
```
Documentation
Built from the current release. The first visit to a release nobody has asked for starts its build.
Links
This release
- Version
0.3.0- Tagged
- Jan 27, 2024
- Commit
d1ad29630764- Indexed
- not yet
Dependents
No indexed shard depends on this one yet.
Repository
github.com/soveran/toro
Metadata
- Created
- Aug 12, 2026
- Updated
- Aug 12, 2026
- Synced
- Aug 12, 2026
- Versions
- 12