Skip to content

Instantly share code, notes, and snippets.

@stravid
Last active June 21, 2016 07:24
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save stravid/b9a96410154b6114db6edf218b39080d to your computer and use it in GitHub Desktop.
Save stravid/b9a96410154b6114db6edf218b39080d to your computer and use it in GitHub Desktop.
Ruby JSON API Stack

Ruby JSON API Stack

What is this? I'm a little bugged out by all current approaches that are available to build JSON API APIs. I'm writing all this down to help me figure out what I want and how to accomplish it.

There are basically two sides, reading data and writing data. There are also a few parts of the stack that can be generalized. On the other side there are also parts where I think generalization is harmful.

Things like transforming parameters from dasherized to snake case, and serializing an object graph to JSON can be kept pretty generic. The thing I'm not so sure about on the read side is handling query parameters like included, fields, sort, filter, page and so on. For example using JSONAPI::Resources gives you the query parameter handling out of the box. On the other side I think it's impossible to deal with associations if you don't use the Active Record pattern and/or have 1:1 mapping between API resources and models.

On the write side I'm pretty much against generic solutions since they break immediately once you leave CRUD. I do think it would be useful to have a generic stack, like middlewares, with a well defined place and interface for your business logic.

In general I don't want to validate parameters by hand or have to react to exceptions and serialize them.

Read Side

  1. If desired, authenticate the request.
  2. Transform parameters to snake case.
  3. Pass parameters to application and get object graph returned.
  4. Serialize object graph.

Ideas: Models/Repositorys have to implement a query method that get query parameters passed in and know how to deal with associations, sorting, pagination and all the other generic stuff.

Write Side

  1. If desired, authenticate the request.
  2. Transform parameters to snake case.
  3. Validate incoming parameters. (Short circuit if there are problems.)
  4. Pass parameters to application and get object graph returned.
  5. Serialize object graph.

Example Hanami Implementation

There are a lot of placeholders and dummy implementations that will be replaced by real pieces of code like dry-validations for parameter handling.

module TransformParameters
  def call(params)
    hash = params.to_hash
    super Hash[hash.map { |k, v| [k.gsub('-', '_'), v] }]
  end
end

module ValidateParameters
  def call(params)
    halt_with_errors({ code: "missing parameter" }) unless params.key?("query_name")
    super params
  end
end

module AuthenticateRequest
  def call(params)
    halt_with_errors({ code: "unauthorized" }) unless authenticated?
    super params
  end

  def authenticated?
    true
  end
end

module ErrorHandling
  def halt_with_errors(errors)
    halt 422, errors.to_json
  end
end

module JSONAPIResponse
  def jsonapi_response(status_code, object_graph)
    self.status = status_code
    self.body = object_graph.to_json
  end
end

module WriteAction
  def self.included(base)
    base.prepend ValidateParameters
    base.prepend TransformParameters
    base.prepend AuthenticateRequest

    base.include ErrorHandling
    base.include JSONAPIResponse
    base.include Api::Action
  end
end

module Api::Controllers::Commands
  class Create
    include WriteAction

    def call(params)
      # Pass params to your applications entry point and let it do what it does
      jsonapi_response(200, { data: { type: "person" } })
    end
  end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment