Getting Started with JSONAPI

This guide walks you through installing the JSONAPI library, defining a view, and rendering your first spec-compliant JSON:API response.

Installation

Add jsonapi to your dependencies in mix.exs:

defp deps do
  [
    {:jsonapi, "~> 1.10.0"}
  ]
end

Then fetch dependencies:

mix deps.get

Configuration

Add basic configuration in config/config.exs:

config :jsonapi,
  namespace: "/api",
  field_transformation: :underscore,
  json_library: Jason
OptionDescription
namespacePath prefix for generated resource URLs (e.g., /api)
field_transformationHow field names are transformed: :underscore, :camelize, :dasherize
json_libraryJSON encoder module (defaults to Jason)
hostOverride the connection host for generated URLs
schemeOverride the connection scheme (:http or :https)
remove_linksSet to true to exclude links from output

Define a View

Create a view module for each resource type. The view declares the JSON:API type name, which fields to include, and any relationships:

defmodule MyApp.UserView do
  use JSONAPI.View, type: "users"

  def fields do
    [:name, :email, :inserted_at]
  end
end

The type option sets the resource type string in the JSON:API document. The fields/0 callback returns a list of atoms matching your schema’s field names.

Add Plugs to Your Pipeline

JSONAPI ships with several plugs for handling requests and responses. Add them to your Phoenix router pipeline:

pipeline :api do
  plug :accepts, ["json"]
  plug JSONAPI.EnsureSpec
  plug JSONAPI.Deserializer
  plug JSONAPI.UnderscoreParameters
end
  • JSONAPI.EnsureSpec validates that incoming requests have proper JSON:API content types and structure
  • JSONAPI.Deserializer transforms the JSON:API request body into a flat params map your controllers can work with
  • JSONAPI.UnderscoreParameters converts parameter keys from the configured format (e.g., camelCase) back to underscored keys

Render a Response

In your Phoenix controller, render data through your view:

defmodule MyAppWeb.UserController do
  use MyAppWeb, :controller

  def show(conn, %{"id" => id}) do
    user = Accounts.get_user!(id)
    render(conn, MyApp.UserView, "show.json", %{data: user})
  end

  def index(conn, _params) do
    users = Accounts.list_users()
    render(conn, MyApp.UserView, "index.json", %{data: users})
  end
end

This produces a JSON:API document like:

{
  "data": {
    "type": "users",
    "id": "1",
    "attributes": {
      "name": "Jane Smith",
      "email": "jane@example.com",
      "inserted_at": "2024-01-15T10:30:00Z"
    },
    "links": {
      "self": "https://myapp.com/api/users/1"
    }
  },
  "jsonapi": {
    "version": "1.0"
  }
}

Standalone Serialization

You can also serialize without Phoenix rendering:

JSONAPI.Serializer.serialize(MyApp.UserView, user, conn)

# With metadata
JSONAPI.Serializer.serialize(MyApp.UserView, user, conn, %{total: 42})

Add Query Parsing

To support JSON:API query parameters (sorting, filtering, includes), add the JSONAPI.QueryParser plug to specific routes or controllers:

plug JSONAPI.QueryParser,
  filter: ~w(name email),
  sort: ~w(name inserted_at),
  view: MyApp.UserView

This parses query parameters into conn.assigns.jsonapi_query:

# GET /api/users?sort=-name&filter[email]=jane@example.com

def index(conn, _params) do
  query = conn.assigns.jsonapi_query
  # query.sort => [desc: :name]
  # query.filter => %{"email" => "jane@example.com"}
end

Next Steps

  • Serialization — Define relationships, computed fields, meta, and customize output
  • Pagination — Add pagination links to list responses
  • Cheatsheet — Quick reference for views and configuration