Getting Started with Sails.js

Getting Started with Sails.js

Intro to Sails.js Framework for Next-Level Nodejs Backend Apps. Sails.js - Realtime MVC Framework for Node.js. Sails.js is a Backend MVC Framework built on top of Express that provides a Rich set of features that allows you to develop high-level application including servers and full web applications by giving you all the tools you need to start putting your app together without thinking about low-level configuration and setup.

Getting Started

Sails.js is a Backend MVC Framework built on top of Express that provides a Rich set of features that allows you to develop high-level application including servers and full web applications by giving you all the tools you need to start putting your app together without thinking about low-level configuration and setup.

Sails does a lot of things for you out of the box for you and your application.

  • Awesome Sails-CLI that assist you thought out the development process
  • Security Standard already being handled and taken care of avoiding the highest risks.
  • Models and support for multi-database system plus it includes an ORM for writing once and supporting multiple databases with one single code-base.
  • Node-Machine system supported for Controllers to make it easier for you to develop and maintain your servers and APIs.

If you still can quietly decide why you should choose sails over you basic express app setup you can take a look on the Sails.js website for seeing all the available features and options plus what can Sails do for you.

Now let's install sails and try to Setup and create our first Sails App.

npm install sails -g 

Sails need to be installed globally on your machine since it plays a role of a CLI (Command Line Interface) so you will not only use it for creating and setting up the project but for other things concerning your project (for ex: deploying, lifting, generating Controllers).

For creating a new project using Sails-CLI we use the new command and give it the name of the project then relax grab a cup of coffee and le sails do the heavy job for you.

sails new sails-app

Make sure to cd to the directory where you want to create your sails app project, sails will create the folder for you with the supplied name and put all the sub-folder and configuration files ready for you with a simple app already created for you to see how things works.

Also when you try to create a sails project you will be prompted to choose between two templates:

  1. Web App: for creating a full web app already loaded with functions and features like (Auth, Login, 404 handlers.)
  2. Empty: this is for creating a boilerplate only project that has only the sails.js CLI and it's a standard configuration.

For this tutorial, we want to dive deep into sails and its base structure so we go with an Empty sails project.

App Structure

Sails project comes with a kind of unique and awesome project structure with some configuration already put on, if you are not a guy how looks and reads through other people's code a lot you mind find some difficulties to understand the structure.

api/ Where Controllers, Models and helpers live (handling API Requests and serve reponses).

assets/ All of you js, css and static resources are placed in here (for js and css it means you create your front-end app inside of this folder too this comes in handy for smaller apps).

config/ Sails.js system config files live in here so for customizing your application and adding any third-party modules or integrating middlewares, databases you will be configuring one of the files.

tasks/ the folder is a suite of Grunt tasks used for automating frequent tasks like bundlling assets or publishing some vendor files or even making the project ready for production use.

views/ All your app views, templates and layouts live in here Sails.js comes pre-installed and working with EJS but you can use your favorite template engine too.

.editorconfig Custom setup for the sails files for you code editor.
.eslintignore List of files, folders and patterns to be ignore by ESLint. 
.eslintrc ESLint custom configuration for your project files.
.gitignore Files, folders and patterns on which to be ignored by GIT stashing system.
.htmlhintrc Configuration for HTML hints and intellisense.
.sailrc Sails App Custom Configuration (You can customize and configure your app through this file).
app.js Main Entry point for your server which has some error handling and it does start the server.
Gruntfile.js Grunt Task manager custom configuration.
package.json Your Node.js App Packagejson.
README.md readme file for instructions and repos.

The main entry point for your Sails/Nodejs/Express Application is the app.js if you look inside the package.json you will see custom scripts for running, linting and testing your code.

Also, you can customize the Sails.js framework by defining custom configuration on the .sailrc.

Sail CLI

The Sails Command Line Interface plays a major role in assisting you thought out the development and deployment process of your application so you sit back and run commands that helps you manage your code and project.

