
Bootstrapping API project with Phoenix 1.3
Here’s how to create a minimal Phoenix 1.3 setup for API server without HTML, Brunch, Gettext, channels and sessions.
Update: I’ve edited the article to include a clearer and more thorough explanation of reasoning behind removal of Gettext and a session plug from my lightweight API project.
Installing Phoenix release candidate
At the time of this write-up, Phoenix 1.3 is in RC0 release phase. So let’s start with an explanation of how to install such non-stable release.
Installation of release candidate (or any arbitrary) version of Phoenix (such as the 1.3.0-rc.0 which this article is based upon) is only a tiny bit more complicated than what you may have come to expect from current stable path.
First, you may want to uninstall the existing stable release archive (if you previously installed it):
mix archive.uninstall phoenix_new.ez
Then you should visit the Phoenix archives page and download the version that you’re interested in. Finally, as advertised by Phoenix installation guide, you can install the downloaded package:
mix archive.install ~/Downloads/phx_new-1.3.0-rc.0.ez
That’s it. You should now be able to run mix phx.new
targeting new version.
Creating new API project
You can invoke mix help phx.new
to see the option list. In my case, I’ve run:
mix phx.new example-api --module ExampleAPI --app example_api --no-brunch --no-html
This gets us rid of phoenix_html
and static Brunch setup. But we won’t stop with that.
Extra luggage
Despite using phx.new
option flags to the fullest, the newly created project still includes many parts that may not be needed for lightweight API project. Let’s get rid of those now.
Removing Gettext
If you assume that the API messages (such as errors) are not supposed to be consumed by end users, then the initial Gettext setup, as convenient as it is, may be quite useless.
In my case, I’ve decided that my API will only return error codes (via the code
property of JSON API error objects) and it’ll be up to the front-end client (that lives in separate repo) to translate them for the user. This way, only the client repo will have to bother with translations and I won’t have two separate Gettext translation sources to maintain.
First, delete the following files:
- lib/example_api/web/gettext.ex
- priv/gettext/errors.pot
- priv/gettext/en/LC_MESSAGES/errors.po
Then remove the following code references:
gettext
package fromdeps
function in mix.exsgettext
compiler fromcompilers
key inproject
function in mix.exstranslate_error
function fromExampleAPI.Web.ErrorHelpers
- all calls to
import ExampleAPI.Web.Gettext
in lib/example_api/web/web.ex
Finally, unlock the gettext
dependency from mix.lock:
mix deps.unlock gettext
Removing channels and PubSub
I imagine that despite the increasing impact and undeniable convenience of WebSockets technology, many API servers still don’t (yet) need anything but a classical HTTP flow. If that is your case, you may want to remove the channel boilerplate as well. You can always take a peak into your repo’s Git history and restore them when the time comes for their great return.
PubSub is, to quote Phoenix documentation, a “nuts and bolts of organizing Channel communication”. Since we don’t need channels, we shouldn’t need those nuts and bolts either.
First, delete the following files:
- lib/example_api/web/channels/user_socket.ex
- test/support/channel_case.ex
Then remove the following code references:
socket
clause in lib/example_api/web/endpoint.exphoenix_pubsub
package fromdeps
function in mix.exspubsub
key inExampleAPI.Web.Endpoint
config clause in config/config.exs
Unfortunately, it’s not possible to unlock the phoenix_pubsub
package entirely as it’s a dependency of Phoenix itself, but at least the project code makes it clear about not being directly dependent on the package or channels functionality.
Removing unneeded plugs
As the final step, let’s take a closer look at the plugs hooked by default into the endpoint (defined in lib/example_api/web/endpoint.ex). If you do decide that any of plugs found there may not be useful for you, just remove the corresponding plug
clause and enjoy having the thinnest middleware stack possible.
The default RequestId
, Logger
, MethodOverride
and Head
plugs all seem to fit the typical API server use case. The Parsers
plug also seems to be configured with reasonable and flexible defaults, allowing the API to consume params from URL, multipart body and JSON body.
This leaves us with the following strong candidates for removal.
Plug.Static
This plug serves static assets from priv/static
when the server is running. Note that passing the --no-brunch
option passed to mix phx.new
got us rid of the Brunch setup, but Phoenix assumes we may still want to serve static files that we assemble with means other than Brunch. This may not be the case if the API is just an API and all the assets are bundled directly with the front-end project or otherwise out of scope.
Plug.Session
This one gives us an out-of-the-box per-client session store that lives on the client side in a cookie and that can be assumed to be secure and impossible to tamper with from the client side thanks to the server-side salt and optional encryption. While it’s convenient for traditional full-stack web applications, you may want to rethink its use with API project for the following reasons:
- APIs often live on different domains than their clients which may be problematic for a cookie-based session store, as cookies are designed as a per-domain resources
Solution proposal: Cross-Origin Resource Sharing (CORS) - Cookies are also prone to CSRF attacks and the most common protection against those - the CSRF token - is tricky and unnatural to apply for APIs
Solution proposal: LocalStorage, SessionStorage - Each web framework’s session is implemented in a non-standard way, which makes it hard to share with other services if your API grows and spans multiple servers and technologies
Solution proposal: JSON Web Tokens - Concept of session as “an ability to continue a conversation with specific user’s browser through a series of HTTP calls” is obsolete in a world with web sockets
Solution proposal: WebSockets (Phoenix channels)
If you stumble upon a use case where session seems to be necessary for your API, you may need to rethink your architecture and reconsider modern solutions to solve the above limitations.
Summary
With the above setup, you get the most up-to-date Phoenix project, custom-tailored for a thin, blazing fast API server with just the components you really do need.
Although the mix phx.new
task has some convenient customization options, sometimes you may want to go a step further and remove some more extra luggage. Fortunately, the structure of Phoenix projects is very explicit, boilerplate-free and straightforward (even more so in Phoenix 1.3 with the web
directory removed) and it’s not too hard to customize it.
Now, jump in and create your first API resource, perhaps by invoking mix phx.gen.json
, because in Phoenix 1.3 this is where the real context-driven fun starts.