Components overview

Onyx Framework consists of a number of components. You can combine them as much as you want in your applications, for example, you can take the ORM but stay with your favorite web framework and vice versa.

HTTP

Onyx::HTTP component includes powerful modules to build scalable web applications. It allows to build REST APIs which have separate business logic and rendering layers, type-safe params parsing, greater control flow and also convenient websocket channels.

A model for example:

class Models::User
  property id : Int32?
  property name : String?

  def initialize(@id : Int32?, @name : String?)
  end
end

Views take care of rendering:

struct Views::User
  include Onyx::HTTP::View

  def initialize(@user : Models::User)
  end

  json do
    object do
      field "id", @user.id
      field "name", @user.name
    end
  end
end

Endpoints encapsulate business logic:

struct Endpoints::Users::Get
  include Onyx::HTTP::Endpoint

  params do
    path do
      type id : Int32
    end
  end

  errors do
    type UserNotFound(404)
  end

  def call
    raise UserNotFound.new unless path.id == 42

    user = Models::User.new(42, "John")
    return Views::User.new(user)
  end
end

Channels conveniently wrap websockets:

class Channels::Echo
  include Onyx::HTTP::Channel

  def on_message(message)
    socket.send(message)
  end
end

Server puts it all together:

require "onyx/rest"

require "models/**"
require "endpoints/**"
require "views/**"
require "channels/**"

Onyx.get "/users/:id", Endpoints::Users::Get
Onyx.ws "/echo", Channels::Echo

Onyx.listen

SQL

Onyx::SQL is an SQL ORM for databases implementing crystal-db interface. This includes SQLite, MySQL, PostgreSQL, Cassandra and more.

The component includes convenient mapping schema defintion:

require "onyx/sql"

class Models::User
  include Onyx::SQL::Model

  schema do
    pkey id : Int32
    type name : String, not_null: true
    type age : Int32
    type created_at : Time, not_null: true, default: true
    type updated_at : Time
    type posts : Array(Post), foreign_key: "author_id"
  end
end

class Models::Post
  include Onyx::SQL::Model

  schema do
    pkey id : Int32
    type content : String, not_null: true
    type created_at : Time, not_null: true, default: true
    type updated_at : Time
    type author : User, key: "author_id"
  end
end

A powerful type-safe SQL query builder:

user = User.new(name: "John", age: 18)
user.insert.to_s # => "INSERT INTO users (name, age) VALUES (?, ?)"

query = User.where(name: "John").or("age >= 18").select(:id)
query.to_s # "SELECT users.id FROM users WHERE name = ? or age >= ?"

query = User.where(name: 42) # Compile-time error (name must be String, not Int32)

And a repository to wrap a database instance and execute the queries:

user = Onyx.query(User.where(id: 42)).first
pp user # <User @name="John" @age=18>

EDA

Onyx::EDA allows to implement Event-Driven Architecture in your applications to make them reactive. See the Distributed websocket chat in 40 lines of code blog post for a real-life example.

require "onyx/eda"

struct MyEvent
  include Onyx::EDA::Event

  def initialize(@foo : String)
  end
end

Onyx.subscribe(Object, MyEvent) do |event|
  puts "Got MyEvent with foo = #{event.foo}"
end

spawn Onyx.emit(MyEvent.new("bar"))

Helpers

The framework also includes some helper modules for everyday development. These helpers are automatically required whenever you require any of the Onyx components or just the "onyx" itself.

Env

First of all, it sets CRYSTAL_ENV environment variable to "development" if not set yet. Then it loads other environment variables from .env files in this order, overwriting if defined multiple times:

  1. .env file
  2. .env.local file
  3. .env.#{CRYSTAL_ENV} file
  4. .env.#{CRYSTAL_ENV}.local file

It also enables runtime_env and buildtime_env top-level macros which raise if an environment variable is not defined.

require "onyx"
# or
require "onyx/env"

# At this point, all variables are loaded from .env files
#

# Will raise on program start if DATABASE_URL variable is missing
runtime_env DATABASE_URL

Logger

Enables the beautiful singleton Logger instance.

require "onyx"
# or
require "onyx/logger"

Onyx.logger.info("Hello world!")
 INFO [12:45:52.520] Hello world!