There are many ways to combine a single page AngularJS app with Rails, and many things to consider.
I will list a few methods and describe why we prefer a particular method over other options.
- Use gulp / grunt
- Don't use assets pipeline to serve client code
- Don't mix server-template code in client-side templates
Our method is to build the client completely separately from the server using gulp (or grunt). This way, AngularJS developers can work in the environment that they are comfortable with.
You can even work with a staging server that's populated with data, so you don't need Ruby installed on your machine to run the client (we use node sass for scss files). Also, it ensures that you don't combine server-rendered templates with client templates (which can cause a huge mess).
The other approach would be to use Rails assets pipeline (sprockets).
The main reason for choosing gulp / grunt over sprockets is that it's built for developing client side apps.
If you are originally an RoR developer, you might be tempted to combine server-rendered templates with client templates. This is a huge mistake that will cause an unreadable code structure and routing system, as well as duplicate code. The slight performance boost you'll earn is not worth it.
The server should be developed as a pure JSON API server (as if you're building an API server for a mobile app).
In case you want to use an admin GEM (e.g. activeadmin) - you'll still need the view layer of the Rails server.
We usually don't use versioning in the API if it just serves the SPA.
If you're also supporting a mobile app, you need versioning. In this case, the web client will use the latest API version, or a separate unversioned API.
Testing the server code is best done using unit testing, and system tests (testing JSON responses). Of course, you can also do some manual testing using the Chrome POSTMAN extension.
- Gulp task to build a client distribution package
- Gulp task should change server base URL automatically to '/'
- Put the generated files in the Rails public folder
- If your main html file is not called index.html, you'll have to use routes to render it
- The Rails server on production will serve the static files (The minified CSS & JS files and HTML templates).
Make sure you precompile the required vendor components files (JS, CSS, images, fonts, etc.) and include that in the public folder as well.
You can use angular-kick which has the above tasks defined, or see how it's implemented there if it doesn't exactly fit your needs.
Before deploying to production, you can do a sanity check on the rails server with the precompiled assets by running it locally and testing it on the browser.
Done best with tokens, not cookies.
Every attempt we had on using devise and warden built-in authentication system using cookie-based sessions lead to a disaster.
Maintaining the following division of labor between client and server keeps things clean and logical:
- The client is in charge of managing the session:
- Storing and removing the token from the cookies
- Redirect if unauthorized
- Refreshing the current user and checking if it is authorized
- The server is in charge of security:
- Expiring the token on certain events (change password, periodically, suspicious activity, etc.)
- Not serving data to unauthenticated / unauthorized users
In development, separate the client repo from the server repo.
Keeping them separate allows work to be done on the client and server independent of one another.
Also, integrating the CI is easier this way.
In production, there are 2 main approaches.
We prefer the first one:
- Commit the compiled client code in the server's GIT repo:
- Quick rollback & deployment
- No surprises (you can manually test the compiled version before deploying)
- Slight disadvantage - GIT will bloat (even minor changes will be viewed as major change in minified assets)
- Automatically compile code upon deployment:
- Very error prone and risky
- Deployment machine needs to know which client version to compile with which server version - hence harder to rollback and is more risky.
- Prone to bower components versions mishaps if you don't use absolute versions
- Imagine if the node-sass version on localhost is different then deployment server
- Will compile code even when it's not necessary (e.g. when fixing a pure API bug)
- Slight advantage - keeps GIT cleaner
Use rack-cors in development / staging to allow cross origin requests.
Make sure you don't enable it on production (unless you really do need to allow external domains to access your API).
To allow HTML5 mode routes (paths without the '#') - you'll need to support it in the server routing (in the initial page load).
The general approach is to define a wildcard rule using NGinx / Apache, to serve the index.html for all client routes.
Make sure all server routes are either prefixed or scoped to a subdomain (e.g. 'api', 'admin'), and that they don't collide with client routes.
- Given you have a client route called '/users'.
- The user goes to /users, and refreshes the page
- It first reaches your web server, which in turn will serve index.html.
- The browser will render the angular app.
- Angular router will recognize the route and show the correct page to the user.
Make sure you handle 404s in the client app for unrecognized routes.
In rare cases where you don't have access to the nginx config, you can define a wildcard rule in Rails routes. This is not recommended as it will slow down your application significantly.