{"id":13509081,"url":"https://github.com/christopheradams/plug_rest","last_synced_at":"2025-03-25T11:31:19.901Z","repository":{"id":57535053,"uuid":"63038372","full_name":"christopheradams/plug_rest","owner":"christopheradams","description":"REST behaviour and Plug router for hypermedia web applications in Elixir","archived":false,"fork":false,"pushed_at":"2020-01-27T05:33:54.000Z","size":307,"stargazers_count":55,"open_issues_count":0,"forks_count":7,"subscribers_count":5,"default_branch":"master","last_synced_at":"2025-03-20T01:41:40.657Z","etag":null,"topics":["elixir","phoenix","plug","rest"],"latest_commit_sha":null,"homepage":"","language":"Elixir","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/christopheradams.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2016-07-11T05:09:58.000Z","updated_at":"2025-01-28T07:59:42.000Z","dependencies_parsed_at":"2022-09-26T18:21:48.765Z","dependency_job_id":null,"html_url":"https://github.com/christopheradams/plug_rest","commit_stats":null,"previous_names":[],"tags_count":23,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/christopheradams%2Fplug_rest","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/christopheradams%2Fplug_rest/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/christopheradams%2Fplug_rest/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/christopheradams%2Fplug_rest/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/christopheradams","download_url":"https://codeload.github.com/christopheradams/plug_rest/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":245453888,"owners_count":20617937,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["elixir","phoenix","plug","rest"],"created_at":"2024-08-01T02:01:02.694Z","updated_at":"2025-03-25T11:31:19.526Z","avatar_url":"https://github.com/christopheradams.png","language":"Elixir","readme":"# PlugRest\n\n[![Build Status](https://travis-ci.org/christopheradams/plug_rest.svg?branch=master)](https://travis-ci.org/christopheradams/plug_rest)\n[![Hex Version](https://img.shields.io/hexpm/v/plug_rest.svg)](https://hex.pm/packages/plug_rest)\n\nAn Elixir port of Cowboy's REST sub-protocol for Plug applications.\n\nPlugRest has two main components:\n\n* `PlugRest.Router` - supplements Plug's router with a `resource` macro,\n  which matches a URL path with a Plug module for all HTTP methods\n* `PlugRest.Resource` - defines a behaviour for Plug modules to\n  represent web resources declaratively using multiple callbacks\n\nPlugRest is perfect for creating well-behaved and semantically correct\nhypermedia web applications.\n\nIf you use Phoenix, be sure to check out\n[PhoenixRest](https://github.com/christopheradams/phoenix_rest/).\n\n[Documentation for PlugRest is available on hexdocs](http://hexdocs.pm/plug_rest/).\u003cbr/\u003e\n[Source code is available on Github](https://github.com/christopheradams/plug_rest).\u003cbr/\u003e\n[Package is available on hex](https://hex.pm/packages/plug_rest).\n\n### Table of Contents\n\n* __[Introduction](#introduction)__\n* __[Installation](#installation)__\n* __[Getting Started](#getting-started)__\n* __[Information](#information)__\n\n## Introduction\n\n### Hello World\n\nDefine a router to match a path with a resource handler:\n\n```elixir\ndefmodule MyRouter do\n  use PlugRest.Router\n\n  plug :match\n  plug :dispatch\n\n  resource \"/hello\", HelloResource\nend\n```\n\nDefine the resource handler and implement the optional callbacks:\n\n```elixir\ndefmodule HelloResource do\n  use PlugRest.Resource\n\n  def to_html(conn, state) do\n    {\"Hello world\", conn, state}\n  end\nend\n```\n\n## Features\n\n#### Router\n\n  - Route all requests for a path to a plug, for any HTTP method\n\n#### Resource\n\n  - Built-in HEAD and OPTIONS responses, and easy handling of GET,\n    POST, PUT, PATCH, and DELETE requests\n  - Content negotiation for media types, languages, and charsets\n  - HTTP access authentication\n  - Observance of etag, expires, last-modified, and vary headers\n  - Multiple choices for redirects, not modified responses, etc.\n  - Correct HTTP status codes for common 400 and 500 level errors\n\n## Why PlugRest?\n\n\u003e The key abstraction of information in REST is a resource. \u003cbr/\u003e\n\u003e —Roy Fielding\n\nIn the REST architectural style, one of the uniform interface\nconstraints is the identification of resources. Using\n[Plug](https://github.com/elixir-lang/plug), we can satisfy this\nrequirement by routing requests based on a URL path.\n\nPlugRest goes a step further. Rather than manually defining HTTP\nsemantics for each route and dividing a resource's behavior over\nmultiple controller actions, PlugRest lets us describe our resources\nin a declarative way (by implementing callbacks), and follows protocol\nfor us, including returning the correct status code when something\ngoes wrong (or right).\n\nPlugRest can help your Elixir application become a fluent speaker of\nthe HTTP protocol. It can assist with content negotiation, cache\nheaders, basic authentication, and redirects. However, it is not a\nfull-featured framework with views, templates, sessions, or web\nsockets. It also lacks a requisite solution for hypermedia controls,\nwhich are essential to REST.\n\nPlugRest is not the first REST-based framework to take a\nresource-oriented approach. Basho's\n[Webmachine](https://github.com/webmachine/webmachine) has inspired\nmany such libraries, including cowboy_rest.\n\nYou can use PlugRest in a standalone web app or as part of an existing\nPhoenix application. Details below!\n\n\n## Installation\n\nIf starting a new project, generate a supervisor application:\n\n```sh\n$ mix new my_app --sup\n$ cd my_app\n```\n\nAdd PlugRest to your project in two steps:\n\n1. Add `:plug_cowboy` and `:plug_rest` to your list of dependencies in `mix.exs`:\n\n    ```elixir\n    defp deps do\n      [\n        {:plug_cowboy, \"~\u003e 2.0\"},\n        {:plug_rest, \"~\u003e 0.14\"}\n      ]\n    end\n    ```\n\nInstall the dependencies by running `mix deps.get` and `mix deps.compile`.\n\n## Getting Started\n\n### Resources\n\nCreate a file at `lib/my_app/resources/hello_resource.ex` to hold your Resource\nHandler:\n\n```elixir\ndefmodule MyApp.HelloResource do\n  use PlugRest.Resource\n\n  def allowed_methods(conn, state) do\n    {[\"HEAD\", \"GET\", \"OPTIONS\"], conn, state}\n  end\n\n  def content_types_provided(conn, state) do\n    {[{\"text/html\", :to_html}], conn, state}\n  end\n\n  def to_html(conn, state) do\n    {\"Hello #{state}\", conn, state}\n  end\nend\n```\n\n### Router\n\nCreate a file at `lib/my_app/router.ex` to hold the Router:\n\n```elixir\ndefmodule MyApp.Router do\n  use PlugRest.Router\n  use Plug.ErrorHandler\n\n  plug :match\n  plug :dispatch\n\n  resource \"/hello\", MyApp.HelloResource, \"World\"\n\n  match \"/match\" do\n    send_resp(conn, 200, \"Match\")\n  end\nend\n```\n\nThe PlugRest Router adds a `resource` macro which accepts a URL path,\na Plug module, and its options. If the module is a `PlugRest.Resource`,\nit will begin executing the REST callbacks, passing in any initial\n`state` given to it.\n\nThe router contains a plug pipeline and requires two plugs: `match`\nand `dispatch`. You can add custom plugs into this pipeline.\n\nYou can also use the `match` macros from `Plug.Router`.\nThis provides an escape hatch to bypass the REST mechanism for a\nparticular route and send a Plug response manually.\n\nIf no routes match, PlugRest will send a response with a `404` status\ncode to the client automatically.\n\n#### Dynamic path segments\n\nRouter paths can have segments that match URLs dynamically:\n\n```elixir\nresource \"/users/:id\", MyApp.UserResource\n```\n\nThe path parameters can be accessed in your resource in `conn.params`:\n\n```elixir\ndef to_html(%{params: params} = conn, state) do\n  user_id = params[\"id\"]\n  {\"Hello #{user_id}\", conn, state}\nend\n```\n\n### Application\n\nFinally, add the Router to your supervision tree by editing\n`lib/my_app/application.ex`:\n\n```elixir\nchildren = [\n  {Plug.Cowboy, scheme: :http, plug: MyApp.Router, options: [port: 4001]}\n]\n```\n\n### Running\n\nCompile your application and then run it:\n\n```sh\n$ mix compile\n$ iex -S mix\n```\n\nYour server will be running and the resource will be available at\n`http://localhost:4001/hello`.\n\n### Tasks\n\nYou can generate a new PlugRest resource (with all of the callbacks\nimplemented) by using a Mix task:\n\n```sh\n$ mix plug_rest.gen.resource UserResource\n```\n\nThe task will create a resource at `lib/my_app/resources/user_resource.ex`.\n\n### Callbacks\n\nThe `PlugRest.Resource` module defines dozens of callbacks that offer\na declarative strategy for defining a resource's behavior. Implement\nyour desired callbacks and let this library *do the REST*, including\nreturning the appropriate response headers and status code.\n\nEach callback takes two arguments:\n\n* `conn` - a `%Plug.Conn{}` struct; use this to fetch details about\n  the request (see the Plug docs for more info)\n* `state` - the state of the Resource; use this to store any data that\n  should be available to subsequent callbacks\n\nEach callback must return a three-element tuple of the form `{value,\nconn, state}`. All callbacks are optional, and will be given default\nvalues if you do not define them. Some of the most common and useful\ncallbacks are shown below with their defaults:\n\n      allowed_methods        : [\"GET\", \"HEAD\", \"OPTIONS\"]\n      content_types_accepted : none\n      content_types_provided : [{{\"text/html\"}, :to_html}]\n      expires                : nil\n      forbidden              : false\n      generate_etag          : nil\n      is_authorized          : true\n      last_modified          : nil\n      malformed_request      : false\n      moved_permanently      : false\n      moved_temporarily      : false\n      resource_exists        : true\n\nThe [docs](https://hexdocs.pm/plug_rest/PlugRest.Resource.html)\nfor `PlugRest.Resource` list all of the supported REST callbacks and\ntheir default values.\n\n### Content Negotiation\n\n#### Content Types Provided\n\nYou can return representations of your resource in different formats\nby implementing the `content_types_provided` callback, which pairs\neach content-type with a handler function:\n\n```elixir\ndef content_types_provided(conn, state) do\n  {[{\"text/html\", :to_html},\n    {\"application/json\", :to_json}], conn, state}\nend\n\ndef to_html(conn, state) do\n  {\"\u003ch1\u003eHello\u003c/h1\u003e\", conn, state}\nend\n\ndef to_json(conn, state) do\n  {\"{\\\"title\\\": \\\"Hello\\\"}\", conn, state}\nend\n```\n\n#### Content Types Accepted\n\nSimilarly, you can accept different media types from clients by\nimplementing the `content_types_accepted` callback:\n\n```elixir\ndef content_types_accepted(conn, state) do\n  {[{\"mixed/multipart\", :from_multipart},\n    {\"application/json\", :from_json}], conn, state}\n    end\n\ndef from_multipart(conn, state) do\n  # fetch or read the request body params, update the database, etc.\n  {true, conn, state}\nend\n\ndef from_json(conn, state) do\n  {true, conn, state}\nend\n```\n\nThe content handler functions you implement can return either `true`,\n`{true, URL}` (for redirects), or `false` (for errors). Don't forget\nto add \"POST\", \"PUT\", and/or \"PATCH\" to your resource's list of\n`allowed_methods`.\n\nConsult the `Plug.Conn` and `Plug.Parsers` docs for information on\nparsing and reading the request body params.\n\n### Testing\n\nUse `Plug.Test` to help verify your resource's responses to separate\nrequests. Create a file at `test/resources/hello_resource_test.exs` to\nhold your test:\n\n```elixir\ndefmodule MyApp.HelloResourceTest do\n  use ExUnit.Case\n  use Plug.Test\n\n  alias MyApp.Router\n\n  test \"get hello resource\" do\n    conn = conn(:get, \"/hello\")\n\n    conn = Router.call(conn, [])\n\n    assert conn.status == 200\n    assert conn.resp_body == \"Hello world\"\n  end\nend\n```\n\nRun the test with:\n\n```sh\n$ mix test\n```\n\n### Debugging\n\nTo help debug your app during development, add `Plug.Debugger` to the\ntop of the router, before `use Plug.ErrorHandler`:\n\n```elixir\ndefmodule MyApp.Router do\n  use PlugRest.Router\n\n  if Mix.env == :dev do\n    use Plug.Debugger, otp_app: :my_app\n  end\n\n  use Plug.ErrorHandler\n\n  # ...\nend\n```\n\n### Error Handling\n\nBy adding `use Plug.ErrorHandler` to your router, you will ensure it\nreturns correct HTTP status codes when plugs raise exceptions. To set\na custom error response, add the `handle_errors/2` callback to your\nrouter:\n\n```elixir\ndefp handle_errors(conn, %{kind: _kind, reason: _reason, stack: _stack}) do\n  send_resp(conn, conn.status, \"Something went wrong\")\nend\n```\n\n\n### Phoenix\n\nYou can use PlugRest's router and resources in your Phoenix app like\nany other plug by forwarding requests to them:\n\n```elixir\nforward \"/rest\", HelloPhoenix.RestRouter\n```\n\nTo get the `resource` macro directly in your Phoenix router, use\n[PhoenixRest](https://github.com/christopheradams/phoenix_rest/).\n\n\n## Information\n\nThe Cowboy documentation has more details on the REST protocol:\n\n* [REST principles](http://ninenines.eu/docs/en/cowboy/2.0/guide/rest_principles/)\n* [REST flowcharts](http://ninenines.eu/docs/en/cowboy/2.0/guide/rest_flowcharts/)\n* [Designing a resource handler](http://ninenines.eu/docs/en/cowboy/2.0/guide/resource_design/)\n\n### Upgrading\n\nPlugRest is still in an initial development phase. Expect breaking\nchanges at least in each minor version.\n\nSee the [CHANGELOG](CHANGELOG.md) for more information.\n\n\n## License\n\nPlugRest copyright \u0026copy; 2016, [Christopher Adams](https://github.com/christopheradams)\n\ncowboy_rest copyright \u0026copy; 2011-2014, Loïc Hoguin \u003cessen@ninenines.eu\u003e\n\nPlug copyright \u0026copy; 2013 Plataformatec.\n\nPlugRest source code is licensed under the [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0).\n","funding_links":[],"categories":["REST and API"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fchristopheradams%2Fplug_rest","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fchristopheradams%2Fplug_rest","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fchristopheradams%2Fplug_rest/lists"}