Quickstart
Zero to API in 5 minutes
This quickstart will use Rails with ActiveRecord. Head to the guides section for usage with alternate ORMs or avoiding Rails completely.
If the below seems too “magical”, don’t worry - we’re just applying some sensible defaults to get started quickly.
Installation
Let’s start with a classic Rails blog. We’ll use a template to handle some of the boilerplate. Just run this command and accept all the defaults for now:
Feel free to run git diff
if you’re interested in the
particulars; this is mostly just installing gems and including modules.
Note: if a network issue prevents you from pointing to this URL directly, you can download the file and and run this command as
-m /path/to/all.rb
Defining a Resource
A Resource
defines how to query and persist your Model
. In other
words: a Model
is to the database as Resource
is to the API. So
first, let’s define our model:
Now we can use the built-in generator to define our Resource
,
controller, and specs:
You’ll see a number of files created. If you open each one, you’ll see comments explaining what’s going on. Head over to the tutorial for a more in-depth understanding. For now, let’s focus on two key concepts you’ll see over and over again: inputs (via strong_resources), and outputs (via jsonapi-rb).
Our API Inputs are defined in
config/initializers/strong_resources.rb
. You can think of these as
strong parameter templates.
Our API Outputs are defined in
app/serializers/serializable_post.rb
. The DSL is very similar to
active_model_serializers and full documentation can be found at jsonapi-rb.org:
Now run your app!:
Verify http://localhost:3000/api/v1/posts
renders JSON correctly.
Now we just need data.
Seeding Data
We can seed data in two ways: the usual db/seeds.rb
, or using an HTTP
client. Using the client helps get your feet wet with client-side
development, or you can avoid the detour and plow right ahead.
Seeding With Ruby
Edit db/seeds.rb
to create a few Post
s:
And run the script:
Seeding With JS Client
There are a variety of JSONAPI Clients out there. We’ll be using JSORM which is built to work with Suite-specific functionality like nested payloads. It can be used from the browser, but for now we’ll call it using a simple Node script.
Create the project:
Accept the default for all prompts. Now add the JSORM
dependency, as
well as a polyfill for fetch
:
Add this seed code to index.js
:
This should be pretty straightforward if you’re familiar with
ActiveRecord
. We define Model
objects, putting configuration on
class attributes. We instatiating instances of those Models, and call
save()
to persist. For more information, see the JSORM Documentation.
Run the script:
Now load http://localhost:3000/api/v1/posts
. You should have 3 Post
s in
your database!
Querying
Now that we’ve defined our Resource
and seeded some data, let’s see
what query functionality we have. We’ve listed all Post
s at
http://localhost:3000/api/v1/posts
. Let’s see what we can do:
- Sort
- By title, ascending:
- URL:
/api/v1/posts?sort=title
- SQL:
SELECT * FROM posts ORDER BY title ASC
- URL:
- By title, descending:
- URL:
/api/v1/posts?sort=-title
- SQL:
SELECT * FROM posts ORDER BY title DESC
- URL:
- By title, ascending:
- Paginate:
- 2 Per page:
- URL:
/api/v1/posts?page[size]=2
- SQL:
SELECT * FROM posts LIMIT 2
- URL:
- 2 Per page, second page:
- URL:
/api/v1/posts?page[size]=2&page[number]=2
- SQL:
SELECT * FROM posts LIMIT 2 OFFSET 2
- URL:
- 2 Per page:
- Sparse Fieldsets:
- Only render
title
, notactive
:- URL:
/api/v1/posts?fields[posts]=title
- SQL:
SELECT * from posts
(optimizing this query is on the roadmap)
- URL:
- Only render
- Filter:
- Add one line of code:
- URL:
/api/v1/posts?filter[title]=My title!
- SQL:
SELECT * FROM posts WHERE title = "My title!"
- Any filter not whitelisted will raise
JsonapiCompliable::BadFilter
error. - All filter logic can be customized:
-
Customizations can be DRYed up and packaged into
Adapter
s. -
Extra Fields:
- Sometimes you want to request additional fields not part of a normal response (perhaps they are computationally expensive).
- This can be done like so:
- URL:
/api/v1/posts?extra_fields[posts]=description
- SQL:
SELECT * FROM posts
-
You can conditionally eager load data or further customize this logic. See the tutorial for more.
- Statistics:
- Useful for search grids - “Find me the first 10 active posts, and the total count of all posts”.
- One line of code to whitelist the stat:
- URL:
/api/v1/posts?stats[total]=count
- SQL:
SELECT count(*) from posts
- Combine with filters and the count will adjust accordingly.
- There are a number of built-in stats, you can also add your own.
-
This is rendered in the
meta
section of the response: - Error Handling:
- Your app will always render a JSONAPI-compliable error response.
- Cause an error:
-
View the default payload:
-
Different errors can be customized with different response codes, JSON, and side-effects. View jsonapi_errorable for more.
Adding Relationships
JSONAPI Suite supports full querying of relationships (“fetch me this
Post
and 3 active Comment
s sorted by creation date”), as well as
persistence (“save this Post
and 3 Comment
s in a single request”).
Adding Relationships
Let’s start by defining our model:
…and corresponding Resource
object:
Configure the relationship in PostResource
:
This code:
- Whitelists the relationship.
- Knows to link the objects via
post_id
. - Will use
CommentResource
for querying logic (so we can say things like “only return the latest 3 active comments”) - Uses an unfiltered base scope (
Comment.all
). If we wanted, we could do things likeComment.active
here to ensure only active comments are ever returned.
You should now be able to hit /api/v1/comments
with all the same
functionality as before. We just need to seed data.
Start by clearing out your database:
Again, you can seed your data using a NodeJS client or the traditional
db/seeds.rb
.
Seeding with NodeJS
Let’s edit our node-seed/index.js
. First add a Comment
model:
…and add the relationship to Post
:
Replace the existing Post
instances with one Post
and three
Comment
s:
Tell our controller it’s OK to sidepost comments:
And tell our serializer it’s OK to render comments:
Now run the script to persist the Post
and its three Comment
s in a
single request:
Seeding with Ruby
Replace your db/seeds.rb
with this code to persist one Post
and
three Comment
s:
Usage
Now let’s fetch a Post
and filtered Comment
s in a single request: /api/v1/posts?include=comments
.
Any logic in CommentResource
is available to us. Let’s sort the
comments by created_at
descending: /api/v1/posts?include=comments&sort=-comments.created_at
. This should work out-of-the-box.
Now add a filter to CommentResource
:
That filter now works in two places:
/api/v1/comments?filter[active]=true
/api/v1/posts?include=comments&filter[comments][active]=true
This is why Resource
objects exist: they provide an interface to
functionality shared across many different endpoints, with no extra
code.
What’s Next
We have a full CRUD API with robust querying functionality, and the
ability to combine relationships for both reads and writes. But what
happens when you need to customize the sorting logic? What about replacing
ActiveRecord
with an alternate persistence layer, or avoiding Rails
altogether?
These are important topics that JSONAPI Suite was built to address. To learn more about advanced usage and customization, we suggest following the tutorial. There are also a number of how-tos on this site, a good one to start with is How to Use without ActiveRecord
For additional documentation, view the YARD Docs.
For help with specific use cases, join our Slack chat!
Bonus: Testing
Installation
Our generator applied some sensible defaults:
- Rspec Test runner
- jsonapi_spec_helpers Helpers to parse and assert on JSONAPI payloads.
- factory_girl for seeding our test database with fake data.
- faker for generating fake values, such as e-mail addresses, names, avatar URLs, etc.
- database_cleaner to ensure our fake data gets cleaned up between test runs.
By default we rescue exceptions and return a valid error response. In tests, this can be confusing - we probably want to raise errors in tests. So note our exception handling is disabled by default:
But you can enable it on a per-test basis:
In following this guide, we generated Post
and
Comment
resources. Let’s edit our factories to seed randomized data:
Finally, we need to define a Payload
. Payload
s use a
factory_girl
-style DSL to define expected JSON. A Payload
compares a
Model
instance and JSON output, ensuring:
- No unexpected keys
- No missing keys
- No unexpected value types
- No
null
values (this is overrideable) - Model attribute matches JSON attribute
- This can all be customized. See jsonapi_spec_helpers for more.
Let’s define our payloads now:
Run
We can now run specs. Let’s start with the Post
specs:
You should see five specs, with one failing (spec/api/v1/posts/create_spec.rb
),
and one pending (spec/api/v1/posts/update_spec.rb
).
The reason for the failure is simple: our payload defined in
spec/payloads/post.rb
specifies that a Post
JSON should include the
key title
. However, that spec is currently creating a Post
with no
attributes…which means in the response JSON, title
is null
. null
values will fail assert_payload
unless elsewise configured.
So, let’s update our spec to POST attributes, not just an empty object:
Your specs should now pass. The only pending spec is due to a similar
issue - we need to specify attributes in spec/api/v1/posts/update_spec.rb
as
well. Follow the comments in that file to apply a similar change.
You should now have 5 passing request specs! These specs spin up a fake
server, then execute full-stack requests that hit the database and
return JSON. You’re asserting that JSON matches predefined payloads,
without null
s or unknown key/values.
Go ahead and make the same changes to Comment
specs to get 10 passing
request specs.
It’s up to you how far you’d like to go with testing. Should you add a
new spec to spec/api/v1/posts/index_spec.rb
every time you add a
filter with allow_filter
? This boils down to personal preference and
tolerance of failures. Try adding a few specs following the generated
patterns to get a feel for what’s right for you.
Bonus: Documentation
We can autodocument our code using swagger documentation. Documenting an endpoint is one line of code:
Visit http://localhost:3000/api/docs
to see the swagger documentation. Our custom UI will show all possible query parameters (including nested
relationships), as well as schemas for request/responses:
Our generator set up some boilerplate to enable this functionality, you can learn more at: How to Autodocument with Swagger