PlugRest
A port of Cowboy’s cowboy_rest module to Plug.
PlugRest supplements Plug.Router
with an additional resource
macro, which matches a URL path with a resource handler module
implementing REST semantics via a series of optional callbacks.
Hello World
Define a router to match a path with a resource handler:
defmodule MyRouter do
use PlugRest.Router
resource "/hello", HelloResource
end
Define the resource handler and implement the optional callbacks:
defmodule HelloResource do
use PlugRest.Resource
def to_html(conn, state) do
{"Hello world", conn, state}
end
end
Installation
If starting a new project, generate a supervisor application:
$ mix new my_app --sup
Add PlugRest to your project in two steps:
Add
:cowboy
,:plug
, and:plug_rest
to your list of dependencies inmix.exs
:def deps do [{:cowboy, "~> 1.0.0"}, {:plug, "~> 1.0"}, {:plug_rest, "~> 0.5.0"}] end ```
Add these dependencies to your applications list:
def application do [applications: [:cowboy, :plug, :plug_rest]] end ```
Resources
Create a file at lib/my_app/hello_resource.ex
to hold your Resource
Handler:
defmodule MyApp.HelloResource do
use PlugRest.Resource
def allowed_methods(conn, state) do
{["GET"], conn, state}
end
def content_types_provided(conn, state) do
{[{"text/html", :to_html}], conn, state}
end
def to_html(conn, state) do
{"Hello world", conn, state}
end
end
Router
Create a file at lib/my_app/router.ex
to hold the Router:
defmodule MyApp.Router do
use PlugRest.Router
resource "/hello", MyApp.HelloResource
match "/match" do
send_resp(conn, 200, "Match")
end
end
The PlugRest Router adds a resource
macro which accepts a URL path
and a Module that will handle all the callbacks on the Resource.
You can also use the match
macros from Plug.Router
.
This provides an escape hatch to bypass the REST mechanism for a
particular route and send a Plug response manually.
If no routes match, the PlugRest will send a response with a 404
status code to the client automatically.
Dynamic path segments
Router paths can have segments that match URLs dynamically:
resource "/users/:id", MyApp.UserResource
The path parameters can be accessed in your resource with read_path_params/1
:
def to_html(conn, state) do
params = read_path_params(conn)
user_id = params["id"]
{"Hello #{user_id}", conn, state}
end
Application
Finally, add the Router to your supervision tree by editing
lib/my_app.ex
:
# Define workers and child supervisors to be supervised
children = [
Plug.Adapters.Cowboy.child_spec(:http, MyApp.Router, [], [port: 4001])
]
Running
Compile your application and then run it:
$ iex -S mix
Your server will be running and the resource will be available at
http://localhost:4001/hello
.
Testing
Use Plug.Test
to help verify your resources’s responses to separate
requests. Create a file at test/resources/hello_resource_test.exs
to
hold your test:
defmodule MyApp.HelloResourceTest do
use ExUnit.Case
use Plug.Test
alias MyApp.Router
test "get hello resource" do
conn = conn(:get, "/hello")
conn = Router.call(conn, [])
assert conn.status == 200
assert conn.resp_body == "Hello world"
end
end
Run the test with:
$ mix test
Phoenix
You can use PlugRest in your Phoenix app. Add :plug_rest
to your
dependencies, save your REST router at web/rest_router.ex
, and put
your resources in web/resources/
. Then use the forward
macro in
your Phoenix web/router.ex
:
forward "/rest", HelloPhoenix.RestRouter
The resource will be served at http://localhost:4001/rest/hello
.
Information
The Cowboy documentation has more details on the REST protocol:
Differences between PlugRest and cowboy_rest:
- Each callback accepts a Plug
conn
struct instead of a CowboyReq
record. - The
init/2
callback is not required. However, if it does exist, it should return{:ok, conn, state}
. - The content callbacks (like
to_html
) return{body, conn, state}
where the body is one ofiodata
or{:chunked, Enum.t}
. - Media types take the form
{binary, binary, %{binary => binary}}
, followingPlug.Conn.Utils
parsers. - The content types provided and accepted callbacks can describe each
type with a String
"text/html"
or tuple{"text", "html", %{}}
, - In a media type
{type, subtype, params}
tuple, the params are a map rather than a list of tuples.
Upgrading
PlugRest is still in an initial development phase. Expect breaking changes at least in each minor version.
See the CHANGELOG for more information.
License
PlugRest copyright © 2016, Christopher Adams
cowboy_rest copyright © 2011-2014, Loïc Hoguin essen@ninenines.eu
Cowboy logo copyright © 2016, dikaio