# Lucky: a Crystal web framework that moves bugs to compile time

> Lucky is a full-stack Crystal web framework built around compile-time checks for routes, pages and database columns. It suits teams already writing Crystal or willing to learn it, and it is a poor fit for anyone who needs Ruby gems or a large hiring pool.

**luckyframework/lucky** — A full-featured Crystal web framework that catches bugs for you, runs incredibly fast, and helps you write code that lasts.

- Repository: https://github.com/luckyframework/lucky
- Website: https://luckyframework.org
- Stars: 2,730 · Forks: 172
- Language: Crystal
- License: MIT
- Published: 2026-09-28 · Updated: 2026-09-28 · Language: en
- Canonical page: https://hysenlabs.com/projects/luckyframework-lucky

## The bug class Lucky is built to remove

Lucky targets a specific family of defects: the ones that only appear when a request reaches production. A route parameter that nobody reads, a page rendered without the data it needs, a nilable column printed straight into HTML. In most frameworks these surface as a 500 or an empty string. In Lucky they surface as a compiler error.

The README states the goal plainly: "prevent bugs, forget about most performance issues, and spend more time on code instead of debugging and fixing tests." That is the whole pitch, and it is narrower than it sounds. Lucky is not trying to be the fastest framework or the most minimal one. It is trying to make an entire category of runtime mistake impossible to ship.

The audience is teams that already write Crystal, or teams coming from Rails who are willing to trade the Ruby ecosystem for a language that compiles. The README links a Hacker Noon article titled "Ruby on Rails to Lucky on Crystal" for exactly that reader. If your team has no Crystal experience and no appetite to gain it, nothing else in this review matters.

## Actions, pages and queries: how the pieces connect

A Lucky application is organised around actions. An action declares a route and a block. The README shows a JSON endpoint where the route parameter `:user_id` causes a `user_id` method to be generated on the action, so the block can call `UserQuery.find(user_id)` without any manual parameter parsing.

Models are declared with a table block that lists columns and their Crystal types. A trailing question mark marks a column nilable. The README notes that Lucky sets up presence validations for required fields automatically, so `last_active_at` and `last_name` are validated because they are not nilable, while `nickname : String?` is not.

Queries inherit from an auto-generated `User::BaseQuery`. The convention is to add named scopes as instance methods that return query fragments, then chain them. The README example defines `recently_active` using `last_active_at.gt(1.week.ago)` and `sorted_by_last_name` using `last_name.lower.desc_order`, then calls `UserQuery.new.recently_active.sorted_by_last_name`. Because the columns are known at compile time, a mistyped column name is a build error rather than a runtime SQL error.

Pages are classes too. A page declares `needs users : UserQuery`, and the action passes `users: users` to `render`. If the action forgets, the compiler complains. HTML is written as Crystal method calls, so tags close automatically and whitespace is stripped. Links are built from action classes rather than path helpers: `link "New User", to: Users::New` and `Users::Show.with(user.id)`. The README argues this removes the pluralisation guesswork that path helpers introduce.

## Installing Lucky and rendering your first page

The README does not inline installation steps. It points to the Installing Lucky guides at luckyframework.org, organised by operating system, and says the guide walks you through installing a command-line utility used for generating new Lucky applications. That CLI is where a new project starts; the repository itself is the framework, and its own test setup is the two commands below.

To work on the framework from a clone, the README gives these steps:

```bash
shards install
crystal spec
```

The first resolves Crystal dependencies from `shard.yml`. The second runs the spec suite from the project root. If you only want to build an application, use the guides instead of this repository.

For a container-based environment, the repository ships a `docker-compose.yml` whose only service is named `app`, built from the local `Dockerfile` on `crystallang/crystal:latest`, mounting the repository at `/data` and overriding the command with `sleep infinity` so the container stays up while you work inside it.

```bash
docker compose up -d
docker compose exec app crystal spec
```

Once you have a generated application, the smallest useful thing to write is an action plus the page it renders. The shape below follows the README's HTML example: the action fetches users through a query and hands them to a page that declares what it needs.

```crystal
class Users::Index < BrowserAction
  get "/users" do
    users = UserQuery.new.sorted_by_last_name
    render IndexPage, users: users
  end
end
```

The thing to notice is what happens when you break it. Remove `users: users` from the render call, or change the page's `needs` declaration to a different type, and the build fails before the server starts. That feedback loop is the product.

## Where the compile-time promise costs you

The same mechanism that catches mistakes also raises the cost of changing your mind. Every column, route and page signature is baked into the compiled binary. Renaming a column is not a database migration plus a search-and-replace; it is a migration plus a full recompile, and the compiler will list every call site that broke. For a large application that is a feature. For a quick experiment it is friction.

