Sails.js is a Node based web framework that you can use to easily generate your back-end. If you're not familiar with Sails check out my Sails.js Tutorial before we begin. We'll be looking at using routes with Sails.js as well as how it works with the SANE stack. SANE Stack it's a tool that helps generate both your front-end using Sails and your back-end using Ember.js.

In case you didn't know the router is the mechanism for mapping URLs to your controllers and views. The routes are rules that helps Sails figure out what to do when it receives an incoming connection. If you need some more information about anything we talk about here, check out the official Sails.js routes documentation. It goes into a lot more detail.

Custom Routes

There are two types of routes in Sails, custom and automatic. Custom are routes that we explicitly define in the config/routes folder.

Each route consists of an address and target. You can optionally specify the specific HTTP method if needed. In the target you define what to do with the request. You can pass it to a controller, or view, or even trigger an action.

'GET /foo/bar': 'FooController.bar'  
^^^address^^^^  ^^^^^^target^^^^^^^
//action
'GET /foo/bar': {controller: "Foo", action: "myGoAction"},  

The example above will take any request to /foo/bar and redirect it to controller foo to the action myGoAction.

We can test that out real quickly. This is assuming you have Node, and Sails.js installed using the command line tool.

$ sails new RouteTest
$ cd RouteTest
$ sails generate controller foo

This will create a new sails project. Next we'll generate a controller named foo. We'll need to edit the Foo controller and add the action.

// api/controllers/FooController.js
module.exports = {  
        myGoAction: function(req, res) {
          res.send('hello world');
        }
};

We'll add the route

// config/routes.js
...
 '/': {
    view: 'homepage'
  },
  'GET /foo/bar': {controller: "Foo", action: "myGoAction"}
...

This tells Sails to redirect to the 'homepage' if the user goes to the root of the site '/'. On the other hand if the user browses to 'http://localhost:1337/foo/bar' it send them to the myGoAction in the foo controller. Should be simple enough.

If needed you can also use wildcards that will match any route as you can see below.

'/*'  

There is a lot more you can do with custom routes, here are a few more options you can add to the target.

  • skipAssets
  • skipRegex
  • locals
  • cors
  • populate
  • skip, limit, sort, where

Find more information about these in the Route target options section of the Sails.js website.

Automatic Routes

Automatic routes are a little easier to define then custom routes. They are simply any route that doesn't match a custom route. In fact Sails will bind many of these routes for you automatically. There are a few types of automatic routes.

  • Blueprint routes - the built in blueprints provide your controllers and models with a full REST API.
  • Assets - images, Javascript, and stylesheets
  • CSRF - CSRF Tokens

So for example in the sample application we created earlier we could create a new controller called testr.

$ sails generate controller testr

Now for the testr controller itself.

// api/controllers/TestrController.js

module.exports = {  
        hi: function(req, res) {
          res.send('hello from testr');
        }

};

Due to the default blueprint action route we actually don't need to explicitly set a route. Any GET request to /:controllerIdentity/:nameOfAction will trigger the action. In this example all we need to do is browse to 'http://localhost:1337/testr/hi' and we'll see our 'hello form testr' message. We can do the same thing for models as well.

URL Slugs

A common use case is explicitly naming routes designed for vanity URLS. For example you might use the title of a blog as the slug name you want to display at the end of your URL. Another example is the name used for repositories in Github. Here's an example of this.

'get /:account/:repo': {  
controller: 'RepoController',  
action: 'show',  
skipAssets: true  
}

The :account and :repo are special parameters that can be accessed in your RepoController with req.param('account') and req.param('repo'). At this point you could then pass it to a local view as locals. We won't get into this too much but it's good to know that it exists.

SANE Stack Default Configuration

So how does the SANE Stack configure your routes? Let's take a peek.

$ sane new TestSaneRoute

If we take a look inside the config/routes.js file we'll see this.

// server/config/routes.js
...
 //This automatically serves all routes, apart from /api/** routes to ember
  //(which will be initialized in assets/index.html). This route needs to be
  //at the very bottom if you want to server other routes through Sails, because they are matched in order
  '/*': { controller: 'App', action: 'serve', skipAssets: true, skipRegex: /^\/api\/.*$/ }

Essentially all routes are being redirected to the App controller's serve action. SkipRegex is telling Sails to skip anything that matches the /api/ route. This is the beauty of SANE and Sails.

If we look at the AppController we'll see this.

// server/api/controllers/AppController.js
...
  serve: function(req, res) {
    var emberApp = __dirname + '/../../assets/index.html';
    fs.exists(emberApp, function (exists) {
      if (!exists) {
        return res.notFound('The requested file does not exist.');
      }

      fs.createReadStream(emberApp).pipe(res);
    });
  }
...

This serve action simply points to a index.html file in the assets folder. When you're ready to deploy you'll specify the server/assets folder as the destination of your ember build.

Creating a Second Ember Project Route

For fun let's say you wanted to have two ember applications served by one Sails application. Or perhaps you wanted one to be staging and the other to be production.

// server/config/routes.js
...
'/staging': { controller: 'App', action: 'staging', skipAssets: true, skipRegex: /^\/api\/.*$/ },  
...

This is really simple, we just added a new route for /staging.

Next we'll go into our App controller and add a new staging action.

// server/api/controllers/AppController.js
staging: function(req, res) {  
    var emberApp = __dirname + '/../../assets/staging.html';
    fs.exists(emberApp, function (exists) {
      if (!exists) {
        return res.notFound('The requested file does not exist.');
      }

      fs.createReadStream(emberApp).pipe(res);
    });
  },
...

This is the same code we had before except it's pointing towards staging. For this demo we'll use the same folder as our normal application. How can we get Ember to build to staging.html and not index.html though?

// client/Brocfile.js
var EmberApp = require('ember-cli/lib/broccoli/ember-app');

var app = new EmberApp({  
     outputPaths: {
        app: {
            html: 'staging.html'
        }
    },
    fingerprint: {
        prepend: 'http://localhost:1337/'
    }

});
...

All I did here was set the output path to staging.html and I created a fingerprint that prepended all my assets with this URL. For the sake of this demo I hardcoded the path in.

Last thing I need to do is tell Ember that the root URL will be in /staging not /.

// client/config/environment.js
...
   baseURL: '/staging',
...

Now we can deploy our ember application to the assets folder in our sails server and everything should work.

$ ember build --environment=production --output-path=../server/assets/

That's it. If you run sane up in the root folder or sails lift in the server folder you should see your Ember application running.

Next Up

So what do you think about routing in Sails? Does it make sense? If you have any questions below leave a comment below!

Image Credit

The Best Online Courses on Learning JavaScript

Looking to take your learning a step further with online courses? Are you wanting to jump into Sails but not up to speed on JavaScript? I recommend the following courses from Udemy. This is an affiliate link, which means you get help support this blog at the same time!

Beginning ES6, The Next Generation of JavaScript

Comprehensive JavaScript Programming