I’m proudly presenting you Onyx Framework – the essence of my two years experience with Crystal!
https://onyxframework.org
https://github.com/onyxframework
Introduction
The framework consists of multiple components:
- Onyx::HTTP is a collection of HTTP handlers, which essentialy are building blocks for your web application
- Onyx::REST is a REST layer on top of Onyx::HTTP which implements splitting business and rendering logic into Actions and Views, inspired by Hanami
- Onyx::SQL is a database-agnostic SQL ORM
- Onyx/Onyx is a collection of macros to make the development eaiser. They act as DSL to hide the boilerplate code
Onyx Framework is designed to be both powerful and adoptable by Crystal newcomers. It utilizes complex concepts like annotations and generics, but hides it under beautiful DSL. Such an approach makes it possible to write less code, thus reducing the possibility of bugs, but still make it easy to extend the framework’s functionality.
Onyx Framework is built with scalability in mind. It is able to grow with the developer’s knowledge of Crystal and the framework itself. Almost every piece of code prefers composition over inheritance and respects configuration over convention.
You are not forced to use certain components to achieve your goals. For example, you can use Onyx::SQL in your Amber project or mix Onyx::REST with Granite and Crecto. Furthermore, it is quite easy to write custom renderers for Onyx::REST and custom database converters for Onyx::SQL.
Code example
Here is an example of a simple JSON blogposts application:
# models/post.cr
class Post
include Onyx::SQL::Model
# Define DB mapping for this model
schema posts do
pkey id : Int32
type title : String
type content : String
type created_at : Time, default: true
end
end
# views/posts.cr
struct Views::Posts
include Onyx::REST::View
def initialize(@posts : Enumerable(Post))
end
# Define the way this view is rendered into JSON
# It can be achieved in multiple ways, this is just one of them
json({
posts: @post.map do |post|
{
"id": post.id,
"title": post.title,
"content": post.content,
"createdAt": post.created_at,
}
end,
})
end
# actions/posts/create.cr
struct Actions::Posts::Create
include Onyx::REST::Action
# Define type-safe params for this endpoint
# In this case, we define nested JSON parameters
params do
json do
type post do
type title : String
type content : String
end
end
end
# Define known REST errors with their status codes
# for this endpoint
errors do
type JSONRequired(400)
type DuplicatedTitle(422)
end
def call
json = params.json
raise JSONRequired.new unless json
existing_post = Onyx.query(Post
.where(title: json.post.title)
.select(:id)
).first?
raise DuplicatedTitle.new if existing_post
post = Post.new(
title: json.post.title,
content: json.post.content
)
Onyx.exec(post.insert)
status(201)
end
end
# actions/posts/index.cr
struct Actions::Posts::Index
include Onyx::REST::Action
def call
posts = Onyx.query(Post.all.order_by(:created_at, :desc))
return Views::Posts.new(posts)
end
end
# app.cr
require "pg"
require "onyx/rest"
require "onyx/sql"
require "./actions/**/*"
require "./models/**/*"
require "./views/**/*"
Onyx.post "/posts", Actions::Posts::Create
Onyx.get "/posts", Actions::Posts::Index
Onyx.render(:json) # Will render views as JSON
Onyx.listen
Fun facts
Onyx::REST has a long history from Oct’17 till nowdays. It has been renamed and overhauled multiple times, resulting in 15,890 LOC in additions and 13,947 LOC in deletions. It has been previously known as Prism.
Onyx::SQL once was named Core. And its first version has been released in Sep’17. 23,317 LOC in additions and 17,259 LOC in deletions.
I’ve spent over 900 hours this year coding on Crystal:
Join the community
I’ve prepared platforms for productive communication:
- Onyx Framework community in Gitter with multiple rooms
- Onyx Framework Twitter account
The framework relies on functionaily of some of my shards, so there are Gitter rooms for them too:
- Lobby to discuss all my shards
- HTTP::Params::Serializable room to discuss http-params-serializable
- Migrate room to discuss migrate.cr
Next steps
Releasing Onyx Framework was in my New Year resolutions list as well as updating Crystal World, which now uses Onyx as its foundation
Now I’m going to write some articles related to Onyx:
- A thorough guide to create JSON APIs with Onyx
- A multi-part tutorial on how to re-create Crystal World from scratch
- A guide on how to create a distributed web socket chat with Onyx utilizing Onyx::EDA, which is WIP Event-Driven Architecture framework
All these articles are going to be both in English and Russian language and I hope to see you among the readers. To never miss it, follow my personal Twitter account – @vladfaust!