|
| 1 | +#### Graphiti |
| 2 | + |
1 | 3 | [](https://github.com/graphiti-api/graphiti/actions/workflows/ci.yml) |
2 | 4 | [](https://badge.fury.io/rb/graphiti) |
3 | 5 | [](https://github.com/testdouble/standard) |
4 | 6 | [](https://github.com/semantic-release/semantic-release) |
5 | 7 |
|
6 | | -<p align="center"> |
7 | | - <a href="https://www.graphiti.dev/guides"> |
8 | | - <img " src="https://user-images.githubusercontent.com/55264/54884141-c10ada00-4e43-11e9-866b-e3c01e33a7c7.png" /> |
9 | | - </a> |
10 | | -</p> |
11 | | - |
12 | | -Stylish Graph APIs. |
13 | 8 |
|
14 | | -[Website](https://www.graphiti.dev/guides/) |
| 9 | +[](https://discord.gg/wgqkMBsSRV) |
| 10 | +[](https://www.graphiti.dev) |
| 11 | + |
| 12 | + |
| 13 | + |
| 14 | +<img align="right" src="https://user-images.githubusercontent.com/55264/54884141-c10ada00-4e43-11e9-866b-e3c01e33a7c7.png" alt="Graphiti logo" width="150px" /> |
| 15 | +Graphiti is a resource-oriented framework that sits on top of your models (usually ActiveRecord) and exposes them via a JSON:API-compliant interface. It abstracts common concerns like serialization, filtering, sorting, pagination, and sideloading relationships, so you can build powerful APIs with minimal boilerplate. By defining resources instead of controllers and serializers, Graphiti helps you keep your API logic organized, consistent, and easy to maintain. |
| 16 | + |
| 17 | + |
| 18 | +#### Examples |
| 19 | +Here's an example resource from the [example app](https://github.com/graphiti-api/employee_directory/) just to give you a taste of the possibilities. |
| 20 | + |
| 21 | + |
| 22 | +```ruby |
| 23 | +class EmployeeResource < ApplicationResource |
| 24 | + attribute :first_name, :string |
| 25 | + attribute :last_name, :string |
| 26 | + attribute :age, :integer |
| 27 | + attribute :created_at, :datetime, writable: false |
| 28 | + attribute :updated_at, :datetime, writable: false |
| 29 | + attribute :title, :string, only: [:filterable, :sortable] |
| 30 | + |
| 31 | + has_many :positions |
| 32 | + has_many :tasks |
| 33 | + many_to_many :teams |
| 34 | + polymorphic_has_many :notes, as: :notable |
| 35 | + has_one :current_position, resource: PositionResource do |
| 36 | + params do |hash| |
| 37 | + hash[:filter][:current] = true |
| 38 | + end |
| 39 | + end |
| 40 | + |
| 41 | + filter :title, only: [:eq] do |
| 42 | + eq do |scope, value| |
| 43 | + scope.joins(:current_position).merge(Position.where(title: value)) |
| 44 | + end |
| 45 | + end |
| 46 | + |
| 47 | + sort :title do |scope, value| |
| 48 | + scope.joins(:current_position).merge(Position.order(title: value)) |
| 49 | + end |
| 50 | + |
| 51 | + sort :department_name, :string do |scope, value| |
| 52 | + scope.joins(current_position: :department) |
| 53 | + .merge(Department.order(name: value)) |
| 54 | + end |
| 55 | +end |
| 56 | +``` |
| 57 | + |
| 58 | +A pretty boilerplate controller that just interfaces with the resource |
| 59 | +```ruby |
| 60 | +class EmployeesController < ApplicationController |
| 61 | + def index |
| 62 | + employees = EmployeeResource.all(params) |
| 63 | + respond_with(employees) |
| 64 | + end |
| 65 | + |
| 66 | + def show |
| 67 | + employee = EmployeeResource.find(params) |
| 68 | + respond_with(employee) |
| 69 | + end |
| 70 | + |
| 71 | + def create |
| 72 | + employee = EmployeeResource.build(params) |
| 73 | + |
| 74 | + if employee.save |
| 75 | + render jsonapi: employee, status: 201 |
| 76 | + else |
| 77 | + render jsonapi_errors: employee |
| 78 | + end |
| 79 | + end |
| 80 | + |
| 81 | + def update |
| 82 | + employee = EmployeeResource.find(params) |
| 83 | + |
| 84 | + if employee.update_attributes |
| 85 | + render jsonapi: employee |
| 86 | + else |
| 87 | + render jsonapi_errors: employee |
| 88 | + end |
| 89 | + end |
| 90 | + |
| 91 | + def destroy |
| 92 | + employee = EmployeeResource.find(params) |
| 93 | + |
| 94 | + if employee.destroy |
| 95 | + render jsonapi: { meta: {} }, status: 200 |
| 96 | + else |
| 97 | + render jsonapi_errors: employee |
| 98 | + end |
| 99 | + end |
| 100 | +end |
| 101 | +``` |
| 102 | + |
| 103 | +</details> |
| 104 | + |
| 105 | + |
| 106 | +Now you can query your endpoints simply and powerfully, like: |
| 107 | + |
| 108 | + |
| 109 | + |
| 110 | +Request: |
| 111 | +```http://localhost:3000/api/v1/employees?filter[title][eq]=Future Government Administrator&filter[age][lt]=40``` |
| 112 | + |
| 113 | +<details> |
| 114 | +<summary>JSON-API response</summary> |
| 115 | + |
| 116 | +```json |
| 117 | +{ |
| 118 | + "data": [ |
| 119 | + { |
| 120 | + "id": "1", |
| 121 | + "type": "employees", |
| 122 | + "attributes": { |
| 123 | + "first_name": "Quinn", |
| 124 | + "last_name": "Homenick", |
| 125 | + "age": 36, |
| 126 | + "created_at": "2025-03-21T23:04:40+00:00", |
| 127 | + "updated_at": "2025-03-21T23:04:40+00:00" |
| 128 | + }, |
| 129 | + "relationships": { |
| 130 | + "positions": { |
| 131 | + "links": { |
| 132 | + "related": "/api/v1/positions?filter[employee_id]=1" |
| 133 | + }, |
| 134 | + "data": [ |
| 135 | + { |
| 136 | + "type": "positions", |
| 137 | + "id": "1" |
| 138 | + }, |
| 139 | + { |
| 140 | + "type": "positions", |
| 141 | + "id": "2" |
| 142 | + } |
| 143 | + ] |
| 144 | + }, |
| 145 | + "tasks": { |
| 146 | + "links": { |
| 147 | + "related": "/api/v1/tasks?filter[employee_id]=1" |
| 148 | + } |
| 149 | + }, |
| 150 | + "teams": { |
| 151 | + "links": { |
| 152 | + "related": "/api/v1/teams?filter[employee_id]=1" |
| 153 | + } |
| 154 | + }, |
| 155 | + "notes": { |
| 156 | + "links": { |
| 157 | + "related": "/api/v1/notes?filter[notable_id]=1&filter[notable_type][eql]=Employee" |
| 158 | + } |
| 159 | + }, |
| 160 | + "current_position": { |
| 161 | + "links": { |
| 162 | + "related": "/api/v1/positions?filter[current]=true&filter[employee_id]=1" |
| 163 | + }, |
| 164 | + "data": { |
| 165 | + "type": "positions", |
| 166 | + "id": "1" |
| 167 | + } |
| 168 | + } |
| 169 | + } |
| 170 | + } |
| 171 | + ], |
| 172 | + "included": [ |
| 173 | + { |
| 174 | + "id": "1", |
| 175 | + "type": "positions", |
| 176 | + "attributes": { |
| 177 | + "title": "Future Government Administrator", |
| 178 | + "active": true |
| 179 | + }, |
| 180 | + "relationships": { |
| 181 | + "employee": { |
| 182 | + "links": { |
| 183 | + "related": "/api/v1/employees/1" |
| 184 | + } |
| 185 | + }, |
| 186 | + "department": { |
| 187 | + "links": { |
| 188 | + "related": "/api/v1/departments/3" |
| 189 | + } |
| 190 | + } |
| 191 | + } |
| 192 | + }, |
| 193 | + { |
| 194 | + "id": "2", |
| 195 | + "type": "positions", |
| 196 | + "attributes": { |
| 197 | + "title": "Manufacturing Specialist", |
| 198 | + "active": false |
| 199 | + }, |
| 200 | + "relationships": { |
| 201 | + "employee": { |
| 202 | + "links": { |
| 203 | + "related": "/api/v1/employees/1" |
| 204 | + } |
| 205 | + }, |
| 206 | + "department": { |
| 207 | + "links": { |
| 208 | + "related": "/api/v1/departments/2" |
| 209 | + } |
| 210 | + } |
| 211 | + } |
| 212 | + } |
| 213 | + ], |
| 214 | + "meta": {} |
| 215 | +} |
| 216 | +``` |
| 217 | + |
| 218 | +</details> |
| 219 | + |
| 220 | + |
| 221 | + |
| 222 | +[Graphiti Guides](https://www.graphiti.dev/guides/) |
| 223 | + |
| 224 | +[Join the Discord](https://discord.gg/wgqkMBsSRV) |
15 | 225 |
|
16 | | -[Join the Slack Channel](https://join.slack.com/t/graphiti-api/shared_invite/enQtMjkyMTA3MDgxNTQzLTU5MDI4MDllNTEzOTE1Nzk0ZGJlNTcxZDYzMGY2ZTczMDY2OWZhM2RmNTU0YWNiOWZhZDhkMmU4MzQ5NzIyNWM) |
17 | 226 |
|
18 | | - |
19 | 227 |
|
20 | | -Supports Rails >= 4.1 |
21 | 228 |
|
22 | | -*Looking for JSONAPI-Suite? You're in the right place. Graphiti is the 1.0 version of JSONAPI Suite. For the deprecated Suite gem, go [here](https://github.com/jsonapi-suite/jsonapi_suite_deprecated)* |
|
0 commit comments