14
.
11
.
2023
7
.
05
.
2019
Ruby on Rails
Backend
Tutorial

JSON:API consumption in Rails

Jan Matusz
Ruby Developer

In the world of modern web applications you cannot avoid becoming a heavy user of APIs. Consumption of different interfaces brings the experience, and experience brings knowledge.

One of the many things we learned at Visuality is that its not only okay to try new things, but it is also even better if other people tried these and have some insights on how to actually do it the good way. Over time we researched and developed ways to introduce JSON:API into our Rails applications, so that we could be compliant with this specification, thanks to which the development process would be more effective and time efficient.

If you would like to learn more about JSON:API consider checking out Nadia's article about JSON:API and NIH syndrome.

Let's get down to business now, and as a backend engineer at Visuality, I will be your guide today!

Shortcoming

There are many implementations of this standard (you can find a comprehensive list here), but what we recognised is that most of them usually work one-way: to serialise the data we have in the Rails application so it can be served to the API consumer. The major shortcoming was that there was no go-to solution for receiving and understanding the input the consumer sends to our application.

The ones that supposedly were to support input deserialisation either did not work 100%, were abandonware or weren't supporting JSON:API specification in its entirety.

It was a dead end.

Innovation

It lead us to a point where we decided that maybe it is a good idea to create something of our own. We had a simple goal: our Rails application must read JSON:API compliant input and handle it. It would be the best if it did not require huge configuration overhead and if it didn't force people to learn and write code in a very different (compared to what is acknowledged as standard, Rails-way) convention.

The input that JSON:API specification describes is pretty simple. Take a look at a sample:

POST /photos HTTP/1.1
Content-Type: application/vnd.api+json
Accept: application/vnd.api+json

{
  "data": {
    "type": "photos",
    "attributes": {
      "title": "Ember Hamster",
      "src": "http://example.com/images/productivity.png"
    },
    "relationships": {
      "photographer": {
        "data": { "type": "people", "id": "9" }
      }
    }
  }
}

Lets break down and note a few things:

  • Content-Type --- JSON:API has its own mime type registered: application/vnd.api+json, and specification requires all requests to provide it,
  • Request body. We can see that the main key is data, which holds resources informations.
  • Resource --- it is identified by a type, attributes and optional relationships(which, again, are resources).

This input will not work with what you would typically write inside a Rails controller to handle parameters. Instead, this is how it would look like:

{
 "photo_parameters": {
  "title": "Ember Hamster",
  "src": "http://example.com/images/productivity.png",
  "photographer": { "id": 9 }
 }
}

For a person that had been working with Rails over the years, but not with JSON:API, I would not be surprised if you were in favour of the latter example. However, JSON:API provides lots of great functionalities and features that would not be so easy to achieve on your own (and then, you would have to educate your consumers on how to use these).

At Visuality the choice was made that JSON:API is the way we want to go for now --- as it was either that, or the implemented-here solutions that varied in features between different projects. JSON:API gave us a good, unified solution that had every scenario that we needed. Returning data in this format was easy, as you had ActiveModelSerializers or fast_jsonapi from Netflix, but there was no way to easily understand the input client would send us.\ But since we already could compare the JSON:API compliant input and the Rails expected one, why not get the best out of two worlds? Why should not we let Rails developers keep using their parameters as they always had done, while allowing JSON:API consumers to actually consume and feed the server with JSON:API data?

JsonApi::Parameters

That is why we brought a new library to life. Our gem aims to support Rails developers in consuming JSON:API input. It is mostly just a translator, that transforms standard compliant input to what Rails (and Rails developers) expect. Considering a sample params call in a controller:

params.require(:photo).permit(photographer: [:id])

The only thing you need to consume the JSON:API version of it is prepend require with from_jsonapi:

params.from_jsonapi.require(:photo).permit(photographer: [:id])

What this function does is literally translating the JSON:API input into a new instance of ActionController::Parameters that has a form of standard Rails model parameters.\ The specification has things that may be considered quirks, especially when it comes to creating new related resources --- where you have relationships key, and included key (one holds the resource identifier and its type, second holds the resources attributes object). We have done our best to handle these as well.

We did not want to limit our users to Rails only, so you can also use this gem in vanilla Ruby.

Additionally, if you remember from the things we have noted down above, there was this specific JSON:API Content-Type. JsonApi::Parametersregisters this mime type for you, in a way that should not break your Rails app (obviously you can unregister the Mime Type yourself, if you would like to).

