What you need to know about Sails.js

Lauren Adam
Upstate Interactive
6 min readJul 17, 2019

So what’s Sails?

A few months ago I was tasked with building an API for a client using Sails.js. Having never used this framework before, the first place to start for me was the documentation. After reviewing the docs and some of Upstate Interactive’s existing projects that use Sails, I got the impression that it made things easier, cleaner, and more organized than it would have been without it, but I couldn’t tell you exactly why. After a couple months of working with it and building out an API, my first impression was solidified.

Sails vs. Express.js

Simply put, Sails was built on the back of Express. Express itself is a backend library that makes building the API of your application easier. Sails takes it a few steps further by including rich scaffolding, behind the scenes routing, and varied database support. What does this all mean?

It means that you’ll write less repetitive code while getting started and building out the API, and that you can use any database without changing the code, minus some minuscule configurations. With Sails, setting up your database configuration is done in a handful of lines of code with step by step instructions in the comments.

datastores: {/*************************************************************                                                                          *
*
* Configure your default production database. * *
* 1. Choose an adapter: *
* https://sailsjs.com/plugins/databases *
* *
* 2. Install it as a dependency of your Sails app. *
* (For example: npm install sails-mysql --save) *
* *
* 3. Then set it here (`adapter`), along with a connection URL
* (`url`) and any other, adapter-specific customizations. *
* (See https://sailsjs.com/config/datastores for help.) * **************************************************************/
default: { adapter: 'sails-mysql', url: `mysql://${process.env.DB_USER}:${process.env.DB_PASSWORD}@${process.env.DB_HOST}:3306/horizon`,}

Great Features

Actions

Among the many features of Sails I found actions and the routing system to be the most straightforward, which is encouraging because these are two key features of the framework. I learned that if your project needs more than a few custom endpoints (the basic ones included with blueprints from Sails ORM, Waterline), your controller files are quickly going to turn into a jungle of code. Actions are the best way to mediate this situation, by creating individual files for each controller function you need.

/*** Module dependencies*/// .../**
* farm/add-kittens.js
*
* Add kittens to a farm
*/
module.exports = async function addCredit(req, res) {
const respondWithFailure = err => res.status(500).send(err);
const id = req.param('id')
const { numKittens } = req.body
const farm = await Farm.findOne({
id
})
.intercept(respondWithFailure)
const previousKittens = farm.kittens
// determine the new number of kittens
const currentKittens = previousKittens + numKittens

// create a litter for the new kittens
await Litter.create({
farm: id,
amount: numKittens,
previousKittens: previousKittens,
currentKittens: currentKittens
})
.intercept(respondWithFailure)
const newNumKittens = await Farm.update({
id
})
.set({
kittens: currentKittens
})
.intercept(respondWithFailure)
.fetch()
res.status(201).send(newNumKittens[0)
};

Here you can see a generated action by using sails generate action farm/add-kittens --no-actions2. Sails saves time by formatting the file, giving space for any additional dependencies and generating a quick definition of what the action will do which helps you keep in mind the overall goal of the action and stay out of the weeds.

Routes

Additionally, the ease of creating and designating routes was another feature that was useful and easy to pick up. With the help of Sails blueprints, unless you need a custom API call outside the scope of CRUD, you’re free and clear from routes.

/**
* Route Mappings
* (sails.config.routes)
*
* Your routes tell Sails what to do each time it receives a
* request.
*
* For more information on configuring custom routes, check out:
* https://sailsjs.com/anatomy/config/routes-js
*/
module.exports.routes = { 'GET /farm/:id/kitten': 'farm.find-kittens-by-farm',
'POST /farm/kitten': 'farm.reassign-kittens-to-farm',
};

And that’s it. Super simple, right?

Drawbacks

Policy Cascading

Despite all of its’ wonders, there were a couple things that I found left me wanting from Sails. This may be due to my front end experience, but I found the fact that the policies don’t cascade a bit tedious. For example, if you have a policy called hasAuthorization that checks to see if someone accessing a route has the authorization to do so, you can apply that policy to each action within a controller folder like so…

‘farm/*’: ‘hasAuthorization’

However, if you wanted to add a second policy, perhaps to check if the person has access to a different role, you’d apply a different policy and have to restate the first policy for that route…

‘farm/*’: ‘hasAuthorization’
‘farm/edit-kitten’: [‘hasAuthorization’, ‘isFarmer’]

As you can see, this can add of repetitive code for the different verifications you may need, and since repetitive code writing is something Sails tries to avoid, it feels like this could be improved.

Intercept

Sails has a feature called .intercept() that allows you to make certain errors in certain situations be more verbose and explain a little more about what’s going on without having to weed through the extra stuff. This in itself sounds great, right? It was!

For the most part.

My problem with this stemmed from the aspect of the feature that meant a 500 error was thrown each time. This didn’t matter for me or my project 98% of the time. The other 2% happened when I was trying to throw a 402 error after checking a client’s credit amount using a helper.

So what were the options? I could have it throw a 500 error, but that wasn’t really what the problem was. We opted to use .tolerate() that would create an instance of the error but ultimately swallow it.

// check if a farm has sufficient mice to reduce the number
const newRecord = await sails.helpers.reduceMice.with({
farmId: farm.id
})
.tolerate('notEnoughMice', () => Error('not enough mice'))
if (newRecord instanceof Error) {
return res.paymentRequired(newRecord.message)
}

Here we needed to evaluate if a client had enough credits to process a transaction. If they did, newRecord became that newly created transaction. If they didn’t, and the reduceMice helper returned the notEnoughMice error, it becomes an instance of an error thanks to tolerate(). In the next lines we check if it’s an instance of an error or not, and if it is, we skip everything else in this action and return the appropriate response.

Favorite Feature

You know where this is going… If I had to choose a favorite feature in Sails, it’d be the helpers. They’re probably the most basic feature, but their usefulness outweighs the lack of shiny. Helpers are the place where you stick your duplicate code, your complicated functions that convolute the original document, and help your keep everything clean and organized. Get it, helpers? Don’t worry, I’ll see myself out at the end of this post.

A couple functions I used helpers for included checking error codes and sending that info back to the parent function to update the record, check the credits on an account while performing an action, and checking a user’s ID. Nothing life changing, but these little helpers kept the code they were used in, even if only once, a bit cleaner and easier to read.

Getting started on Sails

Assuming this person has an understanding of MVC frameworks and Express (which could be a big leap, like it was for me), I’d have any new person check out their documentation. I know, I know, we hear that all the time. Read the docs. I mean it. A lot of the headaches I gave myself were from not carefully reading the documentation for a feature and spending time fruitlessly trying to accomplish a task that they already provided a specific feature for, I just hadn’t found it yet.

Additionally, start exploring actions2 and helpers early on. They can be very useful but, much like commenting your code and refactoring as you go along, it can be difficult to implement them after you’ve done a bunch of work.

Next Steps

All in all, using Sails for this project was a great choice, from the simple start-up to the features I didn’t know I needed. If you’ve decided that Sails is the framework for your next project, head over to Sails.js to start reading the documentation and get it up and running on your local machine.

I hope you found this article useful and enlightening. You can send any feedback, question, or comments to our team— team@upstateinteractive.io

☞ To stay up to date with our work with Sails.js and other frameworks, follow us on Medium and Twitter.

--

--