Skip Intro »
JSONAPI SUITE is a collection of ruby libraries that facilitate the jsonapi.org specification.
If you're unfamiliar with JSONAPI, think
"RESTful GraphQL created by Yehuda Katz"
Here's a simple Suite app:
# controllers/posts_controller.rb
class PostsController
  jsonapi resource: PostResource

  def index
    posts = Post.all
    render_jsonapi(posts)
  end
end
  
# resources/post_resource.rb
class PostResource
  type :posts
  model Post

  allow_filter :title
  allow_stat total: [:count]
end
  
# serializers/serializable_posts.rb
class SerializablePosts
  type :posts

  attribute :title
  attribute :created_at
  attribute :updated_at
end
  
This API now supports Sparse Fieldsets, Sorting, Pagination, statistics, and Filtering. Though we're using ActiveRecord in these examples, the same patterns apply to ANY ORM or DATASTORE including HTTP calls. Blend SQL and NoSQL in a single request.
Let's access the API using our Javascript Client, which you can think of as
"ActiveRecord in the browser"
await Post
  .where({ title: "Hello!" })
  .order({ created_at: "desc" })
  .per(10).page(2)
  .stats({ total: "count" })
  .fields(["title", "byline"])
  
Associations are simple and customizable:
# app/resources/post_resource.rb
has_many :comments,
  scope: -> { Comment.all },
  resource: CommentResource,
  foreign_key: :post_id
  
// In your JS app
Post.includes("comments")
  
Associations are deep queryable. In other words, you could fetch the Post and only its active comments, sorted by created_at descending. This applies to your entire graph of data. The server-side code would be nothing more than:
allow_filter :active
  
At this point, You may be thinking:
"Is this just a bunch of incomprehensible ruby magic 😬 ?"
No.
We're simply parsing the request, removing boilerplate, and supplying sensible defaults because we believe in convention over configuration.
Let's make our filter a prefix query:
allow_filter :title_prefix do |scope, value|
  scope.where(["title LIKE ?", "#{value}%"])
end
  
From filtering to pagination, these are all just customizable lambdas. You have complete control of the query.
Just as you can Query the full graph of data, you can also Persist the full graph of data in a single request:
post = new Post({ title: "JSONAPI Rocks!" })
comment = new Comment({ body: "I agree!" })
post.comments.push(comment)
post.save({ with: "comments" })
  
All of this is easily validated with end-to-end integration test patterns that ensure backwards-compatibility:
let(:post1) { create(:post, title: "Hello") }
let(:post2) { create(:post, title: "Hiya") }

it "filters correctly" do
  jsonapi_get '/api/v1/posts', params: {
    filter: { title_prefix: 'He' }
  }
  expect(json_ids(true)).to eq([post1.id])
  assert_payload(:post, post1, json_items[0])
end
  
...and automatically documented in Swagger:
There's so much more to talk about. To get your feet wet, check out our Quickstart, or step-by-step Tutorial. We also have comprehensive documentation on the Server and on the Client. Join our Slack Chat to ask questions or say hi - we'd love to meet you and hear what you think ❤️