Ever find yourself wandering in the cheese aisle of a gourmet shop, overwhelmed by the impressive display of cheeses from all over the world? It’s a nice problem to have, but the trouble is in deciding which of them to bring home.
A cheese selection so abundant that it’s overwhelming is a perfect example of the paradox of choice, the principle that says that while having no choice is bad, having too many choices is equally as bad, and can sometimes lead consumers to give up choosing altogether.
Developers looking for a Node.js framework or library are faced with this very issue, a virtual embarrassment of riches. It can be difficult to know which of the many options to choose from, much less how to get started with a Node.js framework, or which factors will help you decide whether it’s the right fit.
Given the plethora of options, I had to wade through a few alternatives before settling on Sails.js. I like Sails because it embraces some of the ideas that have made the Rails framework so successful, while maintaining a distinct Node.js flair. In particular, Sails uses layered development and convention over configuration, two development techniques popularized by Rails and similar frameworks. Sails also integrates several other Node.js packages for key components, which means you don’t necessarily have to learn the framework from scratch. Another plus is the fact that the documentation for Sails.js is continually improving, a key indicator that I look for from any open source project.
In this first installment, I’ll focus on how Sails approaches layered development. Future installments will introduce convention over configuration and other conveniences of coding with Sails.
The new MVC
The Sails approach to layered development is different from what many traditional web developers might expect. In fact, Sails is one of an emerging breed of frameworks that redefines expectations of web application development by turning MVC on its ear. Instead of the MVC components residing on the server, as in a traditional MVC architecture, Sails puts the models and controllers on the server, while the views live on the client tier. Sails also sees the “view” as a more generic concept; it could be HTML, but it could also be a mobile application written in native iOS or Android.
This reconfiguring of the traditional MVC architecture achieves the very thing that architects and developers have long sought from application servers: a single place for business logic and validation, and a common interface for multiple clients.“
This reconfiguring of the traditional MVC architecture achieves the very thing architects and developers have long sought from application servers: a single place for business logic and validation, and a common interface for multiple clients. The server needs only to project data elements in a representation recognizable to the client, and this approach provides an easy way to do traditional business logic and data validation through HTTP endpoints.
The HTTP API example app
This new architecture, what I call an HTTP API, is similar to the RESTful style, but it’s not actually REST — not, at least, according to Roy Fielding’s definition of REST. REST relies on the exchange of hypermedia as the principal vehicle of application state (this is the Hypertext As The Engine Of Application State, or HATEOAS, principle — quite possibly the worst acronym ever invented). But an HTTP API system surrenders some of the flexibility and “generic client” capability that REST offers in exchange for a simpler model.
Unlike a REST system, HTTP API clients do require some deductive knowledge of the system they’re communicating with. They need to know the expected results and available endpoints ahead of time. It’s contrary to a REST architecture, but I find that the trade-off for simplicity is worthwhile.
In an HTTP API, all communication between client and server takes place over HTTP. For the example app, I’ll use JSON as the data format exchanged and standard HTTP verbs to indicate actions (GET as read, POST as insert, PUT as update, and DELETE as delete). I’ll also use HTTP headers as the mechanism for conveying out-of-band information like authentication data and so on. I will, however, still store some state data on the server side (contrary to a traditional REST design), and the data exchanged will all be JSON and assumed to be specific to this system.
While evaluating Sails.js, I spent some time experimenting with it. So without further ado, let’s start learning the ropes of Sails.
What you’ll need: Prerequisites and setup
I’m going to expect that you are somewhat familiar with the principles and terminology of REST and RESTful architectures; you don’t have to be a zealot, but you should be comfortable using HTTP “in the raw” to submit GET requests, POST requests, and so on. The idea of looking at an in-flight HTTP request shouldn’t scare you.
For your development environment, make sure Node.js is up to date on your machine (as of this writing, mine reports version 0.12.7), and that you have the latest version of npm installed (mine is version 2.12.1). While the Node.js community obviously tries to keep versions backward-compatible, there’s no guarantee that the code I write here will work with significantly earlier versions of the technology. Caveat emptor.
Install Sails.js and MongoDB
The only installation required to use Sails.js is to install it globally to the npm cache, like so:
npm install -g Sails.js.
Once you’ve done that you’ll have a new utility command,
sails, available for your use. Like npm,
-g option. Otherwise, it will only be installed to the current project directory, and this is one of those rare cases where the utility really needs to be global.
While you’re waiting for the Sails.js framework to download and install, take a minute to be sure that you have MongoDB set up in your development environment. If you don’t, now is a good time to download an installation appropriate for your target platform.
Note that Sails.js uses more than just a MongoDB back end; in fact, right out of the box, Sails.js uses a lightweight disk-based format. It’s not a recommended setup for production use, but works delightfully well for examples like this one. It will take a minute to get to the part of this series where MongoDB comes in, but you’ll be glad to have it when I do.
Check the installation
Is Sails.js done installing? Verify it with the command
As of this writing, the latest version of Sails.js is 0.11.0. As long as the above command yields anything other than an error, Sails is installed and ready to go.
Sails.js says “Hello, world!”
Tradition holds that the very first thing a programmer does when learning a new language or environment is to write the simplest possible program in that language: a Hello World application. Fortunately, Sails generates a Hello World as the basic application scaffolding when you create a new app. I can use this to verify that the Sails infrastructure is working. In a nod to agile development strategies, I’ll also deploy the scaffolding to the IBM Cloud as part of a “release early, release often” strategy.
Create a basic Sails.js app
Getting a new Sails.js app is pretty simple. Note that by default, the scaffolding will generate a more traditional web application, where the server-generated HTML is sent back to the client. While that’s useful in its own right, if the goal is to build an HTTP API app that is client-agnostic, I want to ensure that there’s no accidental “leakage” tying the client to the server. The best way to do that is to build the server with zero client components anywhere in the project. In Sails.js, that’s as simple as setting your default configuration to
The downside of using the
--no-frontend option is that I won’t have a visual way to verify that the scaffolding works. I’ll want that verification when I deploy the app to the IBM Cloud. So for this first installment, I’ll create an app that has the pretty HTML front-end scaffolding, but in future installments, I’ll run with the assumption that the app was built without any front-end pieces. (The difference is just what’s populated in the generated views directory, so it’s not as big a deal as it may seem.)
Enough talk. Let’s get sailing!
From the directory where you want the code to live on your development machine, issue a
new command via the
sails command line, then name your app:
After you do this, Sails will think for a second, then should respond with a message like: “Created a new Sails app ‘hello-sails’!” (If anything else comes back, something is probably wrong with your Sails.js installation.) In the target directory, which is inferred from the application name, you’ll see that Sails.js laid down a number of directories and files. I’ll explore most of these in greater detail throughout the series. For now, I’ll just familiarize you with the basics of a Sails application.
Generally, it’s safe to assume that the scaffolding won’t have any problems (so long as nobody touches it), but it’s helpful to know how to run the app locally. So let’s kick off our journey with a “sails lift.”
Figure 1. Running Sails.js locally
Pretty, no? Personally, I like the use of color, and additionally, it’s quite the impressive display of ASCII art. More important is that this little ASCII painting confirms that Node.js is successfully running our scaffolded application on the local machine, on port 1337. Hit it with a browser and the default Sails.js web page will appear, complete with links back to the main Sails homepage and documentation.
Deploy Sails.js to the cloud
Running on a local machine is not the same as deploying your code to a production (or production-like) server, and this is where the cloud comes into play. For this series, I’ll use IBM Cloud as the cloud host. It seems apropos, and it’s an easy platform to use for hosting Node.js-based applications.
IBM Cloud also happens to be based on Cloud Foundry, so if you haven’t already, now is the time to install the Cloud Foundry client (
cf). You’ll want it running on the same machine where your Sails.js code is hosted.
After you’ve installed the client, verify the installation by running
cf--version; at the time of this writing, the version echoed back was 6.12.2.
As mentioned, you’ll also need an IBM Cloud login to deploy the app to the IBM Cloud. Once you’ve got your credentials, log in to the IBM Cloud and push the code from your local hello-sails directory into the IBM Cloud.
Listing 1. cf into IBM Cloud
Teds‑MBP15:hello‑sails ted$ cf login API endpoint: https://api.ng.bluemix.net Email> firstname.lastname@example.org Password> Authenticating... OK Select an org (or press enter to skip): 1. developerWorks 2. email@example.com Org> 1 Targeted org developerWorks Targeted space Ted Neward API endpoint: https://api.ng.bluemix.net (API version: 2.27.0) User: firstname.lastname@example.org Org: developerWorks Space: Ted Neward
cf tool will also need to know the API endpoint to use for deployment. If
cf can’t figure that out, you can manually set the endpoint (
api.ng.bluemix.net) via the command
cf api api.ng.bluemix.net.
Two useful files
Once you’ve logged in and set the API endpoint, you’ll use
push to deploy the scaffolded Sails.js app to the cloud. Cloud Foundry will just need a few parameters in order to deploy the application correctly. Rather than typing those repeatedly (which grows tiresome), create a manifest file (manifest.yml). This will contain information like the application name to use on the IBM Cloud, the hostname to use for a subdomain (if it’s different from the application name), how much RAM to reserve for executing, and so on.
The simple manifest file for the example:
‑‑‑ applications: ‑ name: tedneward‑sailsIntro host: tedneward‑sailsIntro memory: 256M command: node app.js
Another useful file that Cloud Foundry recognizes is .cfignore. This contains the list of files or directories that shouldn’t be pushed up from the local app to the cloud. For now, just file that information away for future reference.
With all that in place, all that’s left is to do a
push and let the push do its thing. (The output from this can be sometimes quite impressive in length, as the code must be packaged up, uploaded, unpacked, then installed on the cloud instance, which in turn means running an
npm install on the cloud instance, which generates a ton of results. As a result, only about the first 20 and the last half-dozen lines are displayed here.)
Listing 2. cf push to the IBM Cloud
Using manifest file /Users/ted/Projects/sails‑intro/code/hello‑sails/manifest.yml Updating app tedneward‑sailsIntro in org developerWorks / space Ted Neward as email@example.com... OK Using route tedneward‑sailsIntro.mybluemix.net Uploading tedneward‑sailsIntro... Uploading app files from: /Users/ted/Projects/sails‑intro/code/hello‑sails Uploading 31M, 11773 files Done uploading OK Stopping app tedneward‑sailsIntro in org developerWorks / space Ted Neward as firstname.lastname@example.org... OK Starting app tedneward‑sailsIntro in org developerWorks / space Ted Neward as email@example.com... ‑‑‑‑‑> Downloaded app package (24M) ‑‑‑‑‑> Downloaded app buildpack cache (18M) ‑‑‑‑‑> IBM SDK for Node.js Buildpack v2.5‑20150902‑1526 Based on Cloud Foundry Node.js Buildpack v1.5.0 ‑‑‑‑‑> Creating runtime environment NPM_CONFIG_LOGLEVEL=error NPM_CONFIG_PRODUCTION=true NODE_MODULES_CACHE=true ‑‑‑‑‑> Installing binaries engines.node (package.json): unspecified engines.npm (package.json): unspecified (use default) . . . App tedneward‑sailsIntro was started using this command node app.js Showing health and status for app tedneward‑sailsIntro in org developerWorks / space Ted Neward as firstname.lastname@example.org... OK requested state: started instances: 1/1 usage: 256M x 1 instances urls: tedneward‑sailsIntro.mybluemix.net last uploaded: Mon Oct 5 01:36:54 UTC 2015 stack: lucid64 buildpack: SDK for Node.js(TM) (ibm‑node.js‑0.12.7)
Assuming you’ve uploaded and installed all the components correctly, you should see a new subdomain on the IBM Cloud (in my case, it’s http://tedneward-sailsIntro.mybluemix.net). The new subdomain will respond to an HTTP
GET / request by producing the Sails.js sample hello page.
With the scaffolding in place, I’m set up to begin extending and improving the example app. Thanks to the work done so far, I’ll be able to deploy the new code to the IBM Cloud each time I finish a new segment. Remember the principle, “always be shipping” — or put another way, “release early, release often.” Each time I push code to the server for others to use, I’m creating opportunities for people to find bugs, verify that I’m headed in the right direction with the code, and so on. Using a well-established cloud foundation makes it trivial to ship, since I’ve done most of the work up front. With a simple
cf push, the latest version of the Sails app will be aloft and waiting for HTTP clients to find and use it.
The next installment will feature the Sails.js models. Getting to know the models will also introduce you to a hefty amount of Sails convention, called blueprints or the Sails Blueprint API. Like its inspiration, Rails, Sails favors convention over configuration, which means I’ll be able to build models, and get some useful functionality out of the box, without having to write a ton of configuration files or code.
I have just a scaffold now, but the example app that will take shape over the next several tutorials will be a blog engine — with a twist. Rather than build the engine as a traditional server-side web app, I’ll develop it as an HTTP API. The API packs all the technology I’ll need into the back end, leaving the client-side code to be determined by whatever front-end tool somebody wants to write.
For now, it’s time to pause. Given the name of the framework, let us not say “so long” but the more appropriate “bon voyage.”