Crystal's compile times are the second constraint. Nothing in the README claims otherwise, and the framework's own contribution workflow is `crystal spec`, which compiles the suite. If your team is used to editing a file and refreshing a browser, the iteration loop will feel slower, particularly on the first build after a dependency change.

The third limitation is the ecosystem. Lucky is a Crystal framework, so it cannot use Ruby gems. Anything you would reach for in a Rails application, from authentication to background jobs to admin panels, either has a Crystal equivalent, has to be written, or does not exist. The README mentions Lucky JumpStart as a way to "copy a real working app", which suggests the intended path for new projects is to start from an existing application rather than assemble one from libraries.

Finally, the framework's own documentation is split. The README points to the Lucky Guides site for tutorials and to a separate API documentation site for reference. Neither is reproduced in the repository, so offline reading means reading the source.

## Lucky compared with Amber, the other Crystal framework in this repository

The most direct alternative is Amber, another Crystal web framework. The README's attributions section states that Lucky's SessionHandler, CookieHandler and FlashHandler are based on Amber, so the two projects share lineage at the handler level. The difference is in what sits above the handlers.

Amber presents itself as a general MVC framework with generators and a familiar structure. Lucky's distinguishing choice is the type-checked layer: actions that generate parameter methods, queries generated from model definitions, and pages that declare their inputs. Amber does not make a missing page argument a compile error, because it does not model pages as typed classes with declared needs.

That difference decides the choice. If you want a Crystal web framework that stays close to conventional MVC and lets you move fast without the compiler enforcing contracts, Amber is the closer match. If the reason you are looking at Crystal at all is the compiler, Lucky is the framework that uses it as a design tool rather than just a language. The README's own framing supports this: the value it advertises is fewer bugs, not faster requests.

## Releases, upgrades and the MIT licence

Lucky is MIT licensed. That permits commercial use, modification and redistribution provided the copyright notice and permission notice are included. It is a permissive licence with no copyleft obligation, but it also comes with no warranty, and this review is not legal advice; read the LICENSE file in the repository if the terms matter to your organisation.

The release cadence visible in the repository is roughly one minor version per year. v1.3.0 was published on 2024-11-02, v1.4.0 on 2025-06-03, and v1.5.0 on 2026-04-23. The last push to the default branch was on 2026-08-13, which is recent, so the project is not dormant.

The upgrade cost is concentrated in breaking changes between minors. The repository carries an `UPGRADE_NOTES.md` at the top level, and its presence is the signal that upgrading is a read-then-edit task rather than a version bump. Because Lucky is a compiled framework, an upgrade also means recompiling the whole application, and any breaking change in a macro or a generated method will show up as a wall of compiler errors rather than a runtime stack trace. Budget for that.

There is also a `shard.edge.yml` and a `shard.override.yml` in the repository root, which indicates the project maintains a way to build against edge dependencies. Applications pinning to a released version should not need them.

## Conclusion

Adopt Lucky if your team already writes Crystal or wants a Rails-shaped framework where missing params and nilable columns fail the build, and if you can accept the smaller ecosystem and the compile step. Do not adopt it if you need Ruby gems, a large hiring pool, or a framework whose behaviour you cannot change without recompiling. Before committing, verify the Crystal version pinned in .crystal-version, run shards install and crystal spec in a clone, and read UPGRADE_NOTES.md for the 1.3.0 to 1.5.0 range.

## FAQ

### What is Lucky framework?

Lucky is a full-featured Crystal web framework whose stated goal is to prevent bugs, remove most performance concerns, and let developers spend more time on code than on debugging and fixing tests. It provides actions, typed models and queries, and pages written as Crystal classes.

### How do I install Lucky?

The README does not list install commands. It directs readers to the Installing Lucky guides at luckyframework.org for their operating system, which walk through installing a command-line utility used for generating new Lucky applications.

### How do I run the Lucky test suite from a clone?

The README gives two steps: run shards install to fetch the Crystal dependencies, then run crystal spec from the project root.

### Does Lucky work in Docker?

The repository includes a docker-compose.yml with a single service named app, built from the local Dockerfile on the crystallang/crystal:latest image, mounting the repository at /data and running sleep infinity so the container stays available for commands.

### What licence does Lucky use?

The repository is MIT licensed, which allows commercial use and modification as long as the copyright and permission notice are included, and provides no warranty.

## Sources

- [License: MIT](https://github.com/luckyframework/lucky/blob/main/LICENSE)
- [luckyframework/lucky on GitHub](https://github.com/luckyframework/lucky)
- [Project website](https://luckyframework.org)
- [README](https://github.com/luckyframework/lucky/blob/main/README.md)
- [Releases](https://github.com/luckyframework/lucky/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/luckyframework-lucky