There are many useful commands provided by the CLI.

Plenty of commands can really help you out and make it a bit easier for you.

Also, you can use the REPL Console which is normal Node.js/Javascript Console but it integrates with your sails server so you can easilly test and interect with you app environment with quick commands and it is specifically helpful when dealing with Models and databases.

Just Run:

sails console 

Sails MVC

The MVC Architecture allows you to create extensible API endpoint for your front-end app and any other application that does need communicate with your server endpoint, where Sails fully support this architecture but not only that it also provides other systems and structures for having a smooth development experience plus creating maintainable and understandable code.

Routes are what tells sails/express to where to route the request but sails create a wrapper around the standard Express routes system which easily allows you to create Routes with all the configuration needed all in one place with simple commands, take a look on the following Route path that routes the request to a controller that is saved on the api/controllers/

{
  "get /todo/add": "TodoController.addNewTodo"
}

Here you are telling it the HTTP method and the route path then you where to redirect the HTTP Request (ControllerName.methodName).

**Controllers **are the heart of an MVC application since they do the request handling and make sure to deliver the response as expected back to the client.

You can use the generate command on Sails-CLI to quickly create and generate controllers, models or actions.

You create the controllers in the standard way where you would have a controller file sitting in the api/controllers folder and all the actions are inside that file or use the new way that sails offer which is much easier to follow and maintain your controllers which offers you to put a folder with the controller name inside the controllers folder and inside of that folder you put all of your actions each action in its own separate file.

in Sails, you can use the standard Express Controllers structure or you can use Action2 the new architecture introduced by Sails team using Node-Machine to create controllers are ready to read and maintain through the time where it implements the notion of one input and one exit for your controller.

Look at the noticeable differences between a standard express controller structure and the new Action2 method.

Standard Express

exports.addNewTodo = (req, res) => {
  return res.send("Todo Added!");
}

Action2

/*api/controllers/todo/add.js*/
module.exports = {
  friendlyName: "Signup User",
  description: "Add a new Todo on your list",
  inputs: {
    name: {
      description: "Todo name",
      type: "string",
      required: true
    }
  },
  exits: {
    success: {
      responseCode: 201
    },
    invalidData: {
      description: "Please Fill in TODO name correctly!",
      responseCode: 500
    }
  },

  fn: async (inputs, exits) => {
    if (!inputs || !inputs.name) return exits.invalidData();
    const newTodo = await sails.models.todo
      .create(inputs)
      .intercept(err => {
        if (err) console.error("Cannot Created Todo!");
      })
      .fetch();
    console.log("New Todo has been created: ", newTodo);
    return exits.success(newTodo);
  }
};

If you try to read carefully thought out the Action2 code you see an object with some input and exist attributes plus a function (fn) attribute which gets executed as a controller handler taking the input and exit objects as parameters.

You can read more about Action2 method and Node-Machine from Here.

**Models **are an interface for interacting and communicating with a database(s) and with Sails Waterline ORM you can use the ORM API to create a single code base and make it work with multiple database systems with different types (MySQL, MongoDB...).

All of you models will live inside of api/models folder and need to implement a specific schema for the Waterline ORM then it can be translated and compiled to your target database(s) in runtime.

Here is the following example of a Todo model and how you can call it and use it from the todo/add controller

/*The Attributes represents different things when depending on wich database system you are working with (Columns in MySQL) and (Key Attributes in MongoDB).*/
module.exports = {
  attributes: {
    name: { type: "string", required: true, allowNull: false },
    done: { type: "boolean", defaultsTo: false }
  }
};

All Models are global by default on Sails so you don't have to import your created model they will be automatically available for you during the running on your controllers once you need them.

so just type the name of the controller or you can access it through the sails global variable that is available all over your application.

