Home

Awesome

Placid

Build Status Coverage Status Hex.pm Version

A REST toolkit for building highly-scalable and fault-tolerant HTTP APIs with Elixir.

Configuration

HTTP

Options

HTTPS

By default, connecting to a Placid-based API will require all requests to be made over HTTPS, responding to HTTP requests with a 403 Forbidden. If desired, the https_only option may be set allow HTTP requests to be served by your application. Take a look at the kitchen sink example to see this in action.

Options

Note: Create a self-signed certificate for easy testing.

# Generate a keyfile
$ openssl genrsa -out key.pem 2048

# Create a CSR
$ openssl req -new -key key.pem -out request.pem

# Generate a certfile that expires in $NUM_DAYS
$ openssl x509 -req -days $NUM_DAYS -in request.pem -signkey key.pem -out cert.pem

Routing

defmodule Router do
  use Placid.Router

  # Define one of the versions of the API
  # with a simple version number "1"
  # or following semver "1.0.0"
  # or date of release "2014-09-06"
  version "1" do
    # Define your routes here
    get  "/",               Handlers.V1.Pages, :index
    get  "/pages",          Handlers.V1.Pages, :create
    post "/pages",          Handlers.V1.Pages, :create
    put  "/pages/:page_id" when id == 1,
                            Handlers.V1.Pages, :update_only_one
    get  "/pages/:page_id", Handlers.V1.Pages, :show

    # Auto-create a full set of routes for resources
    #
    resource :users,        Handlers.V1.User, arg: :user_id
    #
    # Generates:
    #
    # get     "/users",           Handlers.V1.User, :index
    # post    "/users",           Handlers.V1.User, :create
    # get     "/users/:user_id",  Handlers.V1.User, :show
    # put     "/users/:user_id",  Handlers.V1.User, :update
    # patch   "/users/:user_id",  Handlers.V1.User, :patch
    # delete  "/users/:user_id",  Handlers.V1.User, :delete
    #
    # options "/users",           "HEAD,GET,POST"
    # options "/users/:_user_id", "HEAD,GET,PUT,PATCH,DELETE"
  end

  # An updated version of the AP
  version "2" do
    get  "/",               Handlers.V2.Pages,  :index
    post "/pages",          Handlers.V2.Pages,  :create
    get  "/pages/:page_id", Handlers.V2.Pages,  :show
    put  "/pages/:page_id", Handlers.V2.Pages,  :update

    raw :trace, "/trace",   Handlers.V2.Tracer, :trace

    resource :users,        Handlers.V2.User
    resource :groups,       Handlers.V2.Group
  end
end

get/3, post/3, put/3, patch/3, delete/3, options/2, and any/3 are already built-in as described. resource exists but will need modifications to create everything as noted.

raw/4 allows for using custom HTTP methods, allowing your application to be HTTP spec compliant.

version/2 will need to be created outright. Will allow requests to contained endpoints when version exists in either Accepts header or URL (which ever is defined in app config).

Extra routes will need to be added for *.json, *.xml, etc. requests for optionally specifying desired content type without the use of the Accepts header. These should match parsing/rendering abilities of Placid.

Should required/optional params be gathered for matching purposes? Only return a matched route when all required params are present?

Handlers

defmodule Handlers.V2.Pages do
  use Placid.Handler

  @doc """
  List all available pages
  """
  def index(conn, []) do
    # Somehow get our content
    pages = Queries.Page.all
    render conn, pages
  end

  @doc """
  Show an individual page
  """
  def show(conn, args) do
    result = case Integer.parse args["page_id"] do
        :error ->
          %Error{ id: "no_page_id",
                  message: "A valid page_id is required." }
        {i, _} ->
          Queries.Page.get i
      end

    render conn, result
  end

  @doc """
  Create a new page
  """
  def create(conn, args) do
    render conn, Queries.Page.create(args), status: :created
  end

  @doc """
  Update an individual page
  """
  def update(conn, args) do
    result = case Integer.parse args["page_id"] do
        :error ->
          %Error{ id: "no_page_id",
                  message: "A valid page_id is requried." }
        {i, _} ->
          Queries.Page.update i, args
      end

    render conn, result
  end
end

Actions in handler modules are responsible for handling a request once it has been routed. These actions typically generate a response, whether that be an error, a result, or a result set, so that it can be rendered to the client with the correct content type further up the stack.

CORS

Should have an option to respect Cross-origin resource sharing (CORS) when desired.

Main response headers:

Should check over the HTML5 Rocks CORS flowchart as much as possible.

Is JSON-P still a thing? Should it be supported? What happens with non-GET requests?

Request Parsing

Parsing request bodies from their content type to Elixir terms allows the handler actions to easily use that data in responding to the client. There should be one parser for each supported response content type, with an additional parser for form encoded data.

Current Parsers:

Rendering

Render layer serializes/encodes data based on the requested content type unless overridden for whatever reason in the response stack.

Rendering engine behavior:

defmodule Placid.Response.Rendering.Engine do
  use Behaviour

  @type data :: Keyword | Map | List

  defcallback serialize(data, type, subtype) :: { :ok, binary } | :next
end

defmodule Placid.Response.Rendering.JSON do
  @behaviour Placid.Response.Rendering.Engine

  @types ["application", "text"]

  def serialize(data, type, "json") when type in @types do
    { :ok, data |> Poison.encode!(string: true) }
  end
  def serialize(_, _, _), do: :next
end

Internationalization

I18n should always be considered when producing an API.

Linguist is already a part of the project's dependencies. Need to think of ways to make translations seemless as possible, similar to rendering.

TODO

This list comes primarily from the HTTP API Design Guide by @interagent and friends but will be updated to fit the needs of the project.

License

Placid is released under the MIT License.

See LICENSE for details.