Menu Search
Jump to the content X X
Smashing Conf New York

We use ad-blockers as well, you know. We gotta keep those servers running though. Did you know that we publish useful books and run friendly conferences — crafted for pros like yourself? E.g. upcoming SmashingConf Barcelona, dedicated to smart front-end techniques and design patterns.

Sailing With Sails.js: An MVC-style Framework For Node.js

I had been doing server-side programming with Symfony 2 and PHP for at least three years before I started to see some productivity problems with it. Don’t get me wrong, I like Symfony quite a lot: It’s a mature, elegant and professional framework. But I’ve realized that too much of my precious time is spent not on the business logic of the application itself, but on supporting the architecture of the framework.

I don’t think I’ll surprise anyone by saying that we live in a fast-paced world. The whole startup movement is a constant reminder to us that, in order to achieve success, we need to be able to test our ideas as quickly as possible. The faster we can iterate on our ideas, the faster we can reach customers with our solutions, and the better our chances of getting a product-market fit before our competitors do or before we exceed our limited budget. And in order to do so, we need instruments suitable to this type of work.

Sailing With Sails.js

If you’re developing a complex application with three hundred pages of documentation for some big corporate client and you know most of its details from the start, then Symfony 2 or some enterprise Java framework would probably be the best tool for the job. However, if you are a startup developer or you just want to test some of your ideas quickly without compromising the overall quality of the application, then Sails1 (or Sails.js) is a very interesting candidate to consider.

I’ll neither confirm nor deny that Sails is being developed by a giant smart octopus, but I will do my best to guide you from the humble ensign to being the confident captain of your own ship!

Introduction Link

Sails is a comprehensive MVC-style framework for Node.js specifically designed for rapid development of server-side applications in JavaScript. It’s robust service-oriented architecture provides different types of components you can use to neatly organize code and separate responsibilities. And if you’re disciplined, then developing an enterprise-level application with it is even possible.

Written in JavaScript, Sails gives you the additional benefit of being able to share your code between the server and client. This could be very helpful, for example, for implementing data validation where you need to have the same validation rules both in the client and on the server. Also, with Sails you need to master only one programming language, instead of several.

One major concept of the framework is that it wraps a stack of loosely coupled components. Almost every aspect of the system is customizable: You can add, remove or replace most of the core components without compromising the framework’s overall stability. In other words, if you need to get a job done as quickly as possible, Sails will help you by providing robust built-in components with sensible defaults; however, if you’d like to create a fully customized solution, Sails will not stand in your way either. If you are already familiar with the philosophy behind the Node.js development community, then you will get what I mean; if not, then you will understand it during the course of this article.

Under the hood, Sails contains probably the most well-known web framework for Node.js, Express. Express2 is a very simple, basic framework. It provides the mere bones for your web development needs. To implement a serious web app with it, you will need to find and integrate a bunch of third-party components yourself. Also, Express doesn’t really care about the structure of code or a project’s file system, so you will need to manage that yourself and come up with a sensible structure. That’s where Sails comes to the rescue. Built on top of Express’ robust design, it provides all required components out of the box and gives developer a well-thought-out organization for their code and project files. With Sails, you will be able to start development with the built-in and documented tools.

I believe the best way to understand something is to get ahold of it and explore it firsthand. So, enough talking. Let’s grab the code and create our first local project!

Getting Started Link

I’ll start with a clean slate. Let’s begin by installing all of the requirements and the latest version of Sails itself.

I’m using Ubuntu Linux, so all commands will be presented for this OS. Please adjust them according to your working environment.

Install Node.js Link

To install the latest version of Node.js on your Ubuntu machine from NodeSource Node.js Binary Distributions3, just run these three commands:

# Make sure cURL is available in the system
sudo apt-get install -y curl

# Adding NodeSource repository to the system via provided script
curl -sL | sudo bash -

# Actually installing the Node.js from the NodeSource repository
sudo apt-get install -y nodejs

You can confirm that Node.js has been successfully installed by using this command:

node --version

It should output something like v0.12.4.

Note: If you’re not using Ubuntu, then please see Joyent’s instructions on installing Node.js on different platforms4.

Install Sails Link

The following command will install Sails globally:

sudo npm -g install sails

You can test whether the framework was installed with this command:

sails --version

It should output the number of the latest stable version of Sails.

Create a Project Link

Let’s create the test project that we will be experimenting with:

sails new sails-introduction
cd ./sails-introduction

Start a Project Link