//Action2 main function from todo/add.js 
fn: async (inputs, exits) => {
  if (!inputs || !inputs.name) return exits.invalidData();
  //We access te todo model through the sails.models global variable
  const newTodo = await sails.models.todo
    .create(inputs)
    .intercept(err => {
      if (err) console.error("Cannot Created Todo!");
    })
    .fetch();
  /*Then you can use the Waterline API for talking with your database(s)*/
  console.log("New Todo has been created: ", newTodo);
  return exits.success(newTodo);
}

Make sure to check out the Waterline ORM Docs for more information about the API and how to communicate with the database.

Middlewares

Middlewares are a piece of function that runs before your controllers and actions does, for example, to provide meta-data to your controllers (You can use it for Authenticating users) in Sails they work the same way the does on Express but in a much meaningful way.

All of you sails middlewares will live inside of config/http.js there you can define your middleware function which takes (request, response, next) objects as a standard express middleware but the best part in here is you can actually define them in an arbitrary order but then you use the order object provided by sails which allow you to change and alter the order of which your middleware functions run.

order: [
  "cookieParser",
  "session",
  "bodyParser",
  "compress",
  "poweredBy",
  "router",
  "www",
  "favicon",
  "logIpAddress"
],

/*Here you define a self executing function that returns the actuall middleware function to be able to do some initialization, you can use it the standard way though.*/
logIpAddress: (() => {
  console.log("Initializing LogIpAddress Middleware...");
  return (req, res, next) => {
    console.log(
      "Client Ip: ",
      req.headers["x-forwarded-for"] || req.connection.remoteAddress
    );
  };
})()
}

Make sure to include your middleware in the order array in order to get executed.

Also, all the pre-defined middleware are very sensitive so if you will to remove one from the list or change the order it may make a whole difference on how you Sails application will work and operate.

Creating a RESTful Web API with Node.js and Express.js from scratch

Creating a RESTful Web API with Node.js and Express.js from scratch

In this article, I’ll show you step by step how to create a RESTful Web API with Node.js and Express.js by building a simple and useful Todo API. This article assumes you have basic javascript knowledge and terminal using capabilities.

In this article, I’ll show you step by step how to create a RESTful Web API with Node.js and Express.js by building a simple and useful Todo API. This article assumes you have basic javascript knowledge and terminal using capabilities.

You can also build a Web API in Node.js by using another framework except Express.js but Express.js is one of the most popular web framework for Node.js.

You can found the final source code of this Web API in this github repository.

Let’s start to create our mentioned Web API.

Before start

If you have never used Node.js or npm package manager you should install them.

To check whether the Node.js is already installed on your computer, open your terminal and run node -v command. If you see your Node.js version it's installed. Otherwise go to below link.

Click here to download and install Node.js (You can choose LTS version)

And if you don’t have any IDE or text editor for writing javascript I advice you Visual Studio Code.

Click here to download VS Code (Optional)

About express-generator

In fact we could use <a href="https://expressjs.com/en/starter/generator.html" target="_blank">express-generator</a> tool which designed to creating an Express Web API quickly but I want to create this API from scratch because of that tool puts some extra files and folder structures that we don't need them now. But you can use this useful tool next time on creating new Web API. I won't use it now due to keep article simple.

Creating Project

Go to your workspace root folder and create a new folder there named "todo-api".

Then create "package.json" and "server.js" files into "todo-api" folder like below.

package.json

{
    "name": "todo-api",
    "version": "1.0.0",
    "scripts": {
        "start": "node server.js"
    },
    "dependencies": {
        "express": "^4.16.4"
    }
}

server.js

const http = require('http');
const express = require('express');
const app = express();
app.use(express.json());
app.use('/', function(req, res) {
    res.send('todo api works');
});
const server = http.createServer(app);
const port = 3000;
server.listen(port);
console.debug('Server listening on port ' + port);

After creating above files open your terminal in the "todo-api" folder and run npm installcommand.

This command will be install your project dependencies which pointed at the "package.json" file.

After finished package download process, downloaded dependency files will be installed into"node_modules" folder at the root of the "todo-api" folder.

