CI Gem Version Ruby Style Guide semantic-release: angular
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.Here's an example resource from the example app just to give you a taste of the possibilities.
class EmployeeResource < ApplicationResource attribute :first_name, :string attribute :last_name, :string attribute :age, :integer attribute :created_at, :datetime, writable: false attribute :updated_at, :datetime, writable: false attribute :title, :string, only: [:filterable, :sortable] has_many :positions has_many :tasks many_to_many :teams polymorphic_has_many :notes, as: :notable has_one :current_position, resource: PositionResource do params do |hash| hash[:filter][:current] = true end end filter :title, only: [:eq] do eq do |scope, value| scope.joins(:current_position).merge(Position.where(title: value)) end end sort :title do |scope, value| scope.joins(:current_position).merge(Position.order(title: value)) end sort :department_name, :string do |scope, value| scope.joins(current_position: :department) .merge(Department.order(name: value)) end end
A pretty boilerplate controller that just interfaces with the resource
class EmployeesController < ApplicationController def index employees = EmployeeResource.all(params) respond_with(employees) end def show employee = EmployeeResource.find(params) respond_with(employee) end def create employee = EmployeeResource.build(params) if employee.save render jsonapi: employee, status: 201 else render jsonapi_errors: employee end end def update employee = EmployeeResource.find(params) if employee.update_attributes render jsonapi: employee else render jsonapi_errors: employee end end def destroy employee = EmployeeResource.find(params) if employee.destroy render jsonapi: { meta: {} }, status: 200 else render jsonapi_errors: employee end end end
Now you can query your endpoints simply and powerfully, like:
Request:
http://localhost:3000/api/v1/employees?filter[title][eq]=Future Government Administrator&filter[age][lt]=40
JSON-API response
{
"data": [
{
"id": "1",
"type": "employees",
"attributes": {
"first_name": "Quinn",
"last_name": "Homenick",
"age": 36,
"created_at": "2025年03月21日T23:04:40+00:00",
"updated_at": "2025年03月21日T23:04:40+00:00"
},
"relationships": {
"positions": {
"links": {
"related": "/api/v1/positions?filter[employee_id]=1"
},
"data": [
{
"type": "positions",
"id": "1"
},
{
"type": "positions",
"id": "2"
}
]
},
"tasks": {
"links": {
"related": "/api/v1/tasks?filter[employee_id]=1"
}
},
"teams": {
"links": {
"related": "/api/v1/teams?filter[employee_id]=1"
}
},
"notes": {
"links": {
"related": "/api/v1/notes?filter[notable_id]=1&filter[notable_type][eql]=Employee"
}
},
"current_position": {
"links": {
"related": "/api/v1/positions?filter[current]=true&filter[employee_id]=1"
},
"data": {
"type": "positions",
"id": "1"
}
}
}
}
],
"included": [
{
"id": "1",
"type": "positions",
"attributes": {
"title": "Future Government Administrator",
"active": true
},
"relationships": {
"employee": {
"links": {
"related": "/api/v1/employees/1"
}
},
"department": {
"links": {
"related": "/api/v1/departments/3"
}
}
}
},
{
"id": "2",
"type": "positions",
"attributes": {
"title": "Manufacturing Specialist",
"active": false
},
"relationships": {
"employee": {
"links": {
"related": "/api/v1/employees/1"
}
},
"department": {
"links": {
"related": "/api/v1/departments/2"
}
}
}
}
],
"meta": {}
}