Retroactively versioning API endpoints, how to keep retrocompatibility without code duplication in router?

Hello!

I have a Phoenix application with a bunch of routes, looking like this:

  scope "/", MyServiceWeb do
    pipe_through :api

    get "/foo", FooController, :foo
    get "/bar", BarController, :bar
    [ many more ]
  end

I am tasked with adding versioning to this API, for which we went with a path prefix approach, but the old endpoints have to be kept to avoid having to change all other services that use this API.

Having changed the code structure to reflect this, I updated the router to look like this:

  scope "/", MyServiceWeb.V1 do  # Notice the V1 here
    pipe_through :api

    get "/foo", FooController, :foo
    get "/bar", BarController, :bar
    [ many more ]
  end

  scope "/v1", MyServiceWeb.V1 do
    pipe_through :api

    get "/foo", FooController, :foo
    get "/bar", BarController, :bar
    [ many more ]
  end

This works but there’s the issue of duplication that I would like to avoid, if at all possible. I would like to share the route definitions between the two scopes, something like this:

  scope ["/", "/v1"], MyServiceWeb.V1 do

Is this doable someway?

You could write a little macro that inserts the route definitions. You would still have the two scope blocks but all they do then is to call the macro. Not entirely perfect but better than defining everything twice.

EDIT:

You could also have one router per version and then use forward/4 inside the scope blocks to forward to that router. That way you don’t have to deal with macros and can handle each API version in it’s own router for better code organization.

2 Likes

Thank you, the forward/4 suggestion did it, but I didn’t define additional Routers. Instead, I added a clause forward("/v1", Router) and the end of the / scope and it seems to work allright for our purposes.

But won’t that also make /v1/v1/v1/v1... possible since it is referencing itself if I understand correctly?

It’s only duplication right now - presumably you’re adding versioning in preparation for divergent changes. If you leave the existing routes as-is and add new ones under the version prefix, you can be 100% sure you didn’t break the original contract.

1 Like