{"id":13878986,"url":"https://github.com/alxx/surikat","last_synced_at":"2025-08-09T02:13:38.715Z","repository":{"id":59156904,"uuid":"135469354","full_name":"alxx/surikat","owner":"alxx","description":"A backend web framework centred around GraphQL.","archived":false,"fork":false,"pushed_at":"2018-11-05T17:22:57.000Z","size":3917,"stargazers_count":16,"open_issues_count":0,"forks_count":1,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-03-24T12:21:28.323Z","etag":null,"topics":["graphql","graphql-server","graphql-server-framework","ruby","web-framework"],"latest_commit_sha":null,"homepage":null,"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/alxx.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2018-05-30T16:23:22.000Z","updated_at":"2024-10-13T22:25:24.000Z","dependencies_parsed_at":"2022-09-13T17:50:55.022Z","dependency_job_id":null,"html_url":"https://github.com/alxx/surikat","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alxx%2Fsurikat","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alxx%2Fsurikat/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alxx%2Fsurikat/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alxx%2Fsurikat/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/alxx","download_url":"https://codeload.github.com/alxx/surikat/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248225878,"owners_count":21068079,"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":["graphql","graphql-server","graphql-server-framework","ruby","web-framework"],"created_at":"2024-08-06T08:02:06.130Z","updated_at":"2025-04-10T13:35:07.824Z","avatar_url":"https://github.com/alxx.png","language":"Ruby","funding_links":[],"categories":["Ruby"],"sub_categories":[],"readme":"# Surikat\n\n![Surikat](https://i.imgur.com/OlCUw38.png)\n\n## A backend web framework centred on GraphQL.\n\nMany frontend apps require little more than a simple backend that does a few\nCRUD operations and handles user authentication, authorisation and access\n(AAA).\n\nFor even the simplest backends, frontend developers must invest time and\nknowledge into some unfamiliar framework, and then code the app -- often in\na language that they're not very comfortable in.\n\nSurikat solves much of that.\n\nWith Surikat, you can have a backend app up and running in under a minute, that\ndoes CRUD and AAA, with no code at all.\n\nSure, Rails has scaffolding -- but not for AAA, and not for GraphQL. Sure,\nthere are gems for that -- but they have significant learning curves.\n\nSure, Rails can make API-only apps -- but only REST API apps.\n[GraphQL](http://graphql.org), a standard created by Facebook and made\nopen-source since then, is far more efficient than REST. There's only one\nendpoint, and you get exactly the data that you ask for -- nothing else.\n\nSure, Rails can also be taught GraphQL. But that's an add-on to everything else\nthat Rails does; by contrast, Surikat was built, from the ground up, around\nGraphQL.\n\nWriting the backend for GraphQL queries, in most existing frameworks, can be\ntedious and complicated. Surikat organises, simplifies and exemplifies all\nqueries, and it even helps a lot with testing them. You always know how to call\na query, even without introspection; Surikat is intuitive, and will always have\nan example handy.\n\n### Quick Start\n\n```bash\n$ gem install surikat\n$ surikat new library\n```\n\nOnce the Surikat app is created, follow the instructions; `cd` into the app\ndirectory, run `rspec` for tests, `passenger start` to start a web server,\n`bin/console` to try stuff out, etc.\n\nJust type `surikat` to see what the command line tool can do.\n\n### Slow Start\n\nSurikat operates with four concepts: *Routes*, *Types*, *Queries* and *Models*.\n\n#### Models \n\nSurikat is not an MVC framework; it lacks the V and the C. But it does use\nmodels, and in particular, the ActiveRecord library that Ruby on Rails was\ninitially based on. If you're familiar with modern MVC frameworks, then you'll\nfeel right at home with Surikat models.\n\n#### Queries\n\nWith Surikat, Queries are simple Ruby code; you don't have to learn any\ncomplicated DSL or try to adapt to someone else's idea of what a GraphQL query\ndefinition should look like.\n\nEach model file has a companion queries file, but you can also write your own\nqueries. By using some simple conventions, and routes (see below), queries can\neasily be represented as simple methods:\n\n```ruby\nclass AuthorQueries \u003c Surikat::BaseQueries\n  def get\n    Author.where(id: arguments['id'])\n  end\nend\n```\n\n#### Routes\n\nModels and Queries are the only components of Surikat which require\na programming language (Ruby). The other half are simple YAML files, which can\nbe edited manually or programmatically. Routes describe the links between\nGraphQL queries (or mutations), and the queries method.\n\nFor example, the query method above might be routed thus:\n\n```yaml\nAuthor:\n    class: AuthorQueries\n    method: get\n    output_type: Author\n    arguments:\n      id: ID\n```\n\n#### Types\n\nYou'll notice in the route above that it mentions an output_type named\n`Author`. Just like routes, types live also in YAML files, and they are used to\ndescribe the data that goes in the app (input types), and the data that comes\nout (output types).\n\nIn the example above, the `Author` route calls the `get` method of the\n`AuthorQueries` class, and it formats its return (an `Author` database record)\nto match a given type. Case in point:\n\n```yaml\nAuthor:\n  type: Output\n  fields:\n    name: String\n    created_at: String\n    updated_at: String\n    id: ID\n    books: \"[Book]\"\n```\n  \nThese are all the fields that the frontend would have access to; a `name` of\nthe type `String`, two timestamps which are also automatically cast as\n`String`, the record database id, and an array of books (which are, in turn,\nrendered in accordance to their own `Book` output type).\n\n### Examplifying Queries\n\nWhenever you have a query, Surikat will tell you how it works, and it will even\ngive you a `curl` command line to test it with:\n\n```bash\n$ surikat exemplify AuthorQueries get\n\nQuery:\n{\n  Author(id: 1) {\n    name\n    created_at\n    updated_at\n    id\n    books {\n      title\n      created_at\n    }\n  }\n}\n\n\ncurl command:\ncurl 0:3000 -X POST -d 'query=%7B%0A++Author%28id%3A+1%29+%7B%0A++++name%0A++++created_at%0A++++updated_at%0A++++id%0A++++books+%7B%0A++++++title%0A++++++created_at%0A++++%7D%0A++%7D%0A%7D'\n```\n\n### Scaffolding\n\nSurikat comes with a convenient scaffolding tool, which creates a model (with\na database migration), a set of queries for it (to Create, Retrieve, Update and\nDelete), as well as the necessary types, routes and tests.\n\nExample:\n\n```bash\nsurikat generate model Book title:string\n```\n\n#### A Note About Ransack\n\nSurikat comes with Ransack, so that when you retrieve a collection of\nActiveRecord objects, you can already filter and sort them using\n[Ransack search matchers](https://github.com/activerecord-hackery/ransack#search-matchers). \n\nExample query:\n\n```graphql\n {\n  Authors(q: \"is_any_good_eq=false\u0026id_lt=20 \") {\n    id\n    name\n    created_at\n    is_any_good\n    year_of_birth\n  }\n}\n```\n\n### Custom Data\n\nSometimes you need to supply things to the frontend that don't come directly\nfrom the database. In fact, you can send anything you want; here are a few\nsimple recipes:\n\n 1. To add an additional field to the ones already provided by the database,\n the easiest way is to define a method in the model.\n\n    ```ruby\n    class Person \u003c Surikat::BaseModel\n      def favourite_number\n        rand(10)\n      end\n    end\n    ```\n\n    Then, you can add `favourite_number` into the `Author` output type, and you're set.\n\n 2. If you need this field to have arguments:\n\n    ```ruby\n    class Person \u003c Surikat::BaseModel\n      def square(num)\n        num * num\n      end\n    end\n    ```\n    \n    And in the query:\n    \n    ```graphql\n    {\n      Person(id: 1) {\n        square(num: 5)\n      }\n    }\n    ```\n\n 3. Returning custom types is also easy. If you have an output type that\n defines the fields `favourite_food` and `favourite_drink`, all your query\n needs to do is to return a Ruby `Hash` that has those two keys.\n\n    ```ruby\n    class MyQueries \u003c Surikat::BaseQueries\n      def favourite_stuff\n        {\n          favourite_food: 'air',\n          favourite_drink: 'water'\n        }\n      end\n    end\n    ```\n    \n    This works for arrays, too. You can return an array of such objects, and use them \n    in your output types using the brackets notation, for example `[FavouriteStuffType]`.\n\n#### Errors\n\nAs per [GraphQL specs](http://facebook.github.io/graphql/June2018/#sec-Errors),\napplication errors, type errors or model validation errors are returned inside\na field named `errors` which is an array.\n\n#### Arguments\n\nIn the queries, you always have access to the query arguments via the\n`arguments` helper:\n\n```ruby\nclass AuthorQueries \u003c Surikat::BaseQueries\n  def get\n    Author.where(id: arguments['id'])\n  end\nend\n```\n\n### Session\n\nSession management is easy with Surikat. Simply carry around an HTTP header\nnamed 'Surikat' with a value that's as randomly unique as possible. You\nprobably want to generate this value when your frontend app loads, then use it\nfor all Surikat queries. As long as you send the same Surikat header, you'll\nmaintain a session.\n\nWith curl:\n\n```bash\ncurl 0:3000 -X POST -d 'query=%7B%0AHello%0A%7D' -H 'Surikat: 1234'\n```\n \nIn the queries, you always have access to the session object via the `session`\nhelper:\n\n```ruby\nclass AuthorQueries \u003c Surikat::BaseQueries\n  def play_with_session\n    # store something in the session object\n    session[:something] = 'Something'\n    \n    # retrieve something from the session object\n    {\n      name: session[:name]\n    }\n  end\nend\n```\n\n#### Session Stores\n\nThe session store is configured in `config/application.yml` and it can either\nbe a file, or Redis.\n\nThe file method is slower, and it gets slower as the file (which lives in\n`tmp/`) gets bigger. Needless to say, it also doesn't work to scale up the app\nacross several machines.\n\nRedis is much preferred especially in production; remember to add the `redis`\ngem to Gemfile. To configure it, use the `url` field in the same configuration\nfile; that will be passed to the Redis initialisation method.\n\n### Authentication, Authorisation and Access\n\nSurikat comes with triple-A, but it's not enabled by default. Rather, the files\nmust be generated:\n\n```bash\nsurikat generate aaa\n```\n\nThis will create a `User` model (plus migration), a class called `AAAQueries`\nand a suite of tests.\n\nThe model will, by default, have three columns: `email`, `hashed_password` and\n`roleids`.\n\nTo create a user, use the `password` accessor.\n\n```ruby\n$ bin/console\n\nUser.create email:'a@b.com', password:'abc'\n```\n\nSurikat will save a SHA256 digest for that password in the database.\n\nTo restrict a query to logged in users, add `permitted_roles: any` to its\nroute.\n\nTo restrict a query to particular user roles (more about roles below), add for\nexample `permitted_roles: admin,superadmin` to its route.\n\nThe AAA queries available to you are described in `app/queries/aaa_queries.rb`,\nincluding even query examples. In short, they are:\n\n* `Authenticate` - you pass the email and password, and you get a boolean value;\nif the authentication succeeds, then a `user_id` will be stored in the session\nobject, giving you access to the current user.\n\n* `Logout` - self-explanatory.\n\n* `CurrentUser` - returns the current user based on what's in `session[:user_id]`.\n\n* `LoginAs` - allows a superadmin to login as another user (more about superadmins\nin the Roles section below). During this time, the session will also contain\n`:superadmin_id`.\n\n* `BackFrom LoginAs` - having logged in as someone else, return as the initial\nsuperadmin.\n\n* `DemoOne`, `DemoTwo` and `DemoThree` - used by the rspec tests. If you delete\nthem, please also delete the corresponding tests in `spec/aaa_spec.rb`.\n\n#### Roles\n\nRoles are simply identifiers stored, for a user, inside the `roleids`\nattribute, and comma-separated.\n\nBefore a query is executed, the content of its `permitted_roles` field (from\nits route) is evaluated. If it's `any` then a user of any role is allowed\naccess. If it's a comma separated array of role identifiers, then access will\nonly be granted if there's an intersection between those roles and the current\nuser's.\n\n### Application Structure\n\nA Surikat app has the following directory structure:\n\n```bash\n├── Gemfile\n├── Rakefile\n├── app\n│   ├── models\n│   └── queries\n├── bin\n│   └── console\n├── config\n│   ├── application.yml\n│   ├── database.yml\n│   ├── initializers\n│   ├── routes.yml\n│   └── types.yml\n├── config.ru\n├── db\n│   ├── migrate\n├── log\n├── spec\n└── tmp\n```\n\n* app - models and queries. That's where all the code you need to write will be. (Except for tests.)\n* bin - just the console binary. Nothing to touch here.\n* config - contains the database configuration, application configuration, and any initializers.\n* db - migration files, database stuff.\n* log - passenger logs\n* spec - tests\n* tmp - pid files, temporary stuff.\n\n### Testing\n\nAll the scaffolds come with running tests; just run `rspec` or, if you'd rather\nsee some details, `rspec -f d`. \n\nIf you change the scaffolding, you need to change the tests, too.\n\n*Note:* The intention was (and still is) to make autotests fully independent,\nso that they still test the scaffolded code even after you change it. However,\nbecause of field arguments, that's not exactly trivial. Hopefully a later\nrelease will come with a solution to this issue. Until then, you have to adapt\nthe tests to your code changes \"by hand\".\n\n### Web Server\n\nSurikat uses [Phusion Passenger](https://www.phusionpassenger.com/) as a web\nserver. Simply type\n\n```bash\npassenger start\n```\n\nto start a server on port 3000. Then you can use GraphiQL, curl or your actual\nfrontend app to start querying the backend.\n\nTo start in production mode:\n\n```bash\npassenger start -e production\n```\n\n## Demo\n\nThere's a small front-end \"app\" which may be used as a demo, in the\n`frontend-demo` directory, and it has its own README.\n\n## Benchmarking\n\nTo benchmark a Surikat app, you can use Apache Benchmark. For that, first save \na query inside a file, for example:\n\n ```bash\n $ cat surikat.query                                                                                                          ‹ruby-2.4.1›\n query={Authors(q: \"id_lt=10\") {first_name}}\n ```\n \n Then, start Passenger (see above) and invoke Apache Benchmark for example like this:\n \n ```bash\n ab -k -c 10 -n 100 -T 'application/x-www-form-urlencoded' -p surikat.query  http://0:3000/\n ```\n\nThis runs 10 concurrent requests, to a maximum of 100 requests.\n\n## Benchmarking Results\n\nBenchmarking tests were performed in the following conditions:\n\nMachine: iMac 3.1 GHz Intel Core i7, 16 GB RAM (running macOS Mojave, 10.14)\nDatabase Server: MySQL 5.7.10\nDatabase: 20,000 authors who together have 141,224 books\nRuby: 2.4.1-p111\n\nSurikat 0.3.1 versus Rails 5.2.1 with graphql 1.8.11 (latest at the time)\n\nSurikat app: two auto-generated scaffolds (one for authors, one for books).\nRails app: two models (`Author` and `Book` and minimal queries.)\n\nBoth apps use Ransack 2.1.0 in exactly the same way.\n\nBoth apps connected to the same database.\n\nOnly default values used everywhere else (no optimisations etc.)\n\n### Test 1.\nQuery:\n```graphql\n{Authors(q: \"id_lt=10\") {first_name, books{title}}}\n```\n\n* Rails: 6.67 requests per second.\n* __Surikat: 9.70 requests per second.__\n\n### Test 2.\nQuery:\n```graphql\n{Authors(q: \"id_lt=10\") {first_name}}\n```\n\n* Rails: 164.86 requests per second.\n* __Surikat: 1,332.52 requests per second.__\n\n\n## System Dependencies\n\nFor improved performance, Surikat uses a C++ library to parse GraplQL queries,\n[libgraphqlparser](https://github.com/graphql/libgraphqlparser).\n\nOn Mac OS X, install with Homebrew:\n\n```bash\nbrew install libgraphqlparser\n```\n\n## Development\n\nAfter checking out the repo, run `bin/setup` to install dependencies. Then, run\n`rake spec` to run the tests. You can also run `bin/console` for an interactive\nprompt that will allow you to experiment.\n\nTo install this gem onto your local machine, run `bundle exec rake install`. To\nrelease a new version, update the version number in `version.rb`, and then run\n`bundle exec rake release`, which will create a git tag for the version, push\ngit commits and tags, and push the `.gem` file to\n[rubygems.org](https://rubygems.org).\n\nTo install the gem locally:\n`gem uninstall -x surikat \u0026\u0026 gem build surikat.gemspec \u0026\u0026 gem install surikat --no-ri --no-rdoc \u0026\u0026 ruby bin/postinstall`\n\n## Contributing\n\nBug reports and pull requests are welcome on GitHub at\nhttps://github.com/alxx/surikat.\n\n## Version\n\nThis code reflects version 0.3.2.\n\n## License\n\nAuthor: Alex Deva (me@alxx.se)\n\nThe gem is available as open source under the terms of the\n[MIT License](http://opensource.org/licenses/MIT).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Falxx%2Fsurikat","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Falxx%2Fsurikat","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Falxx%2Fsurikat/lists"}