So far we have been using this library in production for a couple of months now and it has proven handy, so we are looking forward to maintain and extend it. Actually, we have already had a couple of feature requests (one being the Mime::Type registration process) that we were happy to handle and implement, so definitely share your insights with us in the Github repository!

If you're interested in more blog posts like this please visit our Visuality Blogor our Medium Page

Resources

Thanks to Grzegorz Korzeniowski.

Jan Matusz
Ruby Developer

Check my Twitter

Check my Linkedin

Did you like it? 

Sign up To VIsuality newsletter

READ ALSO

How to become a Ruby Certified Programmer Title image

How to become a Ruby Certified Programmer

14
.
11
.
2023
Michał Łęcicki
Ruby
Visuality
Vector Search in Ruby - Paweł Strzałkowski

Vector Search in Ruby

17
.
03
.
2024
Paweł Strzałkowski
ChatGPT
Embeddings
Postgresql
Ruby
Ruby on Rails
LLM Embeddings in Ruby - Paweł Strzałkowski

LLM Embeddings in Ruby

17
.
03
.
2024
Paweł Strzałkowski
Ruby
LLM
Embeddings
ChatGPT
Ollama
Handling Errors in Concurrent Ruby, Michał Łęcicki

Handling Errors in Concurrent Ruby

14
.
11
.
2023
Michał Łęcicki
Ruby
Ruby on Rails
Tutorial
Recap of Friendly.rb 2024 conference

Insights and Inspiration from Friendly.rb: A Ruby Conference Recap

02
.
10
.
2024
Kaja Witek
Conferences
Ruby on Rails

Covering indexes - Postgres Stories

14
.
11
.
2023
Jarosław Kowalewski
Ruby on Rails
Postgresql
Backend
Ula Sołogub - SQL Injection in Ruby on Rails

The Deadly Sins in RoR security - SQL Injection

14
.
11
.
2023
Urszula Sołogub
Backend
Ruby on Rails
Software
Michal - Highlights from Ruby Unconf 2024

Highlights from Ruby Unconf 2024

14
.
11
.
2023
Michał Łęcicki
Conferences
Visuality
Cezary Kłos - Optimizing Cloud Infrastructure by $40 000 Annually

Optimizing Cloud Infrastructure by $40 000 Annually

14
.
11
.
2023
Cezary Kłos
Backend
Ruby on Rails

Smooth Concurrent Updates with Hotwire Stimulus

14
.
11
.
2023
Michał Łęcicki
Hotwire
Ruby on Rails
Software
Tutorial

Freelancers vs Software house

02
.
10
.
2024
Michał Krochecki
Visuality
Business

Table partitioning in Rails, part 2 - Postgres Stories

14
.
11
.
2023
Jarosław Kowalewski
Backend
Postgresql
Ruby on Rails

N+1 in Ruby on Rails

14
.
11
.
2023
Katarzyna Melon-Markowska
Ruby on Rails
Ruby
Backend

Turbo Streams and current user

29
.
11
.
2023
Mateusz Bilski
Hotwire
Ruby on Rails
Backend
Frontend

Showing progress of background jobs with Turbo

14
.
11
.
2023
Michał Łęcicki
Ruby on Rails
Ruby
Hotwire
Frontend
Backend

Table partitioning in Rails, part 1 - Postgres Stories

14
.
11
.
2023
Jarosław Kowalewski
Postgresql
Backend
Ruby on Rails

Table partitioning types - Postgres Stories

14
.
11
.
2023
Jarosław Kowalewski
Postgresql
Backend

Indexing partitioned table - Postgres Stories

14
.
11
.
2023
Jarosław Kowalewski
Backend
Postgresql
SQL Views in Ruby on Rails

SQL views in Ruby on Rails

14
.
11
.
2023
Jan Grela
Backend
Ruby
Ruby on Rails
Postgresql
Design your bathroom in React

Design your bathroom in React

14
.
11
.
2023
Bartosz Bazański
Frontend
React
Lazy Attributes in Ruby - Krzysztof Wawer

Lazy attributes in Ruby

14
.
11
.
2023
Krzysztof Wawer
Ruby
Software

Exporting CSV files using COPY - Postgres Stories

14
.
11
.
2023
Jarosław Kowalewski
Postgresql
Ruby
Ruby on Rails
Michał Łęcicki - From Celluloid to Concurrent Ruby

From Celluloid to Concurrent Ruby: Practical Examples Of Multithreading Calls

14
.
11
.
2023
Michał Łęcicki
Backend
Ruby
Ruby on Rails
Software