The most interesting aspect of Node.js is that the application doesn’t require an external web server in order to operate. In the world of Node.js, the application and the web server are the same thing. When you run your Sails application, it binds to the given port and listens for HTTP requests. All requests are handled in the same OS process sequentially by your application. (In contrast, Apache will spawn multiple sub-processes or threads, and each request will have its own context space.)

So, how can your application serve multiple requests without those requests noticeably blocking each other? The key to this is a major feature of the Node.js: asynchronosity. All heavy operations, such as I/O and database access, are performed in a non-blocking asynchronous fashion. Every asynchronous method allows you to specify a callback function, which is activated as soon as the requested operation completes. The result of the operation (or error description) gets passed to your callback function. That way, your application can delegate all heavy work and continue with its own business, returning later to collect the results and continue where it left off.

Note: The more convenient and modern approach is to use promises instead of callback functions, but that is beyond the scope of this article. Please see Jake Archibald’s article5 for more insight into the topic.

Let’s start our project to see that everything is working fine. Just run the following:

sails lift

Sails will initialize the application, bind to the configured port and start to listen for HTTP requests.

Note: When your application is lifted, the terminal window will be in blocked state. You can press Control + C to terminate the application and return to the command prompt.

Now, you will be able to open the default application in your favorite browser by visiting http://localhost:1337/.

At this point, the default page should load correctly.

Diving Into Sails Link

Now, let’s dissect our project to understand what makes it tick!

Sails is an MVC framework, so starting from these components to see what glues them all together makes sense.

The entry point to our application is the app.js file, which lies at the root of the project. You could call it a front controller if you’d like; however, it wouldn’t make sense to edit its content. All it does is require top-level dependencies and give control to Sails itself. After that, all of the magic happens in the framework.

Routing Component Link

When Sails receives an HTTP request, it actually uses its router component to find the controller responsible for generating the response. Router matching can be controlled through a special configuration file located at config/routes.js. If you open this file now, you will see that it contains only a single entry:

