# Relax
An API routing library aimed at building [jsonapi.org](http://jsonapi.org)
spec servers on top of plug.
*WARNING: As of Relax 0.1.0 serialization is handled by a seperate library:
[JaSerializer](http://github.com/AgilionApps/ja_serializer).*
Relax APIs are composed a Router and Resources. Both Routers and Resources
are simple DSLs on top of standard Plugs.
## Example
Simple Plug based DSLs for routing/dispatching API requests.
```elixir
defmodule MyApp do
# Our Router is just a plug router and we can start it as such.
def start, do: Plug.Adapters.Cowboy.http MyApp.Router, []
# Our router is our main entry point for all requests.
# Relax.Router is just a DSL on top of Plug.Router, so the standard plug
# stack still works and is used.
defmodule Router do
use Relax.Router
plug :match
plug :dispatch
version :v1 do
# Dispatch all /v1/posts/* requests to MyApp.API.Posts plug.
resource :posts, MyApp.API.Posts
end
end
# Our "Resource" similar to a controller, is just different DSL on a Plug.Router.
# By including Relax.Resource we define matches for:
# * GET /:id
# * GET /
# * POST /
# * PUT(or PATCH) /:id
# * DELETE /:id.
# Each match is then dispatched to the proper callback.
defmodule API.Posts do
# Don't match put or delete (:update or :delete)
use Relax.Resource, only: [:find_all, :find_one, :create]
# Every resource is expected to define a serializer.
# This will be used by each request. And is expected to be a JaSerializer
# serializer
serializer MyApp.Serializer.Post
plug :match
plug :dispatch
# Call back for GET / - returns 200 with all posts serialized
def find_all(conn), do: okay(conn, MyApp.Post.all)
# Call back for GET /:id1 returns 200 with posts serialized or 404
def find_one(conn, id) do
case MyApp.Post.find(id) do
nil -> not_found(conn)
post -> okay(conn, post)
end
end
end
defmodule Serializer.Post do
use JaSerializer
serialize "posts" do
attributes [:id, :title, :body]
end
end
end
```
## Installation
Relax is Alpha software and APIs are still stabalizing, use at your own risk.
```elixir
{:relax, "~> 0.1.0"}
```
## Usage
### Relax.Router
The Relax.Router is a thin layer on top of the existing Plug.Router implementation. It provides version and resource macros to let you quickly define resources.
You can still use `Plug.Route.forward/2` and `Plug.Route.match/2` as well as hook into the plug stack normally.
```elixir
defmodule MyApp.Router do
use Relax.Router
plug :match
plug :dispatch
forward "/app", to: MyApp.Static
version :v1 do
resource :posts, MyApp.API.V1.Posts do
resource :comments, MyApp.API.V1.Posts.Comments
end
resource :comments, MyApp.API.V1.Comments
end
match _ do
Plug.Conn.send_resp(conn, 404, "")
end
end
```
### Relax.Resource
Relax.Resource wraps macros routing to proper actions as well as serializing and sending responses.
A Relax.Resource delegates the appropriate path matches to the actions `find_all/1`, `find_one/2`, `create/1`, `update/2`, and `delete/2`.
In your resource you can choose to only support a subset of these using `:only` or `:except`.
Once again, normal Plug.Route plug stack, functions, and matching work, however they will be defined after the pre-generated resource matches.
```elixir
defmodule API.V1.Posts do
use Relax.Resource, only: [:find_all, :find_one, :create]
plug :match
plug :dispatch
serializer Serializers.V1.Post
def find_all(conn) do
okay(conn, Post.all)
end
def find_one(conn, id) do
case Post.find(id) do
nil -> not_found(conn)
post -> okay(conn, post)
end
end
def create(conn) do
case MyApp.Models.Post.create(attributes(conn)) do
{:ok, post} -> created(conn, post)
{:error, errors} -> invalid(conn, errors)
end
end
post "/:id/publish" do
#...
okay(conn, post)
end
def match(_), do: not_found(conn)
defp attributes(%{params: params} = conn) do
params["data"]["attributes"]
|> Dict.take(["title", "body"])
end
end
```
## License
Relax source code is released under Apache 2 License. Check LICENSE file for more information.