I've been playing around with crystal for years, and absolutely love it (I'm a full-time ruby dev using Sequel as an ORM). I wish I had more time to spend with crystal. Kemal (referenced above) is really good, and excellent with websockets.
That said, I started playing around with the Marten framework and while it's really new, it's an amazingly good start. I absolutely love the way that models automatically create migrations. Using Sqlite as a default is good for probably 99% of uses cases, and absolutely good for learning the framework (Sqlite itself is amazingly powerful).
I've been wondering what new shiny toys I was going to play around with over vacation during the holidays, and I now have my answer :).
Url with variables routing? Csrf generation? Session loading? Forms processing? Templating? ORM? Assets pipeline integration?
If you're doing a very simple api, maybe stdlib will do. But for actual interactive webapp, it's not even close with what is normally used from web frameworks.
No. But then I find that trivial to implement. Takes around 10 lines of code.
> Csrf generation?
Yes. You can generate secure tokens and send them in headers.
> Session loading?
No. And this one is a pain point. But then I didn't find any session libs in go or nodejs that fitted my needs, so I have no issues rolling my own.
> Forms processing?
Not sure whats the need here. Do you mean validating the inputs?
Again thats just a few lines of code.
> Templating?
ECR Templating in stdlib works well.
> ORM?
I do not use ORMs as I prefer to write my sql queries by hand. Crystal provides object mapping with query results by default.
> Assets pipeline integration?
Not very sure what you mean here.
Overall, I personally feel that I can get all that i need for my app in about a couple of dozen lines of code and I don't really need the emntal overhead of learning a new framework. But I suppose others will have a different view point.
Writing boilerplate code manually is not something that should be touted as best practice esp if you intend to reinvent the wheel every time you want a use case implemented that was probably covered thoroughly by a framework somewhere else.
With frameworks esp the ones like RoR, the Convention over Configuration (CoC) consideration trumps all the purported benefits that may arise from building your own framework from scratch.
You just learn the fundamentals of one of the most robust and well-established frameworks once, and forget about all the fluff and cruft for good, and just focus on building the business logic and satisfying the user's/customer's needs.
The Not Invented There (NIT) anti-pattern is one hell of a drug!
well one of the weakest point of crystal is it's slow compiling time. A full-featured framework for crystal looks nice on paper, but after your app grow it will become a hinder instead, solely for the additional compiling time overhead of all the nice features the framework provided.
It hurts my productivity so bad I have to split the code base to multi independent services, each one use a simple server to serve some part of the system. After this those frameworks just cease to being helpful.
It's 10 lines here, 10 lines there. But they're not really 10 - in practice that's going to be 100 once you get past the most trivial use case and add good error handling. Then there are tests to make sure it works. Once you write all of those bits you need properly and start sharing it with your next web app and add some generalisation/config... you've created half a custom framework.
My side project https://totalrealreturns.com/ is now about 5k lines of Crystal. There are some rough edges: in particular I think it could use a better templating solution (a port of HAML would be ideal!), and there are some failure modes with the Redis connection pool that have required workarounds.
I'm now starting to use Crystal for internal backend infrastructure and microservices.
For anyone who wants to kick the tires on Crystal, I built a crystal-docker-quickstart project template: https://github.com/compumike/crystal-docker-quickstart works without having to install anything locally. (Assuming you have docker.) You can have your own, home-built "Hello world" static binary in under a minute:
git clone https://github.com/compumike/crystal-docker-quickstart.git my_app && cd my_app && ./d_dev
# in the shell that appears:
make && out/my_app
Then just edit src/main.cr and you're off and running :)
Thanks :) No CDN actually -- it's just a side project served straight from a single VPS at the moment.
You're probably just seeing the speed of response caching (which I've written in Crystal too, using Redis and local disk as storage backends). If you request a new ticker symbol that isn't already cached, or specify custom date ranges, it will recompute or even have to fetch data from upstream data provider, and those requests will take a bit longer.
Also, the whole site is currently served up as a single HTTP request, except for a few external JS and CSS files (Bootstrap, uPlot) which are served from public CDNs.
We use it in production for our products at https://ark.fm and are actively working to move all of our Rails apps over to Crystal which ironically this framework looks great and may be a good choice for us. (Thank you to the author!)
Our email service runs an image proxy (to hide your IP from email trackers) with probably about 30-50 lines of code in Crystal that's been in production for multiple years and it just works.
Current project (in the cloud hosting space) that will heavily use Crystal for backend management and WASM which was recently introduced into Crystal and is a game changer for our use-case.
Also wrote a command-line tool with it and it feels like I'm cheating at how easy it is to do so.
Coming from the Ruby/Rails world where you have to cap deploy/bundle etc to push to a production server and hoping nothing breaks on dependancies makes Crystal's single binary deploy a breath of fresh air...not to mention the performance. We run our own hardware so being able to (eventually once we've moved over fully to Crystal) run the same workloads on LESS hardware with electric prices spiking is financially attractive.
As you can tell I'm a fan boy. I'm betting heavily on Crystal and going all in with our stack.
The Crunchy Bridge CLI is written in crystal: https://github.com/CrunchyData/bridge-cli/. The program that monitors databases is likewise a crystal program. There are a few other crystal programs rolling around.
My biggest grievance with Crystal is the lack of incremental compilation. I understand why it's difficult to achieve given the language model, and some of the conveniences of that model. I've been watching the Crystal Interpreter with interest, it would be pretty cool if it could speed up my `M-x crystal-spec-line` interactions, along with all the other usual benefits of an interpreter.
On the other hand, there's a lot to like about Crystal: it has a convenient yet powerful type system, good error messages, good support for invoking C subroutines in libraries, and generates efficient object code in a straightforward manner.
I have recently learned to use it to create an AutoHotkey for Linux implementation (https://github.com/phil294/AHK_X11), and it's been a delightful experience. If you like programming in Go and Ruby syntax, this will be your go to language. Other than that, it bears but few surprises, which I would consider a good thing. Its major downside is its compilation time and poor IDE support. Also, you should not be afraid to search through Crystal's GitHub issues or dig into the well designed stdlib's source for more exotic use cases. However, the latter is as easily accessible as is your own code, and the community around Crystal seems quite friendly and welcoming. Contrary to sibling comments, I find it well suited for programs outside of web development as well.
The only time I used Crystal for work was for creating zip files from a virtual file system in the database that fetched the real file files from S3.
It was pretty easy to write, but I kept on having some issues with the part of the standard library that handled the zipping.
As Crystal is kinda a small community, I didn't get a lot of feedback from the forums. So I just took a day to rewrite in Go, and its zip library worked great.
I'm sure I could have got it working with Crystal if I had more time. I haven't done anything else, but it was fun writing it.
I enjoyed poking around at Crystal, but its library ecosystem seems heavily oriented towards web development (understandably, because its creators are a web dev consultancy), and I don't do much web development for my hobby programming.
Also, I found that its static analysis tooling (eg LSP server) was really slow. Compiling small programs was also rather slow, and it generated large binaries.
I also had some difficulty understanding the documentation related to importing and including modules and classes; it seemed written from a perspective assuming that the reader already knows Ruby, which I do not.
At the time, I was also curious about Common Lisp and Nim, so I spent a bunch of time learning the former, and have lately been writing small personal scripts/tools using the latter.
My experience using Python for backend web development has been mixed. If I had to do work like that professionally in the future, I would be happy to do it in a language like Crystal. It seems a lot more comfortable to me than Go, less intimidating than a JVM language (Kotlin?), and less "weird" than Common Lisp or a functional language like OCaml.
It doesn't seem like a bad choice for a company willing to invest in engineers while they learn the language, and maybe invest in writing some of their own libraries while the ecosystem is still young. I felt like I was able to get productive fairly quickly.
I think an interesting project would be to write a fairly simple web server (maybe some basic REST CRUD thing using idiomatic code) in several languages and frameworks, and compare their performance, ease of getting started, and complexity of the code. I had started working on this myself at one point, and it seems like it could be worthwhile to pick up again, especially now that all of the language ecosystems I had in mind have matured a little bit.
I used to be a huge Ruby fan but I got to say Julia had proven to be a much nicer language than Python or Ruby ever was. It gives stronger typing so it feels a bit more like a statically typed language in terms of safeguarding you, but without getting in the way. Lot of the power of LISP without the odd syntax.
Problem is the community is not web focused so you may not find enough choice in web libraries to your taste.
I am a data scientist, so I've always been interested in Julia and have had fun messing around with it, even if I can never find an excuse to use it at work.
Have you been using Julia for a "general purpose" programming? It seemed very domain specific to me, more like an upgrade from R and Matlab then something you would write a web server with.
> I enjoy writing Ruby, but I hate maintaining it.
Sorbet helps immensely in this regard (though it does perhaps make writing ruby a bit less joyful). The Sorbet language server works great (at least with VSCode) - I have pretty reliable go-to-definition and intellisense.
I would imagine that if crystal had a decent language server the experience would be even better than Ruby+Sorbet.
Refactoring tools are nice (I guess; I've honestly only used them in Java; I'm mostly an Emacs person), but my main gripes with Ruby maintenance are harder to fix with type annotations:
- In duck-typed languages you have to write a lot of tests to do verify things that the compiler does for you in statically typed languages. That neuters much of the benefit of the concision of such languages. Crystal shoots for the best of both worlds. (Static type checking, but usually without explicit signatures.) My main refactoring tool in statically typed languages is the compiler: if I break something, it'll tell me.
- Monkey-patching, open classes, etc. I do it too. Pretty much every Ruby-ist does. But it makes it damn near impossible to track down bugs sometimes, because even finding out what file the relevant code is in isn't trivial. Again, Crystal seems to mostly side-step that pitfall.
- Speed. I don't even attempt to write fast code in Ruby (though I have written a few C++ extensions for Ruby in a pinch). But if I could get near-to systems-language level performance out of something that was almost Ruby, that'd be pretty amazeballs.
> My main refactoring tool in statically typed languages is the compiler: if I break something, it'll tell me.
That really helps. But what's even better in practice is if compiler is integrated into an IDE which can highlight type-errors already while you are editing the code, before you explicitly invoke the compiler. For instance Eclipse IDE does that for Java code.
If you need to run the compiler by hand before you get any error-messages it becomes a huge delay and you lose the immediate feedback which is needed to keep your focus on the code, not on compiler error-messages.
enum class Value
{
Bar,
Baz,
Quux
};
int foo(Value a)
{
return 1;
}
In a strongly typed language you don't need to do any of the input validation. I'm not sure that I'd do that level of granularity of tests in an application, but I spend a lot of times writing libraries, where it's pretty important.
You also have to call all code paths with your tests in Ruby because otherwise you won't hit errors. Just basic, "this thing runs, the methods it calls exist, it returns a value, and it's of this type" is all guaranteed in a strongly typed language and doesn't need to be explicitly tested.
I don't usually have code that tests arguments and raise exceptions, in my code and in my customers code. As those code paths don't exist, we are not writing those tests.
If a method accepts only values >= 0 (eg, square root) we test that we handle negative numbers as the documentation of the API says. That should be the same in C++.
When we refactor the implementation of a web app endpoint we only test that the request still yields the response. Apparently this approach worked well enough.
If somebody sends a foo instead of bar, baz or quux they'll get a 500 with the error message in the documentation and that's it.
def foo(a)
if a == 1
bar
else
baz
end
end
def test_foo
foo(1)
foo(2)
end
In the C++ equivalent there's no need to test those values because the compiler will tell you that the methods exist. In Ruby you need to test them so that you catch those paths in refactoring.
I'm not sure I'm understanding this. Don't you have to test that the implementation of foo returns the correct result (or writes the correct value in the db) both in Ruby and in C++? But I've not used compiled languages for a long time so maybe I forgot something important.
Not every code path really has to be tested. Let's imagine that they're e.g. displaying a message box. I feel pretty confident that if I call a system function to display a message box, it'll display a message box. So in C++ I wouldn't test that.
In Ruby, I'd need to make sure that I made calls to both code paths so that if the function signature for displaying a message box changed, that I'd get an error in my tests.
This isn't a hypothetical: I write Qt applications in C++ and Rails apps in Ruby. The pain of switching major versions in Qt is trivial compared to Rails, mainly because once it compiles in C++, it probably also works.
Could you imagine just assuming that a Rails app worked after a major Rails version upgrade just because it didn't throw an error on startup? That's really the experience of working in statically typed languages.
You are right, the compiler for a statically typed language can do checks that are impossible to perform for a language like Ruby, that could also create methods calls and add arguments at runtime.
I tend not to use metaprogramming except some object.send(method, args) when strictly necessary. The reason is that it slows down the team when trying to understand what a piece of code does and if not properly understood it generates bugs. Metaprogramming is buried down into third party libraries (e.g.: Rails) but we are not testing it there.
I occasionally get bugs that a compiler would catch, maybe one every year or two. One happened on Python this year. I can't remember what. I should dig into slack: I remember I wrote a note to my customer (maybe the classic id as string vs int?) But the time not lost writing type annotations is immense. I wasted cumulative weeks on that when I was working in Java 10+ years ago. And cumulative weeks of malloc/free when I was working in C before Java. All considered I prefer the occasional bug and extra test in Ruby and Python. I consider that path (GC and no types) an improvement. Of course it costs performances but customers are happy running Ruby and Python and are still in business. It can't be wrong
It's not just at runtime -- again, a common case for me (I maintain Ruby, Java, and C++ codebases that are 15 years old) are in major framework upgrades.
I've got Rails apps that were written for Rails 2 that are now on Rails 6. A Rails upgrade is a multi-day, multi-person effort. A Qt upgrade is usually about an hour for one person.
My argument isn't that C++ on the whole is saner than Ruby. There are advantages to both. (Though, to be honest, refactoring Java, which I otherwise dislike, is positively sublime.) My argument is that with something like Crystal where you basically write Ruby and get the advantages of C++ and Java, that that's a net win.
The other part of my argument is that the sum total of tests + code for Ruby isn't significantly more concise than just using a statically typed language with more minimal tests. They're really pretty close. And I personally find the roteness of static typing less mentally taxing than having to imagine all code paths in a duck-typed function.
I genuinely believe that the success of duck typed languages is their support of fast iteration for cowboy coding. I'm wholly convinced that for long lived code bases, static typing makes far more sense. But that's why I'm happy to see languages that support it with minimal to trivial cognitive overhead.
That said: almost always the right reason to pick a particular language is the frameworks. I'm doing some ML stuff in Python right now, even though I hate Python, because Python has the best libs for it. At the end of the day, purity is rarely a strong argument, particularly when it means reinventing the wheel in the language du jour.
Not a Crystal expert, but the Marten tutorial mentions a `crystal play` command that will run a web-interface with an interactive "playground" that you can interact with your program's functionality in: https://martenframework.com/docs/getting-started/tutorial#in....
> Do you get a single binary for deployment which includes all assets, such as a .war?
I think they mean such as an .exe
> Is there debugger, similar to the one in rails?
What debugger are you using for Rails? In my experience Rails has been significantly lacking in a debugger (it's improved recently with the debug gem.)
A .war file is a Web Application Resource file. It’s a modification of a Jar file which is meant for easy deployment to Java servers.
Anyways the answer is, Crystal can be statically compiled in most circumstances. Last I checked OpenSSL was one of the exceptions, but it’s been a year or more since.
Amber packs all the static assets into the binary, Lucky doesn’t. I would doubt that Marten does. If not, you get your app server in one binary and the front end assets get deployed separately.
Regarding ruby debugging, byebug and pry have been the two approaches for a long time. Pry is a fancy line editor and doesn’t come with a true debugger, but it can be augmented with one. Byebug has full debugging but the interface is a bit archaic. Both work fine with Rails.
Interactive debugging with Crystal is still a bit of a pain, but LLDB works with a little fiddling.
Love Crystal and love seeing it here more. Ruby has been day job for years, but Crystal is so much fun for side projects and random things to build. Easily approachable, especially for Ruby devs.
I wonder if it's because Crystal targets so many Ruby developers... Folks are trying to make the next Ruby on Rails and re-create that lightning in a bottle. Hence all the competition in the space.
Nim has this problem where devs like to stitch together libs instead of using an all-in-one solution. It's a tough nut to crack, if anyone does build a "Phoenix for Nim" we should see significant uptick in usage.
What's your definition of Rails clone? MVC? If it is, sure, yeah, this and a whole bunch of other frameworks are Rails clones. Laravel is a Rails clone. Django is a Rails clone. We're all Rails clones!
Does the design of some of the logic look heavily-inspired by Rails? Sure, and I think it's a great choice.
But in reality, calling it a Rails clone is a disservice to Rails. Rails is much more than MVC and patterns implemented from Patterns of Enterprise Application Architecture. The power of Rails isn't in any single one of the patterns — sure, it may have started that way when it was common to ship PHP copy-pasted from php.net and deployed using Filezilla (or explorer.exe!) — but since, what, 2.3? the power of it has been it's ecosystem: nothing can come close to the vastness and maturity of Rubygems.
I don't know, the vastness of the JS ecosystem seems bigger to me? I have come across many third party service providers that do not offer Ruby SDKs but do for JS,Java,Python etc...
I certainly don't mind if a framework is boring, if it comes with qualities I am interested in. Being boring is not one of them – but neither is being exciting.
- What's different from Lucky https://luckyframework.org/
- What's different from Amber https://amberframework.org/
There's probably something exciting there, but finding out the differences in features for yourself... please don't make me do that.