Flavio Milan
Flavio Milan

Reputation: 487

Versioning API in Elixir Plug

I have two modules: lib/endpoints/v1/base.ex and lib/endpoints/v2/base.ex.

lib/endpoints/v1/base.ex

defmodule Http.Endpoints.V1.Base do
  require Logger
  use Plug.Router

  plug(:match)
  plug(:dispatch)
  plug(Plug.Logger)
  plug(Plug.Parsers, parsers: [:json], json_decoder: Poison)

  get "/v1/ping" do
    send_resp(conn, 200, "pong!")
  end
end

lib/endpoints/v2/base.ex

defmodule Http.Endpoints.V2.Base do
  require Logger
  use Plug.Router

  plug(:match)
  plug(:dispatch)
  plug(Plug.Logger)
  plug(Plug.Parsers, parsers: [:json], json_decoder: Poison)

  get "/v2/ping" do
    send_resp(conn, 200, "pong! 2")
  end
end

My endpoint works correctly if I put in my applications.ex the children

Plug.Cowboy.child_spec(scheme: :http, plug: Http.Endpoints.V1.Base, options: [port: Application.get_env(:http, :port)])

But I would like my application starts all endpoints versions.

I tried to create lib/endpoints.ex with require Http.Endpoints.V1.Base and require Http.Endpoints.V2.Base and changed my applications.ex but it did not work.

Upvotes: 3

Views: 275

Answers (2)

Mike Quinlan
Mike Quinlan

Reputation: 2882

You can forward to other routers from your endpoints file. Here is the documentation for the forward/2 function: https://hexdocs.pm/plug/Plug.Router.html#forward/2

Basically you create 2 routers for v1 & v2:

defmodule MyAppWeb.V2.Router do
  use Plug.Router

  plug :match
  plug :dispatch

  get "/ping" do
    send_resp(conn, 200, "OK")
  end
end

and

defmodule MyAppWeb.V1.Router do
  use Plug.Router

  plug :match
  plug :dispatch

  get "/ping" do
    send_resp(conn, 200, "OK")
  end
end

Then in your endpoint you can add all the common functionality and forward to your versioned routes like so:

defmodule MyAppWeb.Endpoint do
  require Logger
  use Plug.Router

  plug :match
  plug :dispatch
  plug Plug.Logger
  plug Plug.Parsers, parsers: [:json], json_decoder: Poison

  # Forwarding
  forward "/v2", to: MyApp.V2.Router
  forward "/v1", to: MyApp.V1.Router

  # You should put a catch-all here
  match _ do
    send_resp(conn, 404, "Not Found")
  end
end

Then in your application.ex file, mount your endpoint as you did before. However, at this point you should be able to ping both /v1/ping and /v2/ping from the same port.

Cheers!

Upvotes: 6

7stud
7stud

Reputation: 48599

My endpoint works correctly if I put in my applications.ex the children

Plug.Cowboy.child_spec(
   scheme: :http, 
   plug: Http.Endpoints.V1.Base, 
   options: [port: Application.get_env(:http, :port)]
)

The modern way to specify that is:

{Plug.Cowboy, [[ 
      scheme: :http, 
      plug: Http.Endpoints.V1.Base, 
      options: [port: Application.get_env(:http, :port)]
    ]]
}

See: https://hexdocs.pm/elixir/Supervisor.html#module-child_spec-1

Although, to me it seems easier to call child_spec() directly, rather than try to figure how many brackets you need around the argument.

Upvotes: 1

Related Questions