The lineup
What already works in Ticket today. Every example on this page comes from the framework's own example app and tests.
Routes the way Rails taught you
root, verb lines, resources with only and except, nesting, member, collection and namespace. The zone generates the router and a typed helper for each named route, like article_path(id).
polar start -- routes prints the table, just like rails routes.
routes
root -> PagesController.home
get /about -> PagesController.about as about
resources articles -> ArticlesController only index show new create
namespace admin
get /stats -> StatsController.show as stats
$ polar start -- routes
root_path GET / PagesController.home
about_path GET /about PagesController.about
articles_path GET /articles ArticlesController.index
new_article_path GET /articles/new ArticlesController.new
POST /articles ArticlesController.create
article_path GET /articles/:id ArticlesController.show
admin_stats_path GET /admin/stats StatsController.show
Controllers are modules of plain functions
Every action takes a Conn and returns a Response. Its effects are in the signature, so you can see at a glance which actions touch state and which can halt.
redirect_to answers a form post with a 303, so the browser follows it with a GET.
functions
show(conn: Conn) -> Response / {Mut, Throws<Halt>} {
let article = find_or_halt(conn)
Response.render(Layout.app(article.title, Articles.show(article)))
}
create(conn: Conn) -> Response / {Mut} {
let article = Article.create(title_of(conn))
Response.redirect_to(conn, Paths.article_path(article.id))
}
Markup that type-checks
A view is a header plus markup, and it compiles to a function that returns Html. Holes are escaped, a Bool attribute appears or disappears, and a None leaves the attribute out.
Shared components go in a view_component zone and are used as <UI.Card> from any module.
view_component
Card(title: String, children: Html)
<section class="card">
<h2>{title}</h2>
{children}
</section>
views
item(article: Article)
<li><a href={Paths.article_path(article.id)}>{article.title}</a></li>
wrapped()
<Card title="Latest">
<p>Fresh off the press</p>
</Card>
Filters without magic
A filter is a function from Conn to Result<Response, Conn>. Ok lets the request through and Err answers it. Action.before runs them in order, like Rails' before_action.
Deep inside a helper, throw Halt(response) answers the request on the spot.
functions
show(conn: Conn) -> Response / {Mut} {
Action.before([require_token], stats)(conn)
}
require_token(conn: Conn) -> Result<Response, Conn> {
if Params.string(conn, "token") == Some("secret") {
Ok(conn)
} else {
Err(Response.text(401, "unauthorized"))
}
}
Typos fail the build, not the request
Ticket's zones are Polar plugins, so mistakes in routes and views are reported by polar check on the line you wrote, with a hint for the fix.
routes
resources posts -> PostController
$ polar check
error[POLAR0901]: no module `PostController` in `uses`
--> src/routes.px:12:23
|
12 | resources posts -> PostController
| ^^^^^^^^^^^^^^
|
= help: a controller is a module in `uses`, named by
its last segment or its `as` alias
Tour dates
Ticket is built one milestone at a time. Here's where it's playing.
-
F0
Played
Skeleton
The package builds, and an example app depends on it and serves a page.
-
F1
Played
Request to response
Conn and params, views, resource routing, controllers and the
ticketCLI (new,server,routes). -
F2
On now
Data
SQLite through a
Dbeffect, migrations and typed queries are done. Validations and associations are next. -
F3
Later
App ergonomics
Forms, sessions, flash and CSRF, static files, generators and test helpers.
Try it early
Ticket isn't published yet. Clone it next to your app and add it as a path dependency. You'll need the polar toolchain and Node 22.5 or newer.
# polar.toml
[project]
name = "app"
hosts = ["Node"]
main = "src/routes.px"
[dependencies]
ticket = { path = "../ticket" }
[run]
launcher = "ticket"
options = { port = 3000 }
$ polar run
# builds every host and serves on :3000
$ polar start -- routes
# prints the route table
A Rails-style ticket command with ticket new, ticket server and ticket routes is the next thing being built.