Awesome
Runcobo
Introduction
Runcobo is a general purpose framework built on Crystal.
Commands
runcobo [<commands>...] [<arguments>...]
Commands:
routes - Print all routes of the app.
version - Print the current version of the runcobo.
help - Print usage synopsis.
Options:
-h, --help Print usage synopsis.
-v, --version Print the current version of the runcobo.
Project Architecture
lib/ # Library
src/
main.cr # Entry file
actions/ # Actions Directory
...
assets/ # Assets Directory
views/ # Views Directory
layouts/ # Layouts directory
...
models/ # Models Directory
...
shards.yml # The packages congfiuration file
shards.lock # The lock file for packages congfiuration file
Keep in this architecture to use layouts
macro, render
macro.
Design Architecture
MVC (Model-View-Controller)
Design Principles
- Simple Design must be simple, both in implementation and interface.
- Intuitive Design must be intuitive.
- Consistent Design must be consistent.
Installation
Install Crystal
Crystal is a language for humans and computers.
Crystal is a type safe, compiled language inspired by the simplicity of Ruby. Type safety means more errors are caught by the compiler during development, so you can be more confident about your code working in production.
See https://crystal-lang.org/install/ to install Crystal.
Install Runcobo
You can install Runcobo from sources. Other installations are working on.
curl -L https://github.com/runcobo/runcobo/archive/stable.zip --output runcobo.zip
unzip runcobo.zip
cd runcobo-stable/
sudo make install
runcobo -v
Getting Started
1.Init a Crystal project.
crystal init app demo && cd demo
2.Add the dependency to your shard.yml and run shards install
.
dependencies:
runcobo:
github: runcobo/runcobo
3.Write down the following code in src/demo.cr
.
require "runcobo"
class Api::V1::Add < BaseAction
get "/api/v1/add"
query NamedTuple(a: Int32, b: Int32)
call do
sum = params[:a] + params[:b]
render_plain sum.to_s
end
end
Runcobo.start
4.Run server.
crystal src/demo.cr
5.Send request.
curl "http://0.0.0.0:3000/api/v1/add?a=1&b=2"
6.Auto restart server.
# Use nodemon to watch file changed and auto restart server.
sudo npm install -g nodemon
nodemon -e "cr,water,jbuilder,yml" --exec "crystal run" src/demo.cr
Route
Runcobo declares routes in every Actions. If you access the route, it will run into related Action.
In this way, you can know the routes of current Action quickly.
You can declare RESTful routes as you want or not.
In my personal view, RESTful is too abstact when you were far way from a CRUD system like Backend Management System.
When writing API, I believe urls should be named like functions, not objects or resources.
When writing a Backend Management System, enjoy RESTful.
Routes Declaration
Runcobo declares routes by following methods: get
, post
, put
, patch
, delete
, options
, head
.
An Action can bind to one or more routes.
class Example < BaseAction
get "/books"
get "/books/:id"
post "/books"
put "/books/:id"
patch "/books/:id"
delete "/books/:id"
options "/books/:id"
head "/books/:id"
call do
render_plain "Hello World"
end
end
URL Params
URL Params can be declared in the route like /add/:apple_count/:banana_count
. And then you should use url
method to declare the type of URL params.
class Example < BaseAction
get "/add/:apple_count/:banana_count"
url NamedTuple(apple_count: Int32, banana_count: Int32)
call do
sum = params[:apple_count] + params[:banana_count]
render_plain sum.to_s
end
end
Custom HTTP method
If you need a route with custom HTTP method, such as LINK
, UNLINK
, FIND
or PURGE
, then you can use route
method to declare it.
class Example < BaseAction
route "LINK", "/books/:id"
call do
render_plain "Hello World"
end
end
Action
Runcobo use one action one file.
Before Filter
class BeforeExample < BaseAction
before required_login
def required_login
Runcobo::Log.info { "Required Login" }
end
get "/before_example"
call do
render_plain "Hello World!"
end
end
After Filter
class AfterExample < BaseAction
after log_params
def log_params
Runcobo::Log.info { "#{params}" }
end
get "/after_example"
call do
render_plain "Hello World!"
end
end
Skip Filter
class BaseAction
before required_login
def required_login
Runcobo::Log.info { "Required Login" }
end
end
class SkipExample < BaseAction
skip required_login
get "/skip_example"
call do
render_plain "Hello World!"
end
end
Params
Type-safe Params
Params in Runcobo are type-safe.
Three Steps To Use Params
- First, declare what params you expect and what type you expect by following methods:
url
,query
,form
andjson
. - Next, Runcobo parses request and wraps params to a local variable called
params
. - Third, use
params
variable to get or set request params.
Url Params
class UrlExample < BaseAction
get "/url_example/:a/:b"
url NamedTuple(a: Int32, b: Int32)
call do
sum = params[:a] + params[:b]
render_plain sum.to_s
end
end
Query Params
class QueryExample < BaseAction
get "/query_example"
query NamedTuple(a: Int32, b: Int32)
call do
sum = params[:a] + params[:b]
render_plain sum.to_s
end
end
Form Params
class FormExample < BaseAction
post "/form_example"
form NamedTuple(a: Int32, b: Int32)
call do
sum = params[:a] + params[:b]
render_plain sum.to_s
end
end
JSON Params
class JsonExample < BaseAction
post "/json_example"
json NamedTuple(a: Int32, b: Int32)
call do
sum = params[:a] + params[:b]
render_plain sum.to_s
end
end
Params Merge Order
You can declare various kinds of params in a action. If params are in same key, they will be merged in following order:
Query Params < Form Params < JSON Params < Url Params
Render
Render HTML
class WaterExample < BaseAction
get "/water_example"
call do
render_water "examples/index"
end
end
Render Plain
class PlainExample < BaseAction
get "/plain_example"
call do
render_plain "Hello World!"
end
end
Render Body
class BodyExample < BaseAction
get "/body_example"
call do
render_body "Hello World!"
end
end
Render JSON
class JbuilderExample < BaseAction
get "/jbuilder_example"
call do
render_jbuilder "examples/index"
end
end
View
Runcobo renders JSON by Jbuilder, renders HTML by Water. Jbuilder is a template engine designed for json using plain Crystal. Water is a template engine designed for html using plain Crystal.
Data transfer
All methods or variables defined in the action are available in the views. This is because the views are compiled in the same scope as the action.
Layout
You can override the default layout conventions in your actions by using the layout declaration. For example:
class BaseAction
layout "application"
#...
end
Partial
Runcobo renders partial view by build-in read_file
macro. There's no magic about partial view.
For example,
src/views/books/index.jbuilder
json.array! "books", books do |json, book|
{{ read_file("src/views/books/_base_book.jbuilder").id }}
end
src/views/books/_base_book.jbuilder
json.book_id book.id
json.author book.author
json.name book.name
json.published_at book.published_at
Render JSON
src/controllers/books/index.cr
class Books::Index < BaseAction
get "/books"
call do
books = Book.all
render_jbuilder "books/index"
end
end
src/views/books/index.jbuilder
json.array! "books", books do |json, book|
json.book_id book.id
json.author book.author
json.name book.name
json.published_at book.published_at
end
Then, output a JSON string.
{
"books": [{
"book_id": 1,
"author": "David",
"name": "Crystal Programming",
"published_at": "2020-08-08T20:00:00+00:00"
}]
}
Render Partial
src/views/books/index.jbuilder
json.array! "books", books do |json, book|
{{ read_file("src/views/books/_base_book.jbuilder").id }}
end
src/views/books/_base_book.jbuilder
json.book_id book.id
json.author book.author
json.name book.name
json.published_at book.published_at
Render HTML
src/controllers/books/index.cr
class Books::Index < BaseAction
get "/books"
call do
books = Book.all
render_water "books/index"
end
end
src/views/books/index.water
table %|class="table table-hover"| {
thead {
tr {
th "ID"
th "Author"
th "Name"
th "Published At"
}
}
tbody {
books.each do |book|
tr {
td book.id
td book.author
td book.name
td book.published_at
}
end
}
}
Then, output a HTML string.
<table class="table table-hover">
<thead>
<tr>
<th>ID</th>
<th>Author</th>
<th>Name</th>
<th>Published At</th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>David</td>
<td>Crystal Programming</td>
<td>2020-08-02 14:07:41 +08:00</td>
</tr>
</tbody>
</table>
Render Partial
src/views/books/index.jbuilder
{{ read_file("src/views/books/table.water").id }}
src/views/books/table.water
table %|class="table table-hover"| {
thead {
tr {
th "ID"
th "Author"
th "Name"
th "Published At"
}
}
tbody {
books.each do |book|
tr {
td book.id
td book.author
td book.name
td book.published_at
}
end
}
}
Model
You can see all ORMs from [awesome-crystal].(https://github.com/veelenga/awesome-crystal#ormodm-extensions)
Runcobo prefers to use jennifer.cr , you can check its docs and api.
jennifer.cr is an Active Record pattern implementation with flexible query chainable builder and migration system.
Contributing
- Fork it (https://github.com/runcobo/runcobo/fork)
- Create your feature branch (
git checkout -b my-new-feature
) - Write and execute specs (
crystal spec
) and formatting checks (crystal tool format
) - Commit your changes (
git commit -am 'Add some feature'
) - Push to the branch (
git push origin my-new-feature
) - Create a new Pull Request
Contributors
- Shootingfly - creator and maintainer