{"id":13808041,"url":"https://github.com/runcobo/runcobo","last_synced_at":"2025-05-07T08:26:31.512Z","repository":{"id":49356112,"uuid":"257044206","full_name":"runcobo/runcobo","owner":"runcobo","description":"An api framework with type-safe params, elegant json serializer. Thanks for enjoying it! 👻👻 https://runcobo.github.io/docs/","archived":false,"fork":false,"pushed_at":"2025-01-14T06:39:03.000Z","size":109,"stargazers_count":50,"open_issues_count":0,"forks_count":1,"subscribers_count":5,"default_branch":"main","last_synced_at":"2025-03-31T08:07:30.523Z","etag":null,"topics":["api","crystal","microservice","webframework"],"latest_commit_sha":null,"homepage":"https://runcobo.github.io/docs/","language":"Crystal","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/runcobo.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2020-04-19T16:12:27.000Z","updated_at":"2024-09-25T02:36:18.000Z","dependencies_parsed_at":"2022-09-24T01:24:05.850Z","dependency_job_id":null,"html_url":"https://github.com/runcobo/runcobo","commit_stats":null,"previous_names":[],"tags_count":5,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/runcobo%2Fruncobo","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/runcobo%2Fruncobo/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/runcobo%2Fruncobo/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/runcobo%2Fruncobo/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/runcobo","download_url":"https://codeload.github.com/runcobo/runcobo/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":252841288,"owners_count":21812444,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["api","crystal","microservice","webframework"],"created_at":"2024-08-04T01:01:34.106Z","updated_at":"2025-05-07T08:26:31.463Z","avatar_url":"https://github.com/runcobo.png","language":"Crystal","readme":"# Runcobo\n\n[![Crystal CI](https://github.com/runcobo/runcobo/actions/workflows/crystal.yml/badge.svg)](https://github.com/runcobo/runcobo/actions/workflows/crystal.yml)\n[![Built with Crystal](https://img.shields.io/badge/built%20with-crystal-000000.svg?style=flat-square)](https://crystal-lang.org/)\n[![Latest release](https://img.shields.io/github/release/runcobo/runcobo.svg?style=flat-square)](https://github.com/runcobo/runcobo/releases)\n[![API docs](https://img.shields.io/badge/api_docs-online-brightgreen.svg?style=flat-square)](https://runcobo.github.io/runcobo/)\n\n# Introduction\n\nRuncobo is a general purpose framework built on Crystal.  \n\n### Commands\n\n```shell\nruncobo [\u003ccommands\u003e...] [\u003carguments\u003e...]\n\n  Commands:\n    routes                      - Print all routes of the app.\n    version                     - Print the current version of the runcobo.\n    help                        - Print usage synopsis.\n\n  Options:\n    -h, --help                       Print usage synopsis.\n    -v, --version                    Print the current version of the runcobo.\n```\n\n### Project Architecture\n\n    lib/                # Library\n    src/\n        main.cr         # Entry file\n        actions/        # Actions Directory\n            ...\n        assets/         # Assets Directory\n        views/          # Views Directory\n            layouts/    # Layouts directory\n            ...\n        models/         # Models Directory\n            ...\n    shards.yml          # The packages congfiuration file\n    shards.lock         # The lock file for packages congfiuration file\n\nKeep in this architecture to use `layouts` macro, `render` macro.\n\n### Design Architecture\nMVC (Model-View-Controller)\n\n### Design Principles\n\n+ Simple Design must be simple, both in implementation and interface.\n+ Intuitive Design must be intuitive.\n+ Consistent Design must be consistent.\n\n# Installation\n\n### Install Crystal\nCrystal is a language for humans and computers. \n\nCrystal 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.\n\nSee [https://crystal-lang.org/install/](https://crystal-lang.org/install) to install Crystal.\n\n### Install Runcobo\nYou can install Runcobo from sources. Other installations are working on.\n\n```shell\ncurl -L https://github.com/runcobo/runcobo/archive/stable.zip --output runcobo.zip\nunzip runcobo.zip\ncd runcobo-stable/\nsudo make install\nruncobo -v\n```\n\n# Getting Started\n\n1.Init a Crystal project.\n```shell\ncrystal init app demo \u0026\u0026 cd demo\n```\n\n2.Add the dependency to your shard.yml and run `shards install`.\n```yaml\ndependencies:\n  runcobo:\n    github: runcobo/runcobo\n```\n\n3.Write down the following code in `src/demo.cr`.\n```crystal\nrequire \"runcobo\"\n\nclass Api::V1::Add \u003c BaseAction\n  get \"/api/v1/add\"\n  query NamedTuple(a: Int32, b: Int32)\n\n  call do\n    sum = params[:a] + params[:b]\n    render_plain sum.to_s\n  end\nend\n\nRuncobo.start\n```\n\n4.Run server.\n```shell\ncrystal src/demo.cr\n```\n\n5.Send request.\n```shell\ncurl \"http://0.0.0.0:3000/api/v1/add?a=1\u0026b=2\"\n```\n\n6.Auto restart server.\n```shell\n# Use nodemon to watch file changed and auto restart server.\nsudo npm install -g nodemon\nnodemon -e \"cr,water,jbuilder,yml\" --exec \"crystal run\" src/demo.cr\n```\n\n# Route\nRuncobo declares routes in every Actions. If you access the route, it will run into related Action.\n\nIn this way, you can know the routes of current Action quickly.\n\nYou can declare RESTful routes as you want or not.\n\n\u003e In my personal view, RESTful is too abstact when you were far way from a CRUD system like Backend Management System.\n\n\u003e When writing API, I believe urls should be named like functions, not objects or resources.\n\n\u003e When writing a Backend Management System, enjoy RESTful.\n\n### Routes Declaration\nRuncobo declares routes by following methods: `get`, `post`, `put`, `patch`, `delete`, `options`, `head`.\nAn Action can bind to one or more routes.\n\n```crystal\nclass Example \u003c BaseAction\n  get \"/books\"\n  get \"/books/:id\"\n  post \"/books\"\n  put \"/books/:id\"\n  patch \"/books/:id\"\n  delete \"/books/:id\"\n  options \"/books/:id\"\n  head \"/books/:id\"\n\n  call do\n    render_plain \"Hello World\"\n  end\nend\n```\n\n### URL Params\nURL 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.\n\n```crystal\nclass Example \u003c BaseAction\n  get \"/add/:apple_count/:banana_count\"\n  url NamedTuple(apple_count: Int32, banana_count: Int32)\n\n  call do\n    sum = params[:apple_count] + params[:banana_count]\n    render_plain sum.to_s\n  end\nend\n```\n\n### Custom HTTP method\nIf you need a route with custom HTTP method, such as `LINK`, `UNLINK`, `FIND` or `PURGE`, then you can use `route` method to declare it.\n\n```crystal\nclass Example \u003c BaseAction\n  route \"LINK\", \"/books/:id\"\n\n  call do\n    render_plain \"Hello World\"\n  end\nend\n```\n\n# Action\n\nRuncobo use one action one file. \n\n### Before Filter\n```crystal\nclass BeforeExample \u003c BaseAction\n  before required_login\n  def required_login\n    Runcobo::Log.info { \"Required Login\" }\n  end\n\n  get \"/before_example\"\n  call do\n    render_plain \"Hello World!\"\n  end\nend\n```\n\n### After Filter\n```crystal\nclass AfterExample \u003c BaseAction\n  after log_params\n  def log_params\n    Runcobo::Log.info { \"#{params}\" }\n  end\n\n  get \"/after_example\"\n  call do\n    render_plain \"Hello World!\"\n  end\nend\n```\n\n### Skip Filter\n```crystal\nclass BaseAction\n  before required_login\n  def required_login\n    Runcobo::Log.info { \"Required Login\" }\n  end\nend\n\nclass SkipExample \u003c BaseAction\n  skip required_login\n\n  get \"/skip_example\"\n  call do\n    render_plain \"Hello World!\"\n  end\nend\n```\n\n# Params\n\n### Type-safe Params\n\n**Params in Runcobo are type-safe.**\n\n### Three Steps To Use Params\n\n+ First, declare what params you expect and what type you expect by following methods: `url`, `query`, `form` and `json`.\n+ Next, Runcobo parses request and wraps params to a local variable called `params`.\n+ Third, use `params` variable to get or set request params.\n\n### Url Params\n```crystal\nclass UrlExample \u003c BaseAction\n  get \"/url_example/:a/:b\"\n  url NamedTuple(a: Int32, b: Int32)\n\n  call do\n    sum = params[:a] + params[:b]\n    render_plain sum.to_s\n  end\nend\n```\n\n### Query Params\n```crystal\nclass QueryExample \u003c BaseAction\n  get \"/query_example\"\n  query NamedTuple(a: Int32, b: Int32)\n\n  call do\n    sum = params[:a] + params[:b]\n    render_plain sum.to_s\n  end\nend\n```\n\n### Form Params\n```crystal\nclass FormExample \u003c BaseAction\n  post \"/form_example\"\n  form NamedTuple(a: Int32, b: Int32)\n\n  call do\n    sum = params[:a] + params[:b]\n    render_plain sum.to_s\n  end\nend\n```\n\n### JSON Params\n```crystal\nclass JsonExample \u003c BaseAction\n  post \"/json_example\"\n  json NamedTuple(a: Int32, b: Int32)\n\n  call do\n    sum = params[:a] + params[:b]\n    render_plain sum.to_s\n  end\nend\n```\n\n### Params Merge Order\nYou can declare various kinds of params in a action. If params are in same key, they will be merged in following order:\n\n`Query Params \u003c Form Params \u003c JSON Params \u003c Url Params`\n\n# Render\n\n### Render HTML\n```crystal\nclass WaterExample \u003c BaseAction\n  get \"/water_example\"\n  call do\n    render_water \"examples/index\"\n  end\nend\n```\n\n### Render Plain\n```crystal\nclass PlainExample \u003c BaseAction\n  get \"/plain_example\"\n  call do\n    render_plain \"Hello World!\"\n  end\nend\n```\n\n### Render Body\n```crystal\nclass BodyExample \u003c BaseAction\n  get \"/body_example\"\n  call do\n    render_body \"Hello World!\"\n  end\nend\n```\n\n### Render JSON\n```crystal\nclass JbuilderExample \u003c BaseAction\n  get \"/jbuilder_example\"\n  call do\n    render_jbuilder \"examples/index\"\n  end\nend\n```\n\n# View\n\nRuncobo renders JSON by **Jbuilder**, renders HTML by **Water**.\n**Jbuilder** is a template engine designed for json using plain Crystal.\n**Water** is a template engine designed for html using plain Crystal.\n\n### Data transfer\nAll methods or variables defined in the action are available in the views.\nThis is because the views are compiled in the same scope as the action.\n\n### Layout\n\nYou can override the default layout conventions in your actions by using the layout declaration. For example:\n```crystal\nclass BaseAction\n  layout \"application\"\n  #...\nend\n```\n\n### Partial\n\nRuncobo renders partial view by build-in `read_file` macro. There's no magic about partial view. \nFor example,\n\n*src/views/books/index.jbuilder*\n```crystal\njson.array! \"books\", books do |json, book|\n  {{ read_file(\"src/views/books/_base_book.jbuilder\").id }}\nend\n```\n\n*src/views/books/_base_book.jbuilder*\n```crystal\njson.book_id      book.id\njson.author       book.author\njson.name         book.name\njson.published_at book.published_at\n```\n\n### Render JSON\n\n*src/controllers/books/index.cr*\n```crystal\nclass Books::Index \u003c BaseAction\n  get \"/books\"\n  call do\n    books = Book.all\n    render_jbuilder \"books/index\"\n  end\nend\n```\n\n*src/views/books/index.jbuilder*\n```crystal\njson.array! \"books\", books do |json, book|\n  json.book_id      book.id\n  json.author       book.author\n  json.name         book.name\n  json.published_at book.published_at\nend\n```\n\nThen, output a JSON string.\n```json\n{\n  \"books\": [{\n    \"book_id\": 1,\n    \"author\": \"David\",\n    \"name\": \"Crystal Programming\",\n    \"published_at\": \"2020-08-08T20:00:00+00:00\"\n  }]\n}\n```\n\n### Render Partial\n\n*src/views/books/index.jbuilder*\n```crystal\njson.array! \"books\", books do |json, book|\n  {{ read_file(\"src/views/books/_base_book.jbuilder\").id }}\nend\n```\n\n*src/views/books/_base_book.jbuilder*\n```crystal\njson.book_id      book.id\njson.author       book.author\njson.name         book.name\njson.published_at book.published_at\n```\n\n### Render HTML\n\n*src/controllers/books/index.cr*\n```crystal\nclass Books::Index \u003c BaseAction\n  get \"/books\"\n  call do\n    books = Book.all\n    render_water \"books/index\"\n  end\nend\n```\n\n*src/views/books/index.water*\n```crystal\ntable %|class=\"table table-hover\"| {\n  thead {\n    tr {\n      th \"ID\"\n      th \"Author\"\n      th \"Name\"\n      th \"Published At\"\n    } \n  }\n  tbody {\n    books.each do |book|\n      tr {\n        td book.id\n        td book.author\n        td book.name\n        td book.published_at\n      }\n    end\n  }\n}\n```\n\nThen, output a HTML string.\n```html\n\u003ctable class=\"table table-hover\"\u003e\n  \u003cthead\u003e\n    \u003ctr\u003e\n      \u003cth\u003eID\u003c/th\u003e\n      \u003cth\u003eAuthor\u003c/th\u003e\n      \u003cth\u003eName\u003c/th\u003e\n      \u003cth\u003ePublished At\u003c/th\u003e\n    \u003c/tr\u003e\n  \u003c/thead\u003e\n  \u003ctbody\u003e\n    \u003ctr\u003e\n      \u003ctd\u003e1\u003c/td\u003e\n      \u003ctd\u003eDavid\u003c/td\u003e\n      \u003ctd\u003eCrystal Programming\u003c/td\u003e\n      \u003ctd\u003e2020-08-02 14:07:41 +08:00\u003c/td\u003e\n    \u003c/tr\u003e\n  \u003c/tbody\u003e\n\u003c/table\u003e\n```\n\n### Render Partial\n\n*src/views/books/index.jbuilder*\n```crystal\n{{ read_file(\"src/views/books/table.water\").id }}\n```\n\n*src/views/books/table.water*\n```crystal\ntable %|class=\"table table-hover\"| {\n  thead {\n    tr {\n      th \"ID\"\n      th \"Author\"\n      th \"Name\"\n      th \"Published At\"\n    } \n  }\n  tbody {\n    books.each do |book|\n      tr {\n        td book.id\n        td book.author\n        td book.name\n        td book.published_at\n      }\n    end\n  }\n}\n``` \n\n# Model\n\nYou can see all ORMs from [awesome-crystal].(https://github.com/veelenga/awesome-crystal#ormodm-extensions)\n\nRuncobo prefers to use [jennifer.cr](https://github.com/imdrasil/jennifer.cr) , you can check its [docs](https://imdrasil.github.io/jennifer.cr/docs/) and [api](https://imdrasil.github.io/jennifer.cr/latest/).\n\n[jennifer.cr](https://github.com/imdrasil/jennifer.cr) is an Active Record pattern implementation with flexible query chainable builder and migration system.\n\n## Contributing\n\n1. Fork it (\u003chttps://github.com/runcobo/runcobo/fork\u003e)\n2. Create your feature branch (`git checkout -b my-new-feature`)\n3. Write and execute specs (`crystal spec`) and formatting checks (`crystal tool format`)\n4. Commit your changes (`git commit -am 'Add some feature'`)\n5. Push to the branch (`git push origin my-new-feature`)\n6. Create a new Pull Request\n\n## Contributors\n\n- [Shootingfly](https://github.com/shootingfly) - creator and maintainer\n","funding_links":[],"categories":["Web Frameworks"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fruncobo%2Fruncobo","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fruncobo%2Fruncobo","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fruncobo%2Fruncobo/lists"}