{"id":13396373,"url":"https://github.com/soveran/syro","last_synced_at":"2025-12-30T00:32:03.412Z","repository":{"id":30896845,"uuid":"34454606","full_name":"soveran/syro","owner":"soveran","description":"Simple router for web applications","archived":false,"fork":false,"pushed_at":"2021-08-02T07:05:55.000Z","size":75,"stargazers_count":138,"open_issues_count":1,"forks_count":15,"subscribers_count":12,"default_branch":"master","last_synced_at":"2024-12-18T06:23:22.584Z","etag":null,"topics":["lesscode","routing","ruby","tree"],"latest_commit_sha":null,"homepage":"http://soveran.github.io/syro/","language":"Ruby","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/soveran.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG","contributing":"CONTRIBUTING","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2015-04-23T12:21:49.000Z","updated_at":"2024-11-07T12:21:14.000Z","dependencies_parsed_at":"2022-09-07T15:01:32.212Z","dependency_job_id":null,"html_url":"https://github.com/soveran/syro","commit_stats":null,"previous_names":[],"tags_count":22,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/soveran%2Fsyro","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/soveran%2Fsyro/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/soveran%2Fsyro/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/soveran%2Fsyro/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/soveran","download_url":"https://codeload.github.com/soveran/syro/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":243499840,"owners_count":20300703,"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":["lesscode","routing","ruby","tree"],"created_at":"2024-07-30T18:00:48.299Z","updated_at":"2025-12-30T00:32:03.384Z","avatar_url":"https://github.com/soveran.png","language":"Ruby","readme":"Syro\n====\n\nSimple router for web applications.\n\nCommunity\n---------\n\nMeet us on IRC: [#syro](irc://chat.freenode.net/#syro) on\n[freenode.net](http://freenode.net/).\n\nDescription\n-----------\n\nSyro is a very simple router for web applications. It was created\nin the tradition of libraries like [Rum][rum] and [Cuba][cuba], but\nit promotes a less flexible usage pattern. The design is inspired\nby the way some Cuba applications are architected: modularity is\nencouraged and sub-applications can be dispatched without any\nsignificant performance overhead.\n\nCheck the [website][syro] for more information, and follow the\n[tutorial][tutorial] for a step by step introduction.\n\n[rum]: http://github.com/chneukirchen/rum\n[cuba]: http://cuba.is\n[syro]: http://soveran.github.io/syro/\n[tutorial]: http://files.soveran.com/syro/\n\nUsage\n-----\n\nAn example of a modular application would look like this:\n\n```ruby\nAdmin = Syro.new do\n  get do\n    res.write \"Hello from admin!\"\n  end\nend\n\nApp = Syro.new do\n  on \"admin\" do\n    run(Admin)\n  end\nend\n```\n\nThe block is evaluated in a sandbox where the following methods are\navailable: `env`, `req`, `res`, `path`, `inbox`, `call`, `run`,\n`halt`, `handle`, `finish!`, `consume`, `capture`, `root?` `match`,\n`default`, `on`, `root`,`get`, `put`, `head`, `post`, `patch`,\n`delete` and `options`. Three other methods are available for\ncustomizations: `default_headers`, `request_class` and `response_class`.\n\nAs a recommendation, user created variables should be instance\nvariables. That way they won't mix with the API methods defined in\nthe sandbox. All the internal instance variables defined by Syro\nare prefixed by `syro_`, like in `@syro_inbox`.\n\nAPI\n---\n\n`env`: Environment variables for the request.\n\n`req`: Helper object for accessing the request variables. It's an\ninstance of `Rack::Request`.\n\n`res`: Helper object for creating the response. It's an instance\nof `Syro::Response`.\n\n`path`: Helper object that tracks the previous and current path.\n\n`inbox`: Hash with captures and potentially other variables local\nto the request.\n\n`call`: Entry point for the application. It receives the environment\nand optionally an inbox.\n\n`run`: Runs a sub app, and accepts an inbox as an optional second\nargument.\n\n`halt`: Terminates the request. It receives an array with the\nresponse as per Rack's specification.\n\n`handle`: Installs a handler for a given status code. It receives\na status code and a block that will be executed from `finish!`.\n\n`finish!`: Terminates the request by executing any installed handlers\nand then halting with the current value of `res.finish`.\n\n`consume`: Match and consume a path segment.\n\n`capture`: Match and capture a path segment. The value is stored in\nthe inbox.\n\n`root?`: Returns true if the path yet to be consumed is empty.\n\n`match`: Receives a String, a Symbol or a boolean, and returns true\nif it matches the request.\n\n`default`: Receives a block that will be executed unconditionally.\n\n`on`: Receives a value to be matched, and a block that will be\nexecuted only if the request is matched.\n\n`root`: Receives a block and calls it only if `root?` is true.\n\n`get`: Receives a block and calls it only if `root?` and `req.get?` are\ntrue.\n\n`put`: Receives a block and calls it only if `root?` and `req.put?` are\ntrue.\n\n`head`: Receives a block and calls it only if `root?` and `req.head?`\nare true.\n\n`post`: Receives a block and calls it only if `root?` and `req.post?`\nare true.\n\n`patch`: Receives a block and calls it only if `root?` and `req.patch?`\nare true.\n\n`delete`: Receives a block and calls it only if `root?` and `req.delete?`\nare true.\n\n`options`: Receives a block and calls it only if `root?` and\n`req.options?` are true.\n\nDecks\n-----\n\nThe sandbox where the application is evaluated is an instance of\n`Syro::Deck`, and it provides the API described earlier. You can\ndefine your own `Deck` and pass it to the `Syro` constructor. All\nthe methods defined in there will be accessible from your routes.\nHere's an example:\n\n```ruby\nclass TextualDeck \u003c Syro::Deck\n  def text(str)\n    res[Rack::CONTENT_TYPE] = \"text/plain\"\n    res.write(str)\n  end\nend\n\nApp = Syro.new(TextualDeck) do\n  get do\n    text(\"hello world\")\n  end\nend\n```\n\nThe example is simple enough to showcase the concept, but maybe too\nsimple to be meaningful. The idea is that you can create your own\nspecialized decks and reuse them in different applications. You can\nalso define modules and later include them in your decks: for\nexample, you can write modules for rendering or serializing data,\nand then you can combine those modules in your custom decks.\n\nExamples\n--------\n\nIn the following examples, the response string represents\nthe request path that was sent.\n\n```ruby\nApp = Syro.new do\n  get do\n    res.write \"GET /\"\n  end\n\n  post do\n    res.write \"POST /\"\n  end\n\n  on \"users\" do\n    on :id do\n\n      # Captured values go to the inbox\n      @user = User[inbox[:id]]\n\n      get do\n        res.write \"GET /users/42\"\n      end\n\n      put do\n        res.write \"PUT /users/42\"\n      end\n\n      patch do\n        res.write \"PATCH /users/42\"\n      end\n\n      delete do\n        res.write \"DELETE /users/42\"\n      end\n    end\n\n    get do\n      res.write \"GET /users\"\n    end\n\n    post do\n      res.write \"POST /users\"\n    end\n  end\nend\n```\n\nMatches\n-------\n\nThe `on` method can receive a `String` to perform path matches; a\n`Symbol` to perform path captures; and a boolean to match any true\nvalues.\n\nEach time `on` matches or captures a segment of the PATH, that part\nof the path is consumed. The current and previous paths can be\nqueried by calling `prev` and `curr` on the `path` object: `path.prev`\nreturns the part of the path already consumed, and `path.curr`\nprovides the current version of the path.\n\nAny expression that evaluates to a boolean can also be used as a\nmatcher.  For example, a common pattern is to follow some route\nonly if a user is authenticated. That can be accomplished with\n`on(authenticated(User))`. That example assumes there's a method\ncalled `authenticated` that returns true or false depending on\nwhether or not an instance of `User` is authenticated. As a side\nnote, [Shield][shield] is a library that provides just that.\n\n[shield]: https://github.com/cyx/shield\n\nCaptures\n--------\n\nWhen a symbol is provided, `on` will try to consume a segment of\nthe path. A segment is defined as any sequence of characters after\na slash and until either another slash or the end of the string.\nThe captured value is stored in the `inbox` hash under the key that\nwas provided as the argument to `on`. For example, after a call to\n`on(:user_id)`, the value for the segment will be stored at\n`inbox[:user_id]`. When mounting an application called `Users` with\nthe command `run(Users)`, an inbox can be provided as the second\nargument: `run(Users, inbox)`. That allows apps to share previous\ncaptures.\n\nStatus code\n-----------\n\nBy default the status code is set to `404`. If both path and request\nmethod are matched, the status is automatically changed to `200`.\nYou can change the status code by assigning a number to `res.status`,\nfor example:\n\n```ruby\npost do\n  ...\n  res.status = 201\nend\n```\n\nHandlers\n--------\n\nStatus code handlers can be installed with the `handle` command,\nwhich receives a status code and a block to be executed just before\nfinishing the request.\n\nBy default, if there are no matches in a Syro application the\nresponse is a `404` with an empty body. If we decide to handle the\n`404` requests and return a string, we can do as follows:\n\n```ruby\nApp = Syro.new do\n  handle 404 do\n    res.text \"Not found!\"\n  end\n\n  get do\n    res.text \"Found!\"\n  end\nend\n```\n\nIn this example, a `GET` request to `\"/\"` will return a status `200`\nwith the body `\"Found!\"`. Any other request will return a `404`\nwith the body `\"Not found!\"`.\n\nIf a new handler is installed for the same status code, the previous\nhandler is overwritten. A handler is valid in the current scope and\nin all its nested branches. Blocks that end before the handler is\ninstalled are not affected.\n\nThis is a contrived example that shows some edge cases when using handlers:\n\n```ruby\nApp = Syro.new do\n  on \"foo\" do\n    # 404, empty body\n  end\n\n  handle 404 do\n    res.text \"Not found!\"\n  end\n\n  on \"bar\" do\n    # 404, body is \"Not found!\"\n  end\n\n  on \"baz\" do\n    # 404, body is \"Couldn't find baz\"\n\n    handle 404 do\n      res.text \"Couldn't find baz\"\n    end\n  end\nend\n```\n\nA request to `\"/foo\"` will return a `404`, because the request\nmethod was not matched. But as the `on \"foo\"` block ends before the\nhandler is installed, the result will be a blank screen. On the\nother hand, a request to `\"/bar\"` will return a `404` with the plain\ntext `\"Not found!\"`.\n\nFinally, a request to `\"/baz\"` will return a `404` with the plain text\n`\"Couldn't find baz\"`, because by the time the `on \"baz\"` block ends\na new handler is installed, and thus the previous one is overwritten.\n\nAny status code can be handled this way, even status `200`. In that\ncase the handler will behave as a filter to be run after each\nsuccessful request.\n\nContent type\n------------\n\nThere's no default value for the content type header, but there's\na handy way of setting the desired value.\n\nIn order to write the body of the response, the `res.write` method\nis used:\n\n```ruby\nres.write \"hello world\"\n```\n\nIt has the drawback of leaving the `Content-Type` header empty.\nThree alternative methods are provided, and more can be added by\nusing custom Decks.\n\nSetting the Content-Type as `\"text/plain\"`:\n\n```ruby\nres.text \"hello world\"\n```\n\nSetting the Content-Type as `\"text/html\"`:\n\n```ruby\nres.html \"hello world\"\n```\n\nSetting the Content-Type as `\"application/json\"`:\n\n```ruby\nres.json \"hello world\"\n```\n\nNote that aside from writing the response body and setting the value\nfor the Content-Type header, no encoding or serialization takes\nplace. If you want to return a JSON encoded response, make sure to\nencode the objects yourself (i.e., `res.json JSON.dump(...)`).\n\nSecurity\n--------\n\nThere are no security features built into this routing library. A\nframework using this library should implement the security layer.\n\nRendering\n---------\n\nThere are no rendering features built into this routing library. A\nframework that uses this routing library can easily implement helpers\nfor rendering.\n\nMiddleware\n----------\n\nSyro doesn't support Rack middleware out of the box. If you need them,\njust use `Rack::Builder`:\n\n```ruby\nApp = Rack::Builder.new do\n  use Rack::Session::Cookie, secret: \"...\"\n\n  run Syro.new {\n    get do\n      res.write(\"Hello, world\")\n    end\n  }\nend\n```\n\nTrivia\n------\n\nAn initial idea was to release a new version of [Cuba](http://cuba.is)\nthat broke backward compatibility, but in the end my friends suggested\nto release this as a separate library. In the future, some ideas\nof this library could be included in Cuba as well.\n\nInstallation\n------------\n\n```\n$ gem install syro\n```\n","funding_links":[],"categories":["Cuba Components \u0026 Related Gems","Ruby","Middlewares"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsoveran%2Fsyro","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsoveran%2Fsyro","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsoveran%2Fsyro/lists"}