JSONAPI Suite
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 ❤️