module.exports.routes = {
  '/': {
    view: 'homepage'

Note: The default project for Sails contains a lot of comments, which were introduced specifically to speed up project configurations and ease the learning curve. Feel free to remove them if you’d like. No code snippets in this article will contain any built-in comments, in order to preserve space and improve readability.

The left part of the expression, '/', is the URL pattern that tells Sails that the following configuration (the right part) should be used for an index page. The view property of the configuration contains the homepage value, which is the name of the view (the V in MVC).

Views Layer Link

Views are handled by a separate component of the framework. With the help of the “Consolidate” Node.js package, Sails supports at least 31 different templating languages6. So, choose the language that is most suitable for you, your project and your team.

All templates are located in the views directory of your project. You will find there the aforementioned views/homepage.ejs template file that is used to render the home page, and you can play with it if you like.

Note: All templates are rendered dynamically on the server. You will not need to restart Sails in order to refresh any changed templates. All changes will be shown immediately upon the page being refreshed. Try it!

If you look at the homepage.ejs template, you will notice that it’s not complete. It’s missing basic HTML elements, such as the DOCTYPE, html, head body tags. This is on purpose. The most reusable parts of the template are extracted into a separate template file, views/layout.ejs. The name of the layout template is configured in the config/views.js file (look for the layout property). This really helps to keep things DRY. However, if you need to use another layout for some particular page, you can easily override the property dynamically in your controller.

Be advised that this layout configuration works only for the default EJS templating system and will not work with other languages. This is done for the purpose of legacy- and backwards-compatibility. Using the layout functionality provided by the templating language of your choice is recommended. For example, in Twig and Jinja2, you can use the extends expression to extend a parent template and overload required blocks.

Using Custom Views Engine Link

This section demonstrates how to change the views engine that is used to render templates in Sails. This should give you an idea of how easy some parts of Sails can be overridden and customized. I’m going to use the Twig/Jinja2 templating language, because of its flexibility and extensibility. I’ve been using it for at least three years now, and the language has never constrained me in any way. So, I highly recommend you try it.

Note: Twig and Jinja2 are a common family of templating languages with the same core functionality and features. However, each concrete implementation can have its own small differences and flavors. I will be using the Swig library during the course of this article. It provides a concrete implementation of the Twig and Jinja2 templating syntax for Node.js. Please see Swig’s official documentation7 for more details.

As I said earlier, Sails delegates view rendering to the Node.js package called “Consolidate.” This package actually consolidates about 30 different view engines. I will be using the Swig view engine, which implements support for the Twig and Jinja2 templating languages. To use it, I will need to complete a few simple steps:

  1. Define dependencies and install the Swig package: npm install --save swig.
  2. Change Sails’ configuration a bit by editing the config/views.js file. All you need to do is to set the engine property to swig.
  3. Rewrite all templates from EJS format to Twig and Jinja2. Don’t forget to change the extension to .swig!
  4. Reload the Sails server.

Note: In order to see the changes, you will need to reload the application by terminating the server and then lifting it again.

An answer on Stack Overflow gives some hints on how this can be automated.

The content for all of the changed files are provided below for your reference.


module.exports.views = {
  engine: 'swig'


<!DOCTYPE html>
<html lang="en">
    <meta charset="utf-8">
    <title>{{ title|default('The Default Title') }}</title>
    {% block body %}{% endblock %}


{% extends 'layout.swig' %}
{% set title = 'Homepage Title' %}
{% block body %}
  <p>Welcome to the homepage!</p>
{% endblock %}


{% extends 'layout.swig' %}
{% set title = 'Page Not Found' %}
{% block body %}
  <h1>{{ title }}</h1>
{% endblock %}

The content for 403.swig and 500.swig is almost the same as for 404.swig presented above. I will leave it to you to fix the files yourself.

The Controller Link

OK, we’ve looked into the routes and views components, but where is the controller part of the MVC, you ask? Actually, the default Sails project is so simple that it doesn’t require any custom logic. If you open the api/controllers directory, you will see that it’s empty.

As you have guessed, Sails can even run without a controller; the routing configuration may specify the view directly, without the need of a controller. This could be a useful feature for static pages that don’t require any input from the user or any additional processing, as is the case with our home page right now. But let’s fix this shortcoming and introduce some business logic into our route.

Let’s create a new controller for our home page with the following command:

sails generate controller homepage

Sails will generate a file for you, api/controllers/HomepageController.js.

We can open this file and introduce a new action for our home page. I will call it index:

module.exports = {
  index: function (request, response) {
    return response.view('homepage', {
      currentDate: (new Date()).toString()

This simple action will just render our homepage view that we discussed earlier and pass an additional variable to it called currentDate, which will contain the textual presentation of the current date.

Note: The controller’s action is a simple JavaScript function that accepts two arguments: the special request and response objects. These objects correspond directly to the objects provided by the Express framework. Please look at Express’ documentation8 for the API details.

To make Sails actually use our controller, we need to slightly alter the routing configuration in the config/routes.js file:

module.exports.routes = {
  '/': 'HomepageController.index'

Here, we are telling the system to give control of the request to our HomepageController and, specifically, its index action. Now, the controller is responsible for handling the request and generating the response.

Also, don’t forget to add the following line to the views/homepage.swig:

<p>Current date is: {{ currentDate }}</p>

This will render the date string passed from the controller.

Now, reload the server and refresh the page. You should see the changes.

Shadow Routes for Actions Link

By default, Sails will generate implicit routes (also called shadow routes) for every controller’s action. The generated URL will look like /:controller/:action. In our case it will be http://localhost:1337/homepage/index. Although, this feature can be useful, sometimes it’s not desired (such as when you get two URLs for a home page, as in our case).

You can control this behavior by configuring the blueprints component, which can be done in two place. The first and most obvious place is the config/blueprints.js configuration file. You can disable action shadow routes for an entire application by setting the actions option to false:

module.exports.blueprints = {
  actions: false

However, to disable shadow routes for a single controller only, you would set it in the controller itself, api/controllers/HomepageController.js:

module.exports = {
  _config: {
    actions: false
  index: function (request, response) {
    return response.view('homepage', {
      currentDate: (new Date()).toString()

The special _config option of the controller’s module allows you to provide custom configuration for a specific controller.

Model Layer Link

The final part of the MVC paradigm is the model. Sails comes with an advanced ORM/ODM component called Waterline. It was initially designed as a part of the Sails framework and later extracted into a separate Node.js module that can now be used independently.

Waterline provides an abstraction layer that connects your application to a wide variety of databases transparently and seamlessly. The main idea is that you would define your application’s domain model as a set of related entities (JavaScript objects) and that entities are automatically mapped to the database’s underlying tables and/or documents. The interesting aspect of Waterline is that it supports related entities between several databases. For example, you could store users in the PostgreSQL database and related orders in the MongoDB; the abstraction layer would be able to fetch them for you without your even noticing the difference.

Waterline is a pretty big component, and I’m not able to fully cover it in this introductory article, but I will try to give you the taste of it.

Suppose we are creating a simple app to manage contacts. Our app will have just two types of entities: a person and their contact information. The end user would be able to create a person and add multiple contact details for them.

Each separate database system that you would use in your Sails project requires a connection specification. The connections are configured in the config/connections.js file. I’m going to use a special database type called sails-disk. This database adapter is actually built into Sails, and it stores all data in a simple JSON file. This can be very useful when you are starting to design an app and have not yet selected or deployed a real database server.

Let’s now open the config/connections.js file and configure our connection:

module.exports.connections = {
  main: {
    adapter: 'sails-disk'

This short configuration is enough for the sails-disk adapter. However, in a real-world scenario, you would need to provide all of the details required to connect to the database system of your choice — for example, the host name, port number, database name, username and so on.

Also, we would need to configure the model layer to use the specified connection by default for each model that we define. Open the config/models.js file and change its content to the following:

module.exports.models = {
  connection: 'main',
  migrate: 'alter'

The migrate property controls how Sails rebuilds the schema in your underlying database when a model definition changes. When it’s set to alter, Sails will try to update the schema without losing any data each time while the application lifts. The drop could also be a viable option in some cases — then, Sails will just recreate the schema every time the app is lifted. In a production environment, Sails will use the safe option, which will not make any changes to the schema at all. This really helps with protecting the fragile data in the production database. In safe mode, you will need to execute the migration manually. Leaving the migrate option undefined is also possible. In this case, Sails will ask you for an interactive choice every time when a migration is required.

Now, we are ready to define our models. Let’s use Sails’ built-in generator to create model files for us. Just issue these commands:

sails generate model person
sails generate model contact

Sails will create two basic files. Let’s edit them.

First, open the generated api/models/Person.js model and edit it:

module.exports = {
  attributes: {
    firstName: {
      type: 'string',
      size: 128,
      required: true
    lastName: {
      type: 'string',
      size: 128
    contacts: {
      collection: 'Contact',
      via: 'person'

Here, we are defining three fields: firstName, lastName and the contacts collection to hold the contact details. To define a many-to-one relationship between two models, we need to use two special properties. The collection property holds the name of the related model. The via property tells Waterline what field of the related model will be used to map back to this model. Hopefully, this is pretty self-explanatory.

Also, the size property specifies the maximum length of the string in the database column, and the required property specifies which columns may not contain null values.

Let’s edit the second model in api/models/Contact.js file:

module.exports = {
  attributes: {
    type: {
      type: 'string',
      enum: ['mobile', 'work', 'home', 'skype', 'email'],
      required: true,
      size: 16
    value: {
      type: 'string',
      size: 128,
      required: true
    person: {
      model: 'Person',
      required: true

Here, we’re defining yet another three fields. The type field will hold the type of the contact information. It could be a mobile number, a home phone number, a work number, etc. The additional enum property specifies the list of accepted values for this field. The value field holds the corresponding value. And the person field, mentioned earlier, maps the contact model to its parent person model through the special model property.

Note: We are not defining any primary keys or ID fields in our models. Waterline handles that for us automatically. The form of the ID’s value will depend on the database adapter being used because each database system uses different strategies to generate unique keys.

Also, Waterline will create two additional fields for each model, called createdAt and updatedAt. These fields hold the dates of when the entity was created and updated, respectively.

This behavior can be configured through the model options9.

Using Sails’ Console to Test the Models Link

Sails offers a very nice interactive console that immerses the developer in the depth of an application’s context and that runs any JavaScript code we like.

The models are now defined, and we can use Sails’ console to test them and learn some basic APIs of Waterline.

Run the following command to launch Sails’ console:

sails console

After the console has launched, we can type in and execute some JavaScript in the context of our application. This is a quick way to test some aspects of a project.

First, let’s create some entities. Just type the following code into Sails’ console and execute it:

Person.create({ firstName: 'John', lastName: 'Doe' }).exec(console.log);

The Person here is the model we defined earlier (Sails exposes all models globally for your convenience). The create() is the method that creates new entities of the specified models; it takes an object with the field’s values as an argument. Make sure to correctly specify all required fields. Finally, the exec() method actually runs the required operations on the underlying database. It takes a single argument, the callback function, which will be called when the action completes. The created entity gets passed to it as a second argument. We are using the convenient console.log function here to output the newly created entity to the console.

The result should look as follows:

  firstName: 'John',
  lastName: 'Doe',
  createdAt: '2015-05-07T22:01:26.251Z',
  updatedAt: '2015-05-07T22:01:26.251Z',
  id: 1

See how the unique ID was assigned to the entity and additional fields were added with the actual dates.

Next, let’s create two contacts:

Contact.create({ type: 'mobile', value: '+7 123 123-45-67', person: 1 }).exec(console.log);
Contact.create({ type: 'skype', value: 'johndoe', person: 1 }).exec(console.log);

Make sure to specify the required person field with the proper ID value. This way, Waterline will know how to relate the entities to each other.

The final thing to do is fetch the created person, as well as the collection of its child contacts:


The find() method finds entities of the specified model; by passing 1 to it, we are telling Waterline to find the person entity with the ID of 1. The populate() method fetches the related entities; it accepts the name of the field to fetch.

It should return the person entity with all of its child contact entities as a traversable JavaScript object.

Note: I suggest that you experiment now and create multiple entities. As part of your experiment, see how validation rules are enforced by omitting some required fields or by using an incorrect enum value.

Of course, use Waterline’s documentation10 to your advantage!

Shadow Routes for Models Link

The Blueprints component, mentioned earlier when we talked about controllers, also comes into play with models. Again, it makes the developer’s life easier with two useful features: automatic REST and shortcut routes for our models.

By default, the Blueprints API provides implicit (shadow) routes for each model, with a defined controller. For this to work, we need to create empty controllers for our models. Just create two files, api/controllers/PersonController.js and api/controllers/ContactController.js, with the following content:

module.exports = {

After that, restart the application.

Now, with the help of the Blueprint API and its shortcut routes, we can enter the following URLs in the browser:

URL Description
/person/create?firstName=John&lastName=Doe to create a new person entity
/person/find/2 to get the person with the ID of 2
/person/update/2?firstName=James to update a person with the ID of 2, giving it a new first name

These shortcut methods could be pretty useful during application development, but should be disabled in a production environment. I will show you how to do exactly that in the “Environments” section of this article.

Another, and probably the most useful, part of Blueprints is the automatic support for the REST APIs. The following implicit routes are provided for CRUD operations:

HTTP Method URL Description
POST /person creates a new person
GET /person/2 gets a person with ID of 2
PUT /person/2 updates a person with ID of 2
DELETE /person/2 deletes a person with ID of 2

Let’s create a new person using the REST API provided. I’ll use the great application for Google Chrome called Postman11. It’s free and extremely useful for working with different HTTP APIs.

Select the POST HTTP method. Enter the URL http://localhost:1337/person, and provide the following JSON “raw” request body:

  "firstName": "John",
  "lastName": "Doe"

Make sure to select application/json as the request’s content type.

Now, hit the “Send” button.

Sails should satisfy your request and return a new entity with the freshly generated ID: STATUS 201 Created.

  "firstName": "John",
  "lastName": "Doe",
  "createdAt": "2015-05-13T21:54:41.287Z",
  "updatedAt": "2015-05-13T21:54:41.287Z",
  "id": 4

Note: I would recommend experimenting with these methods now. Try to create a new person and some contacts. Update the contacts to assign them to a different person. Try to delete a person. What happens with their associated contacts?

Every implicit Blueprint API route will be provided only if the model’s controller lacks the required action. For example, when you’re getting a single entity, the Blueprint API will look for an action called findOne. If such an action is not present in your model controller, then the Blueprint API will implement its own generic version of it. However, when an action is present, it will be called instead. Let’s create a very simple example just for the sake of demonstration: api/controllers/PersonController.js:

module.exports = {
  findOne: function (request, response) {
    Person.find( (error, persons) {
      var person = persons[0];
      person.fullName = person.firstName + ' ' + person.lastName;

This is a very simplified example of how such an action could work. All it does is fetch the required entity from the database and generate a new field called fullName from the first and last name of the person; then, it just returns a JSON result.

Be advised: This is a simple example that doesn’t handle errors or edge cases properly.

The complete list of all REST operations that are supported by the Blueprint API can be found in the official documentation12.

Environments Link

Sails supports multiple execution environments; the built-in ones are development and production. When you run sails lift, it runs your app in the development environment by default. In other words, it’s equivalent to running sails lift --dev. You can also execute sails lift --prod to run your application in the production environment.

Multiple environments are provided to make the developer’s life easier. For example, in a development environment, some caching functionality is disabled by default in order to always return fresh results. Also, Sails will look for changes in your assets directory and will recompile assets in real time using its Grunt task.

We can take this concept further and use it to our advantage.

Each environment can override the application configuration to make it behave differently. If you look in your config directory, you will find a subdirectory named env. It contains custom configuration files for each environment. By default, these files are empty (not counting the comments).

Let’s configure our application to use port 80 in a production environment and also disable the Blueprint API’s shortcut methods. Open the config/env/production.js file and change its content:

module.exports = {
  port: 80,
  blueprints: {
    shortcuts: false

Now, start Sails using the following command:

sudo sails lift --prod

Here, sudo is required in order to bind to the privileged port. Also, make sure that the port you’ve specified is not used by some other web server, like Apache 2 or nginx. If you can’t start Sails this way for some reason, just replace the port with something else, like 8080, and run the command again without the sudo.

Now, your Sails app should listen on port 80, and all shortcut requests like http://localhost/person/find/213 should not work. However, the REST API should work as expected.

You can also check the current environment in your code dynamically and adjust the business logic according to it. The name of the current environment is stored in the global sails.config.environment property. Here’s an example:

if ('production' == sails.config.environment) {
  // Actually send the email only in production environment.

Final Words Link

In this introductory article, I’ve shown you the most important parts of the Sails framework and given you some specific examples to get you going. Of course, if you want to use it in your daily work, you will have to spend some time mastering it and taking it to the next level. The good news is that Sails comes with pretty solid documentation and an active community. The creator of Sales even answers questions on StackOverflow personally. You will not be alone.

And remember, constant self-education and exploration is key to success. When you get some good results with Sails, feel free to come by and help the developers make it even better.

I’m hoping to continue writing about more specific aspects of Sails to give you an even deeper understanding of the framework itself and the Node.js ecosystem as well. Stay tuned!

(al, ml, rb, jb)

Footnotes Link

  1. 1
  2. 2
  3. 3
  4. 4
  5. 5
  6. 6
  7. 7
  8. 8
  9. 9!/documentation/concepts/ORM/model-settings.html
  10. 10!/documentation/concepts/ORM
  11. 11
  12. 12!/documentation/reference/blueprint-api
  13. 13 http://localhost/person/find/2
SmashingConf New York

Hold on, Tiger! Thank you for reading the article. Did you know that we also publish printed books and run friendly conferences – crafted for pros like you? Like SmashingConf Barcelona, on October 25–26, with smart design patterns and front-end techniques.

↑ Back to top Tweet itShare on Facebook

Slava Fomin II is an entrepreneur and a passionate Web-developer from Moscow, Russia. As a founder of the Better Solutions studio, he tries to make Web a better place by combining great design with the cutting-edge technology.

  1. 1

    I’ve been using Django for the past 1 and a half year and I am looking for a similar NodeJS framework to try on some new projects.
    I don’t really have any complaint about Django, it is amazing, the thing is Python… I am way more comfortable writing Javascript and I realise NodeJS + Express is the “thing” right now.
    Perhaps I will give Sails a try, I already heard about it, as well as KeystoneJS, let’s see if it makes me forget Django.
    Nice article btw ;)

  2. 2

    Nice article! I gave a test run to sail last summer but I found it is hard to go beyond the basics with Sail. What would have really helped me is having something similar to Cakephp Blog tutorial, where you have a simple tutorial / app where every aspect is put together as an example. Like you’d rarely build an application without an API, ACLs / rights management, internationalization, authentication, models with complex n/n associations, test fixtures, etc. I find the documentation from Sail lacking of some depth there, but I surely hope it gets better so that I can have another try!

  3. 6

    I’ce used sails pretty extensively over the last year and don’t actively hate it yet (that’s high praise from me). A couple things to watch out for: development seems to have stalled—is sails abandon-ware? The documentation is pretty, but all the internals are a magic black box. Enjoy spelunking when you want to know more about *how* something works.

    What we found is that most of the important functionality is handled by Express. Sails provides a nice configuration layer on top (we’re not using the generatiors or models). It’s up to you to decide if that’s worth it.

    • 7

      Sails is dying yes. But not in a terrible way. Watching the authors on github they seem to have been putting alot of effort into

      I have a feeling it will be a replacement for sails. They seem to have established a migration path for making a sails app the ‘core’ of a trails app. I have a feeling it’s going to be largely based around mike’s

      Maybe some kind of open source treeline.

      • 8

        Slava, great article!

        @Brett, Sails is not dying, never fear. The rest of the core team and I are as committed to the project as ever. We’ve been busy writing “Sails in Action”, as well as working on the future of blueprints: Treeline. Sails is a key part of what we’re building with the machine/circuit spec, and both projects are dependent on one another for success. To give you an idea of what’s coming, @particlebanana, @sgress454, and I have started a deep dive to improve the robustness and type normalization in Waterline adapters (and indeed, it does involve machinepacks!), so expect to hear more about that some time before the end of the year.

        • 9

          So what exactly is TrailsJS?

        • 10

          Of course it’s dying. Mike McNeil kicked out the core maintainers last month because he needs tighter control over the framework in order to satisfy the Treeline investors.

          Sails has lost its open source ethos, and is no longer actively maintained. They keep producing patch versions to make it look like there’s activity, while the framework is based on Express3 which was end-of-life as of 5 months ago.

          The Sails maintainers are working on a successor called Trails, and are releasing 1.0-alpha in January. I’d give that a look.

          • 11

            To set the record straight: Travis was the only person who was removed as a contributor. The core maintainers are continuing to work on Sails, while Travis has moved on to his own project, Trails.

          • 12

            Rachael, no one cares about your gossip, they care that their framework is actively maintained, and that you have the best interests of the framework and the community in mind.

            The Treeline team now controls Sails.js, and they have $600,000 reasons to work on Treeline. I don’t see how these incentives are aligned to benefit Sails.js users whatsoever.

          • 13

            Travis, I have purposely tried to steer clear of your negativity, but when I saw what I know to be incorrect/misleading information, I felt obligated to correct it. I don’t appreciate you belittling me and dismissing my comments as ‘gossip’, especially when many of your own comments seem to be your personal speculation/opinions.

          • 14

            Your boyfriend spent the better part of the past year screwing over the people who were running his company and his open-source projects. As one of those people, I find my “negativity” thoroughly justified. Especially since we were supposed to be *on the same team*.

            Anyway, good luck with Treeline, I’ll be working on Trails.js while no one maintains Sails.js.

  4. 15

    Almost forgot: hugely important—sails will *automatically* create instances of your service classes (anything in the services folder). It assumes these are Singletons (which is often true). If you don’t know it’s doing this and attempt to require them in and handle this yourself, you’ll eventually get some really weird behavior. Code will inexplicably run twice, cached objects won’t be found, etc.

  5. 16

    The introduction of this article promised Sails to be a framework that is ideal for quick tryouts, experimenting, etc.

    Having read through the whole article (which is excellent by the way, don’t get me wrong), I don’t see how this framework is any different from MVC framework number 16,342. It’s also not clear how this framework will not make you ‘support a specific architecture’, since the entire article shows just that.

    Compared to somebody already familiar with another MVC, be it back-end or front-end, I really don’t understand how this framework specifically makes it faster to experiment.

  6. 17

    While I’m thrilled to see some node-based MVC frameworks coming to light, I don’t think it’s a fair comparison to say that someone should choose this framework over a PHP framework because of the downsides of Symphony. This is comparing apples and oranges. Symphony is only one of the many PHP frameworks. And yes, its’ a bit large and bloated, but there are many other fully featured, yet lightweight MVC frameworks for PHP, Python, Java, C#, and other languages. The challenge with frameworks is not the language, it is the amount of complexity, interchangeability, scalability, and documentation that comes with it. Yes, node.js is a good thing. But being on the younger side, we will probably see a lot more node.js frameworks coming out in the next few years. Some will be easy, some will be hard, some will be bloated, but a few will become the “good” ones. This has nothing to do with a specific language, and everything to do with the community and how people learn to build and use these frameworks.

    But sails looks interesting and appears to have great potential, and I’ll keep my eye on it. Thanks for the writeup!

  7. 19

    FYI: Swig is outdated by now. There is no maintenance anymore (Date: 24.11.2015)

  8. 20

    I’ve been getting into node.js with the vagrant box of Laravel and Homestead. Are there any equivalents to that with node.js where I can separate my development environment from my operating system?

  9. 22

    This article is fantastic. As someone with experience with Node/MongoDB/Express, I was able to pick up and build a Sails website in an evening. It was extremely well presented.

    I’m thrilled about Waterline. It finally feels like the right ORM for Node.

  10. 23

    Domagoj Katavic

    November 26, 2015 10:26 pm

    I have been developing in Sails for the past year and I can highly recommend it, it offers a lot of things out of the box and allows you to reconfigure everything you don’t like.

    I would like to hear more about trailsjs, Mike maybe you could shed some light on it?

  11. 24

    I’ve been looking at using Sails.JS to rebuild our enterprise grade customer and agent portals. It’s fantastic if you want to get your site up and running quickly with a fantastic ORM provided by Waterline, fantastic security policies applied via a middleware before the request even hits your controller and a lot of behind the scene magic, you’d think it’d be a no-brainer.

    The only thing that’s holding me back is a massive lack of clear documentation or even a case study detailing how to keep the applications live in a distributed environment handling 4+ million daily customers, yeah there’s services such as PM2 which can help keep it up and running but you still end up needing your ops teams to agree to take on the challenge of a new infrastructure, as well as the threading model of node it’s self being slightly flakey.

  12. 25

    Hristo Chakarov

    November 27, 2015 12:22 pm

    I captured the text “share your code between the server and client” and got interested into the article. However – I didn’t see any example how can I share code between client and server. How is this possible? Am I able to choose where to have my model rendered – in the client or on the server? Can I have a “2-way data binding” in the client?

    One year ago I was looking for an MVC framework that operates both in the client and on the server. I needed the model rendered on the server (for SEO purposes) but I also wanted 2-way binding in the client, so the user can easily update the model. I did not find anything suitable. I started experimenting with KnockoutJS and JSDOM, and I created a simple stack that did the job for me:

    Is this possible with Sails.js?

    • 26

      Thiago Medeiros

      December 2, 2015 8:19 am

      Actually i don’t know Sails.js yet. But, i found Meteor MVC framework to have more explict information on the concern in its on website. There is also displayed that it is capable of running isomorphic js and you even can choose Angular.js for your need on 2-way databinding on the client.

  13. 27

    Hm interesting, looks like a stable framework and the documentation seems to be quite good.

  14. 28

    This is a great article, I have been learning Sails in my (very little) spare time for a few months now. It is great to see some updated resources for it as a lot of what is out there on the web is over 1 year old.

    I am really looking forward to reading your future articles so please keep writing, you seem really knowledgable and I wan tot keep learning :-)

    P.S. node.js is going to rule the web and sails will be a bigger Rails…

  15. 29

    After about half a year dealing with sails.js I have to say the experience has been abysmal.

    To understand what is wrong with sails you have to understand what it and specifically its ORM was originally designed for. The problem with sails is that on the surface it gives the impression of trying to be like rails, a general purpose development platform for web applications. The reality is though that this product was built by a web dev consultancy who wanted to be able to reuse projects in a database agnostic fashion.

    This leads to the major problem with Sails. Its ORM is not designed for ease of use or dealing with complex queries, it is designed to make it easy to switch between sql and nosql databases for a single code base. If this is your use case then awesome, sails may be for you. HOWEVER, IF YOU ARE PLANNING ON BUILDING A SINGLE LONG TERM PRODUCT WHERE DATABASE AGNOSTICISM IS NOT A MAJOR CONCERN, RUN AWAY FROM SAILS.

    Dealing with complex relational database operations is a nightmare. Even many simple things such as basic joins are not robustly supported. Creating complex join tables is a nightmare, migrations are a nightmare. In my experience sails has been a nightmare.

    If you are willing to deal with the performance implications and aren’t focused on sockets I would recommend sticking with rails. There is a reason it is as popular as it is.

    If you want in depth socket abilities, serious performance and the rest of what makes node great stick with simple node or express and build a system on top of them with packages.

    Sails is the worst of both worlds though

    • 30

      Andrew, our team also learned this fact the hard way. The Sails.js creators are oblivious to the problems in the framework and in the community, and unwilling to do anything to fix them. They seem to not recognize that anything is wrong at all. To see this in action, just google “status of sails.js” and you’ll see this at the top result:

      You can simply check the activity on the repository to see that the people who have been maintaining Sails for the past year are in fact *not* contributing any longer and are moving over to Trails.js. The majority of activity in the Sails.js ecosystem these days is Mike McNeil occasionally tweaking markdown files and the Sails Bot automatically commenting and closing issues. The framework is not only dying, it is already dead.

  16. 31

    Richard Seldon

    January 9, 2016 1:33 pm

    The comments made by Travis Webb are highly disturbing.

    I used Sails commercially for a year in 2014, and it was a great experience albeit only for a JSON API (no front-end) and reasonable database requirements (nothing too complex although the full mantra of entity relationships etc) using waterline and mysql.

    Just returned to pick up where I left off, and after reading about the lack of maintenance and splintered team situation, am re-evaluating options. Believe SailsJS is a great framework but it does need continued love and support, and a core team / maintainers ready to address upgrades and sensible feature requirements.


↑ Back to top