After finished package installing then run npm start to start our Web API.

Now our Web API listening. To see result open your web browser then write localhost:3000 to address bar and press enter.

As result you’ll see our request handler response in your browser: “todo api works”.

This is a dead simple Express.js Web API. And it needs the some development. For example we need to an api endpoint to get todo items. So let’s add a new API endpoint for this.

Create a new folder named "routes" in the root of the "todo-api" folder.

Then create a "items.js" file inside of "routes" folder and put following codes inside it.

Your final folder structure should be like below;

/todo-api
/node_modules
/routes
    items.js
package.json
server.js

items.js

const express = require('express');
const router = express.Router();
const data = [
    {id: 1, title: 'Finalize project', order: 1, completed: false, createdOn: new Date()},
    {id: 2, title: 'Book ticket to London', order: 2, completed: false, createdOn: new Date()},
    {id: 3, title: 'Finish last article', order: 3, completed: false, createdOn: new Date()},
    {id: 4, title: 'Get a new t-shirt', order: 4, completed: false, createdOn: new Date()},
    {id: 5, title: 'Create dinner reservation', order: 5, completed: false, createdOn: new Date()},
];
router.get('/', function (req, res) {
    res.status(200).json(data);
});
router.get('/:id', function (req, res) {
    let found = data.find(function (item) {
        return item.id === parseInt(req.params.id);
    });
    if (found) {
        res.status(200).json(found);
    } else {
        res.sendStatus(404);
    }
});
module.exports = router;

Initial code of "items.js" file contains two endpoints. First one gets all todo items and second one gets one item which matches given id parameter.

Before testing items routes we should register it in the "server.js" file.

Modify "server.js" file like below to register new item routes.

server.js

const http = require('http');
const express = require('express');
const itemsRouter = require('./routes/items');
const app = express();
app.use(express.json());
app.use('/items', itemsRouter);
app.use('/', function(req, res) {
    res.send('todo api works');
});
const server = http.createServer(app);
const port = 3000;
server.listen(port);
console.debug('Server listening on port ' + port);

Now run npm start to start our Web API.

Then open your web browser and write localhost:3000/items to address bar and press enter.

You’ll see todo items json array in the response body.

And write localhost:3000/items/3 to address bar and press enter.

You’ll see the todo item which has id 3 in the response body.

But not finished up yet.

CRUD Operations and HTTP methods

I think we’ll need CRUD operations to Create, Read, Update and Delete todo items.

We have already two endpoints for getting items. So we need Create, Update and Delete endpoints.

Let’s add also these endpoints into the items.js file.

Our final "items.js" file and endpoints should be like below.

const express = require('express');
const router = express.Router();

const data = [
  {id: 1, title: 'Finalize project',          order: 1, completed: false, createdOn: new Date()},
  {id: 2, title: 'Book ticket to London',     order: 2, completed: false, createdOn: new Date()},
  {id: 3, title: 'Finish last article',       order: 3, completed: false, createdOn: new Date()},
  {id: 4, title: 'Get a new t-shirt',         order: 4, completed: false, createdOn: new Date()},
  {id: 5, title: 'Create dinner reservation', order: 5, completed: false, createdOn: new Date()},
];

router.get('/', function (req, res) {
  res.status(200).json(data);
});

router.get('/:id', function (req, res) {
  let found = data.find(function (item) {
    return item.id === parseInt(req.params.id);
  });

  if (found) {
    res.status(200).json(found);
  } else {
    res.sendStatus(404);
  }
});

router.post('/', function (req, res) {
  let itemIds = data.map(item => item.id);
  let orderNums = data.map(item => item.order);

  let newId = itemIds.length > 0 ? Math.max.apply(Math, itemIds) + 1 : 1;
  let newOrderNum = orderNums.length > 0 ? Math.max.apply(Math, orderNums) + 1 : 1;

  let newItem = {
    id: newId,
    title: req.body.title,
    order: newOrderNum,
    completed: false,
    createdOn: new Date()
  };

  data.push(newItem);

  res.status(201).json(newItem);
});

