I love Ruby and I love Rails, but I’ve found myself searching for something more. Something fast so I don’t need to add caching. Something type-safe so customers see fewer bugs. Something fun to use so I actually enjoy building applications.
Would Elixir scratch my itch? It was a good start. It made it fun to write beautiful code, but I still encountered bugs in production that could have been caught with a better type system.
There’s Elm. It’s beautiful. Fun. But only available on the front-end.
I played briefly with Go and Rust, but I felt like the type-system and syntax was too unfriendly for me to enjoy using them.
At first, I was pretty put-off by Crystal. It looked almost identical to Ruby, so why would I try it?
Well eventually I did try it, and I fell in love 💙
Though Crystal looks similar to Ruby, Crystal is a different beast. It’s familiar enough to get started quickly, but new enough that it will challenge you in interesting ways. Some things I love about Crystal:
Beautiful syntax — unashamedly Ruby-like. Some people might not like it, but I love it.
Catch bugs at compile time — It gives me anxiety just writing this: Undefined method on Nil . I’ve seen this far too many times. Crystal helps catch these bugs at compile time, so they never see the light of day.
Meta-programming with macros — Crystal uses macros to generate code for you. Bye-bye boilerplate.
Incredibly fast — in many benchmarks Crystal beats Go, Elixir and sometimes raw C code. It’s quick.
Introducing Lucky, the web framework written for Crystal
The goal: prevent bugs, forget about most performance issues, and spend more time on code instead of debugging and writing tests.
In summary, make writing stunning web applications fast, fun, and easy.
Why should you try it?
Easy to pick up — intuitive APIs and helpful compile time errors help make things simpler. Guides and our helpful chat room can help if you get stuck.
Batteries included — no more cobbling together a million libraries. Lucky includes what most apps need. Emails, an ORM for working with the database, templates, webpack setup for you, automated browser testing, authentication, and easy deployment.
Lightning fast — stop worrying about performance and how many servers you need. Most responses with lots of HTML and multiple database requests handle thousands of requests per second on moderately priced servers.
Catches bugs for you — spend less time worrying about test coverage and sneaky type errors. Let the Crystal compiler help you out.
Flexible — Build server rendered HTML, use the built-in webpack config for an SPA, or build a JSON API by initializing your project with --api
No more N+1 issues — Lucky prevents slow N+1 queries by raising during development if you accidentally forget to preload associations.
Bullet points are boring, what’s it look like?
Handling an HTTP request
Calling route sets up a route for ”/users” automatically.
If you want you can set up custom routes like get “sign_in” for non REST routes.
If you forget to pass something a page needs, Lucky will let you know at compile-time.
Setting up a model
Sets up the columns that you’d like to use, along with their types
You can add ? to the type when the column can be nil . Crystal will then help you remember not to call methods on it that won’t work.
Lucky will set up presence validations for required fields (name and email since they are not marked as nilable).
Querying the database
User::BaseQuery is automatically generated when you define a model. Inherit from it to customize queries.
Set up named scopes with instance methods.
Lucky sets up methods for all the columns so that if you mistype a column name it will tell you at compile-time.
Use the lower method on String column to make sure Postgres sorts everything on lowercase. Lucky has lots of powerful abstraction for creating complex queries, and type specific methods (like lower).
Preventing the dreaded N+1 query
If you forget to preload an association, Lucky will raise a runtime error in the development and test environment. No more accidentally slowing down requests with N+1 queries.
In production, the query does not raise. That way if you accidentally make a change and don’t have a test for it, it won’t throw errors for your users. Instead you can fix the performance issue later.
Sometimes you just want to load an association without the preload dance and you’re ok with a slight performance hit. Add ! to load the association without triggering a runtime error.
needs users : UserQuery tells the compiler that it must be passed users of the type UserQuery.
If you forget to pass something that a page needs, it will let you know at compile time. Fewer bugs and faster debugging.
Write tags with Crystal methods. Tags are automatically closed and whitespace is removed.
Easily extract named methods since pages are made of regular classes and methods. This make your HTML pages incredibly easy to read.
Link to other pages with ease. Just use the action name: Users::New. Pass params with Users::Show.with(user.id). No more trying to remember path helpers and whether the helper is pluralized or not.
If you forget to pass a param to a route, Lucky will let you know at compile-time.
Since we defined column age : Int32? as nilable, Lucky would fail to compile the page if you just did text user.age since it disallows printing nil. So instead we add a fallback. No more accidentally printing empty text in HTML!
There’s always a catch, and that’s true with Crystal and Lucky too.
Neither Lucky nor Crystal are at 1.0, which means with some new versions you’ll have to make changes when upgrading. There are also fewer libraries and tutorials than in Ruby and other more mature languages.
For some people, this is a fun opportunity to get in at the ground floor and help shape the community, but it’s not for everyone.