<a href=”https://github.com/simonprev/solage”><img src=”https://cloud.githubusercontent.com/assets/464900/15801312/1cf60030-2a5f-11e6-8470-085779e78046.png” height=”65”></a>

Travis

Provides basic functionalities to implement a JSON API-compliant API.

Installation

Add solage to the deps function in your project’s mix.exs file:

defp deps do
  [
    ,
    {:solage, "~> 0.1"}
  ]
end

Then run mix do deps.get, deps.compile inside your project’s directory.

Why?

While implementing a JSON API-compliant API in Elixir, I stumbled upon two great packages: JaSerializer and jsonapi. With great respect to their maintainers, I found three problems with those packages:

At first I tried to contribute to the packages but I thought that it could be a good exercise to implement those things myself :P A lot of what is implemented comes directly from those two packages. Thanks again for the cleverness of their maintainers.

Description

This package doesn’t implement the whole view with id, type, attributes or relationships. It lets your application define this stuff. It’s a bit more of boilerplate code to write but you’ll never have to bend or hook you in a restrictive DSL. This means you can implement an API with nested resources and not use the included feature. You would benefit from awesome features such as preload parsing, sort parsing, etc. Those concepts are good on their own without attaching the whole JSON API specification in front of it. This is what this package is aiming for.

Solage.QueryConfig

By using the QueryConfig, you don’t have to hardcode data behaviour right in the view. It’s up to who is rendering the view to use a config that fits your need. The config can be initialized with the Solage.Query plug.

What’s inside QueryConfig

Solage.Serializer

Use the Serializer to render the data and included section of the JSON API. You only need a view that implements render/3 and you’re ready to go. To obtain the relation view of an included resource, you will need to implement relation_view/1 that receive the relation name as an atom.

What’s inside Serializer

2 functions

Solage.DataTransform

The DataTransform module provides a quick and extensible way of encoding and decoding data keys. You could use it in a plug before using the params to replace - with _ on all keys. Or run it on the encoded data before sending the response.

The decoder and encoder are set in the config :solage, :decoder_formatter and :solage, :encoder_formatter. Check the Solage.DataTransform.Transformer behaviour to implement your own.

Valid formatters

Examples

plug :deserialize_params

defp deserialize_params(conn, _) do
  %{conn | params: Solage.DataTransform.decode(conn.params)}
end

Solage.AttributeTransform

The AttributeTransform module provides a clean and extensible way to transform a JSON API payload to a usable flat map like a regular REST API would accept. You can use the default resolver that assume that your relationship will be the name of the association + _id for belongs_to associations and name of the association + _ids for has_many associations.

But the interface could be implemented to support other kind of input. Check the Solage.AttributeTransform.Transformer behaviour to implement your own.

Examples

params = {
  "type": "person",
  "attributes": {"first": "Jane", "last": "Doe", "type": "anon"},
  "relationships": {"user": {"data": {"id": 1}}}
}

Solage.AttributeTransform.flatten(params)
# => {
#      "person": {
#        "first": "Jane",
#        "last": "Doe",
#        "type": "anon",
#        "user_id": 1
#      }
#    }

Examples

Views

defmodule PostView do
  def render(data, _config, _conn) do
    %{
      id: data["id"],
      relationships: %{
        user: data["author_id"]
      }
    }
  end

  def relation_view(:author), do: UserView
end

defmodule UserView do
  def render(data, _config, _conn) do
    %{
      id: data["id"],
      full_name: data["fullname"]
    }
  end
end

Endpoint

def show(conn, _) do
  # Transform params key with the right format
  conn = %{conn | params: Solage.DataTransform.decode(conn.params)}

  # Render the data value
  data = Solage.Serializer.render(PostView, conn.assigns[:post], conn.assigns[:jsonapi_query], conn)

  # Render the included value
  included = Solage.Serializer.included(PostView, conn.assigns[:post], conn.assigns[:jsonapi_query], conn)

  # Encode the final body with the right format
  body = Solage.DataTransform.encode(%{data: data, included: included})

  # Send the json response
  json(conn, 200, body)
end

Reponse

{
  "included": [
    {
      "id": "my-author-id",
      "full-name": "Testy"
    }
  ],
  "data": [
    {
      "id": "my-post-id",
      "relationships": {
        "author": "my-author-id"
      }
    }
  ]
}

License

Solage is © 2016 Simon Prévost and may be freely distributed under the MIT license. See the LICENSE.md file for more information.