router.put('/:id', function (req, res) {
  let found = data.find(function (item) {
    return item.id === parseInt(req.params.id);
  });

  if (found) {
    let updated = {
      id: found.id,
      title: req.body.title,
      order: req.body.order,
      completed: req.body.completed
    };

    let targetIndex = data.indexOf(found);

    data.splice(targetIndex, 1, updated);

    res.sendStatus(204);
  } else {
    res.sendStatus(404);
  }
});

router.delete('/:id', function (req, res) {
  let found = data.find(function (item) {
    return item.id === parseInt(req.params.id);
  });

  if (found) {
    let targetIndex = data.indexOf(found);

    data.splice(targetIndex, 1);
  }

  res.sendStatus(204);
});

module.exports = router;

Short Explanation

I wanna explain shortly some points of our last codes.

First of all you must have noticed that our api works on a static data and keeps it on memory. All of our GET, POST, PUT and DELETE http methods just manipulate a json array. The purpose of this is to keep article simple and draw attention to the Web API structure.

Due to this situation our POST method has some extra logic such as calculating next item ids and order numbers.

So you can modify logic and data structures in these http methods to use a database or whatever you want.

Testing API with Postman

We have tested the GET methods of our Web API in our web browser and seen responses. But we can’t test directly POST, PUT and DELETE http methods in web browser.

If you want to test also other http methods you should use Postman or another http utility.

Now I’ll show you how to test the Web API with Postman

Before we start click here and install Postman.

When you first launch Postman after installing you’ll see start window. Close this start window by clicking close button on top right corner. Then you must see following screen.

An empty Postman request

Sending GET Request

Before sending a request to API we should start it by running npm startcommand as we do before.

After start the Web API and seeing “Server listening on…” message write localhost:3000/itemsto address bar as seen below and click Send button. You'll see todo items array as API response like below.

Sending a GET request with Postman

You can try similarly by giving an item id in request url like this localhost:3000/items/3

Sending POST Request

To sending a POST request and create a new todo item write localhost:3000/items to address bar and change HTTP verb to POST by clicking arrow at front of the address bar as seen below.

Sending a POST request with Postman

Before sending the POST request you should add request data to body of the request by clicking body tab and selecting raw and JSON as seen below.

Attaching a JSON body to POST request in Postman

Now click Send button to send POST request to the Web API. Then you must get “201 Created” http response code and seeing created item in the response body.

To see the last status of todo items send a get request to localhost:3000/itemsaddress. You must see newly created item at the end of the list.

Sending PUT Request

Sending PUT request is very similar to sending POST request.

The most obvious difference is request url should be pointed specific item like this localhost:3000/items/3

And you should choose PUT as http verb instead of POST and send all of the required data in the request body unlike POST.

For example you could send a JSON body in the PUT request as below.

An example JSON body for PUT request

{
    "title": "New title of todo item",
    "order": 3,
    "completed": false
}

When you click Send button you must get “204 No Content” http response code. You can check item you updated by sending a get request.

Sending DELETE Request

To send a DELETE request, change the request url to address a specific item id like this localhost:3000/items/3

And select DELETE as http verb and click Send button.

You must get “204 No Content” http response code as result of the DELETE operation.

Send a get request and see the last status of list.

About the DELETE Http Request

I want to say a few words about DELETE http request. You must have noticed something in our delete code. DELETE request returns “204 No Content” every situation.

Http DELETE requests are idempotent. So what that mean? If you delete a resource on server by sending DELETE request, it’s removed from the collection. And every next DELETE request on the same resource won’t change outcome. So you won’t get “404 Not Found” in the second request. Each request returns same response whether succeed or not. That’s mean idempotent operation.

Conclusion

Finally we’ve tested all http methods of our Web API.

As you can see, it works just fine.

Thanks for reading ❤

If you liked this post, share it with all of your programming buddies!