I’m currently working on a project which has an API backend and a JS frontend which consumes that API. Both parts are built with Rails and must be served from the same domain and port because of the same origin policy.
The API will be served from a sub-directory like so:
- http://example.com - serves the JS app
- http://example.com/api - serves the API
It’s pretty trivial to set this up with nginx, but developing locally is a bit trickier.
Running both apps with rails server
will put them on different ports and the JS app won’t be able to communicate with the API.
We could setup a local nginx config on our development machines, but this makes it harder to setup breakpoints in ruby-debug amongst other things.
Rails apps are just Rack apps, so my first thought was to create a config.ru which mounts both Rails apps:
1 2 3 4 5 6 7 8 9 10 11 12 |
|
This raises an error saying You cannot have more than one Rails::Application
so that’s that idea out the window.
We could turn the API into a Rails Engine and mount that inside the other app, but we really want these two apps to be completely separate and not have to know about each other outside of the documented API.
The obvious solution is to use a proxy to let us run each Rails app independently and have the proxy forward requests to each one depending on the URL.
The simplest thing I could think to setup a proxy server was to use Rack::Proxy and about 5 minutes later I had a working solution:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
|
Pretty simple, we just rewrite the HTTP_HOST depending on whether or not the requested path starts with “/api”.
Now we fire up the frontend and backend Rails apps on port 3000 and 3001 respectively, run the proxy on another port and point the browser there.
Using rackup config.ru
worked fine, but when I tried to run the proxy using passenger-standalone I got the following error:
1 2 3 4 5 6 7 8 9 10 11 |
|
This is because passenger-standalone sets up the nginx config expecting there to be a public
directory, so I just created an empty one and everything worked fine.
With this setup we can also trivially switch the API host to point to production, letting us develop the frontend against the production API should we want to test the UI with live data.