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:

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!