sailsCasts

Learning about sails.js one screencast at a time.

Building an Angular Application in Sails: Ep3 - Understanding Asset Delivery Options in Our SignUp Page.

| Comments

Follow me on twitter here.

Subscribe to the sailscasts mailing list here.

The repo for this episode can be found here.

Transcript

In activityOverlord v1.0 the user interface or Views of the app were built primarily on the server before being sent to the client and rendered by the client’s browser. This approach is the V of the MVC architecture and also known as Server Rendering even though the web page is actually being rendered on the client’s browser.

This so called Server Rendering is the server preprocessing one or more templates of markup, usually some combination of HTML, CSS, and Javascript and combining them with data via a Template Engine. All template engines use some form of tags that surround variables that when processed yield a result. Sails uses the EJS template engine by default. For example, this template contains some standard HTML mark-up along with EJS tags.

1
2
3
4
5
6
<!DOCTYPE html>
<html>
  <head>
    <title><%- title %></title>
  </head>
  ...

Between the tags is a variable title. When the Sails server processes this template it will attempt to replace the title variable with a value, hopefully the page title. This is also called String Interpolation, but I only mention that because I’m a geek.

Server Rendered Views

So let’s review this approach. When a client browser makes a request to the server, the router parses the request and determines where to send it. Now, the resulting route typically points to a controller/action that might execute some logic. For example, that logic might include accessing a mongo database that retrieves a stored twitter id and access token. The id and token are then used to request additional details of the user from the Twitter API, all before the server pre-processes a template which contains tags with variables that are replaced by the Twitter details into a View. The View is then sent back to the client and ultimately rendered by the browser.

Now, this traditional approach to web applications has at least two weaknesses. First, its reliance on the server for page creation means the responsiveness of the app is impeded by the constant round trips necessary to update changes to the View from the server. Second, is that the API being tightly coupled to the View makes it less flexible for other potential consumers to use.

A more modern approach to web applications solves both weaknesses by pushing the responsibility for changing the UI to the client as well as decoupling the API to act as an independent provider of endpoints. These endpoints can then be accessed by the browser UI, a native app UI, a mobile UI, or even a smart refrigerator UI.

Now this doesn’t mean that we’ll be abandoning Server Rendered Views entirely. Instead activityOverlordv2.0 will take a hybrid approach using a blend of Server Rendered Views combined with Front-end Framework Components to deliver a UI that makes authentication and SEO manageable. But as usual I’m getting ahead of myself.

So let’s go back to the project and see what Server Rendered Views look like in action. By default, Sails generated three files — homepage.ejs, layout.ejs, and a route to the homepage contained in /config/route.js. So when I make a request in the browser to localhost:1337, the Sails router looks at the request and matches it with the route in the routes file (e.g. route.js). This triggers the View Engine to pre-process layout.ejs with homepage.ejs to produce the View that’s being rendered by my browser. The Layout.ejs file is actually a wrapper around homepage.ejs. And what I mean by that is if we look at both files you can see that layout.ejs contains typical mark-up that we want for every page, things like DOCTYPE, html, and head tags. So we have EJS tags here with a body variable which when processed by the Sails View Engine is replaced with the contents of homepage.ejs . The result is our View.

Despite it’s power, and to make this example crystal clear, I’m going to disable the layout functionality in Views at least for the time being. To do this I’ll go into into /config/views.js and change the layout parameter from layout to false.

The first part of activityOverlord v2.0 consists of the Signup Page and when completed will look something like this. Let’s get started. I’ll first create a new file named startup.ejs and I’ll put a simple header in it <h1> Signup Page</h1>. Next, I’ll remove the existing route to the homepage and replace it with a route to the new The Signup Page:

1
'GET /signup': {view: 'signup'}

Let’s take a look. I’ll start Sails using sails lift and then navigate my browser to localhost:1337/signup. Okay good, here’s our signup page.

Let’s go back to the Signup Page and I’ll start by copying in some boilerplate html. Next, I’ll add our first Angular directives into the body tag — ng-app, ng-controller, and ng-cloak. Let’s refresh the browser and see what happens. Well nothing happens, because we haven’t yet added Angular yet. We could manually add the script tags that reference Angular, however, Sails can do this automatically for us using it’s own tagging system (e.g. <!--SCRIPTS--> tag). You know what, it’s easier just to show you. So I’ll add the tags to signup.ejs.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!DOCTYPE html>
<html>
  <head>
    <title>Sign Up for Activity Overlord (angular)</title>

    <!-- Viewport mobile tag for sensible mobile support -->
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">

  </head>

  <body ng-app="SignupModule" ng-controller="SignupController" ng-cloak>
    <h1>The Signup Page
    <!--SCRIPTS-->
    <!--SCRIPTS END-->
  </body>
</html>

I’ll restart the Sails server using Sails lift and then go back into the browser but also open up the browser console…okay let’s refresh the page and see what happens. Okay, that new, we get this console message that we’re “connected to Sails”, but where did that come from? If we look at the page source we’ll get a clue. Towards the bottom of the page a link to a file namedsails.io.js has been added to our signup page (e.g. signup.ejs). But where did that link come from? Let’s go into our project in Sublime and navigate to assets/js. In the dependencies foler there’s our sails.io.js file. First, this file deals with web sockets and socket.io, which we’ll be covering in later episodes. The big question now is, how did a link to that file get inserted into signup.ejs? The short answer is that Grunt did it for us. Okay, so what’s Grunt?

Grunt calls itself a JavaScript Task Runner but really Grunt is all about automation. It allows you to create repetive tasks that can be executed automatically. For example, there’s a Grunt task that’s looking for changes in the Sails assets folder. Sails creates a .tmp folder in the root of our project. As we can see here any files in the assets folder are copied into .tmp/public which acts like a static file folder you’d find on any web server. But what about the link to sails.io.js? This is yet another Grunt Task that will place a link to any javascript file found in assets/js that has the corresponding Script tags we just placed in signup.ejs (e.g. <!--SCRIPTS--><!--SCRIPTS END-->).

Of course all of these tasks are happening automatically without us having to be aware of them. However, for those of you who want a little more detail behind the magic, I created a doc that goes into much more specifics of the Grunt/Sails integration than I do in this screencast. The documentation can be found here.

So let’s go ahead and place the main Angular file in the assets/js/dependencies folder and I’ll refresh the browser. Looking at the page source we can see that a link to angular has been automatically placed in our signup page. But when I refreshed the browser, Angular is mad at us because we have some directives in the signup page without any associated javascript files. But not to worry, we can easily fix that. Let’s navigate back to the assets/js folder in Sublime and we’ll create a folder called public. We’ll put all of our Angular files that have to do with those parts of the application that don’t require authentication. Within public we’ll create a folder called signup. And within signup we’ll create two files SignupModule.jsandSignupController.js. Okay, first let's take a look atSignupModule.js` and I’ll add the bare bones Angular code to define our new module.

1
angular.module('SignupModule', []);

Next, I’ll open SignupController.js and add the bare bones Angular code to define our new controller.

1
angular.module('SignupModule').controller('SignupController', function(){});

Let’s go back to the browser and refresh the page. As you can see, we still have an Angular issue. If we look at the page source, we can see what the issue is, SignupController is getting loaded before SignupModule. This is easy to fix and touches on another aspect of Grunt. Most of the configuration for Grunt can be found within the activityOverlord20/tasks folder. For this issue we’re going to look in the root of the tasks folder for a file named pipeline.js. This file contains the configuration for how the SCRIPTS tags are used (e.g. <!--SCRIPTS-->) as well as some other tags we’ll be using shortly.

Next, I’ll open pipeline.js in Sublime. We want to load the SignupModule after dependencies but before any other javascript files. So we’ll put the path to our file here.

1
2
3
4
5
6
7
8
9
10
11
...

  // Dependencies like jQuery, or Angular are brought in here
  'js/dependencies/**/*.js',

  // All of the rest of your client-side js files
  // will be injected here in no particular order.
  'js/public/signup/SignupModule.js',
  'js/**/*.js'

...

Now when I refresh the browser, Angular no longer complains and if look back at the page source we can see that SignUpModule is being loaded before SignupController.

So let’s go back to signup.ejs in Sublime and insert the remainder of the markup for our Signup Page. Let’s start-up Sails using Sails lift and navigate the browser to localhost:1337/signup. Although the page loads without errors, there’s some obvious dependencies that need to be added in order for this page to look right. First I’ll add Bootstrap (e.g. bootstrap.css) to the /assets/sytles folder. Similar to what we did with javascript files in the previous episode, Sails will automatically include links to the CSS in signup.ejs. Taking a look at signup.ejs we can see the Styles tags that were added when I brought in the earlier mark-up as well as a link Grunt automatically created to the bootstrap file I just added.

1
2
3
4
5
6
...

    <!--SCRIPTS-->
    <!--SCRIPTS END-->
  </body>
</html>

Next, I’m going to create a fonts folder and add some fonts we’ll be using later in the interface.

I’m also going to add some .less files into the assets/styles folder that we’ll being using later in the interface as well.

You may have noticed the importer.less file which let’s us control which less files are included the Styles Tag as well as their order. Note that the Grunt task included in Sails will only compile the .less files that are referenced in this file.

I’m also be adding Jesús Rodríguez’s https://github.com/Foxandxss fantastic angular messaging library Angular-toastr. The library contains both a javascript and a css file.

Finally, I’ll add the CompareTo Angular Directive which will help us with comparing the value of form fields. I’ll place it in the /js/dependencies folder.

So let’s see where we are after these dependencies were added. I’ll head back to the browser refresh the page. The console displays an Uncaught reference error that Angular is not defined. Since this is coming from the toastr library my hunch is that we have a loading order error. And sure enough if we look at the page source, the toaster library is being loaded before Angular. I’ll head back to our project and pipeline.js in sublime and add a reference to Angular here (e.g. 'js/dependencies/angular.1.3.js',) that will load Angular first before any of the other depedencies.

Let’s head back to the browser and refresh the page. Great, there’s no longer and error.

Now let’s take a look at some form validation in signup.ejs. There are three components to the form field validation we’re going to perform. So taking a look at the name field. We first use the ng-class directive to create the has-error class if the field name is invalid and dirty. This will insert a red border around the input field. Next we’ll configure the validation parameters of the field itself. In this case we’re requiring that the field have a value as well as have a maxlength of 50 characters. Finally, we’ll set-up the text for our error message here.

1
2
3
4
5
6
<!-- Also, if signup.name.$dirty is true, show the message depending upon the particular properties truthiness (e.g. required
and/or maxlength) -->
<span class="help-block has-error" ng-if="signup.name.$dirty">
  <span ng-show="signup.name.$error.required">Name is required.</span>
  <span ng-show="signup.name.$error.maxlength">The name cannot be more than 50 characters.</span>
</span>

Let’s see this in action. I’ll go back to the browser and type in a name. Now If I remove the name, the required validation is triggered and if I add to many character’s the maxlength validation will be triggered.

Let’s go back to signup.ejs and review the remaining form fields configuration.

So the Title field’s configuration is identical to the name, the title is required and has a max length of 50 characters.

1
2
3
4
5
6
7
8
9
10
11
12
<!-- T I T L E -->

<div class="control-group form-group col-md-12"
  ng-class="{'has-error':signup.title.$invalid &&
                        signup.title.$dirty}">
  <label>Your title</label>
  <input type="text" class="form-control" placeholder="e.g. Genius" name="title" ng-model="signupForm.title" ng-maxlength="50" required>
  <span class="help-block has-error" ng-if="signup.title.$dirty">
    <span ng-show="signup.title.$error.required">Title is required.</span>
    <span ng-show="signup.title.$error.maxlength">The name cannot be more than 50 characters.</span>
  </span>
</div>

The Email field is required and requires a properly formatted email address.

1
2
3
4
5
6
7
8
9
10
11
12
<!-- E M A I L -->

<div class="control-group form-group col-md-12"
ng-class="{'has-error':signup.email.$invalid &&
                      signup.email.$dirty}">
  <label>Your email address</label>
  <input type="email" class="form-control" placeholder="nikola@tesla.com" name="email" ng-model="signupForm.email" required >
  <span class="help-block has-error" ng-if="signup.email.$dirty">
    <span ng-show="signup.email.$error.required">Email address is required.</span>
    <span ng-show="signup.email.$error.email">Not a valid email address.</span>
  </span>
</div>

The Password field is required and must be at least 6 characters. Also notice that we’re using the CompareTo directive to compare the password field with the confirmPassword model.

1
2
3
4
5
6
7
8
9
10
11
12
13
<!-- P A S S W O R D -->

<div class="control-group form-group col-md-6"
ng-class="{'has-error':signup.password.$invalid &&
                      signup.password.$dirty}">
  <label>Choose a password</label>
  <!-- Added the compareTo directive that compares the passowrds -->
  <input type="password" class="form-control" placeholder="at least 6 characters" name="password" ng-model="signupForm.password" id="password" required ng-minlength="6" compare-to="signupForm.confirmPassword" >
  <span class="help-block has-error" ng-if="signup.password.$dirty">
    <span ng-show="signup.password.$error.required">Password is required.</span>
    <span ng-show="signup.password.$error.minlength">Password must be at least 6 characters.</span>
  </span>
</div>

The Password Confirmation field is required and must match the Password Field.

1
2
3
4
5
6
7
8
9
10
<!-- C O N F I R M  P A S S W O R D -->

<div class="control-group form-group col-md-6">
  <label>Re-enter your password</label>
  <input type="password" class="form-control" placeholder="one more time" name="confirmation" ng-model="signupForm.confirmPassword" required>
  <span class="help-block has-error" ng-if="signup.confirmation.$dirty">
    <span ng-show="signup.password.$error.compareTo">Password must match.</span>
    <span ng-show="signup.confirmation.$error.required">Confirmation password is required.</span>
  </span>
</div>

Finally, we disable the form submission button via the ng-diabled directive until all of the form fields have valid values.

1
2
3
4
5
6
7
8
<!-- C O N F I R M  P A S S W O R D -->

<!-- Disable signup button until the form has no errors -->
<button class="btn btn-primary btn-lg btn-block" type="submit" ng-disabled="signup.$invalid">
  <span ng-show="!signupForm.loading">Create Account</span>
  <span class="overlord-loading-spinner fa fa-spinner" ng-show="signupForm.loading" ></span>
  <span ng-show="signupForm.loading">Preparing your new account...</span>
</button>

Okay let’s go back and take a look at the validations in action. I’m going to refresh the browser.

Believe it or not that rounds out our signup page. In the next episode we’ll start to flesh out our initial API that connects the signup page with an endpoint that creates a user account via a model into a database.

You can find all of the source code for this episode at the activityOverlord20 repo on github.

And If have a moment plese follow me on twitter and be sure to signup for the Sailscasts mailing list so I can finally prove to my wife that there are actually folks watching this stuff. As always thanks for watching.

Setting Up the Development Environment and Creating Your First Project

| Comments

Follow me on twitter here.

Subscribe to the sailscasts mailing list here.

Transcript

Howdy and welcome back.

As in the previous series, I’m going to assume you already have Node installed. If you don’t there’s a bunch of helpful documentation on nodejs.org and installers for both Mac OS X and Microsoft Windows can be found here as well as binaries for Linux and SunOS.

As for as my development enviornment, I’m using OS X v10.10.1 also known as Yosemite. For my text editor I’m using Sublime Text 2 which can be found here. There’s also an update to the editor, version 3, which I’m not currently using but can be found here. Another tool we’ll be using is the quite awesome chrome extension, known as – POSTMAN which can be found here.

And last but certainly not least I’ll be using Sails v0.11.0. Now, you may see me running some release candidates of Sails initially, however, because the release of Sails v0.11.0 is eminent, I’m not going to bother with how to install a release candidate and these initial screencasts are really not effected by the currently published version of v0.10.5. Okay let’s get Sails up and running by heading over to the terminal window.

From the last series, I received comments from a bunch of folks who had a real aversion to the terminal window. For those of you who are new to back-end programming, jumping into the terminal window might seem intimidating. Let me start by saying if I can understand it, believe me you can understand it. By the end of this series you’ll wonder how you got through your day without multiple visits to the terminal. For those of you on OS X you’ll find the terminal application is somewhat hidden in /Applications/Utilities/Terminal.app. One other potential point of confusion about the terminal is how it’s referred to. For example you’ll hear terminal window, prompt, command-line, shell, etc. For what it’s worth here is my attempt at terminology superimposed on this thing I’ll be calling the terminal.

With that said, let’s jump in.

To install sails, you simply type npm install sails -g, where the -g stands for global. Note, that because of the way ownership rights are set up on my machine, I have to use sudo or super duper user, actually according to wikipedia it means substitute user do….something like that. Regardless, it allows you to temporarily issue a command as a super or root user. After that Sails will start installing, so see ya in second.

So, in the last series of building activity overlord some people were confused about the distinction between installing sails globally and where Sails is installed when creating a new project. By installing Sails globally, we can have access to command-line tools from anywhere on the command-line.

For example, let’s create the initial Sails project for activityOverlord v2.0. From the terminal I’ll type sails new activityoverlord20.

So what just happened? Sails used the globally installed version of itself to generate all of the necessary initial structure for our application. This includes installing a copy of Sails itself inside the root of our application. That way, sails applications are completely self contained…there’s no “other software” outside of our project that we’re dependent upon.

Throughout these screencasts there will be times that I want to address specific changes that effect the previous activityoverlord screencasts with v0.11.0 of Sails. So if you don’t have previous experience or interest with older versions of Sails, now is the time to pick up that musical instrument or check some texts for the next few seconds. When I created the new Sails project you may have noticed that I didn’t use the --linker prarameter. That’s because there’s no longer a requirement to use it. By default, that functionality is built into every Sails project.

Now, let’s go back to the terminal and move into the new project by typing cd acitivtyoverlord20. Without changing any of the initial files or folders of the app I can start our newly created web server by typing sails lift and Sails confirms that it is indeed up and running. Next, I’ll open a browser at localhost:1337. The browser is accessing the default home page Sails generated when we initially built the project.

It’s important to take a moment here and realize what we’ve just accomplished. In just a few commands we’ve built the initial infrastructure of a web application which includes the creation of its own web server. We then have a browser, known as the client that is making a request for a previously generated home page through the Sails web server. The Sails server then response with the home page to be rendered in the browser.

Some of you may be confused by the address localhost:1337. Typically, when you browse the web you’ll enter a domain name like google.com…and that name actually resolves to an some ip address which maps to some computer out on the internt. Alright, localhost is default name for what is technically termed a loopback address. My machine’s localhost points to the address 127.0.0.1. What the loopback address allows allows us to do is bypass the outside network and address a specific web service on our local machine. In our case that web service is the Sails server. So when the Sails starts or lifts, by default, it’s given a port number of 1337. As we’ll see in a second we can change the port number if we want. Port numbers are the way in which we can differentiate one web service from another. For example, I currently have our activityoverlord20 project running on port 1337. I created another Sails project called foo that I’m going to lift on port 1338. When I go into the browser and open the address localhost:1338 the homepage that I altered comes up for the foo project. So the combination of localhost and the port number allows us to run both the server and client on the same machine while we’re building the project. To get completely geeky, and show you that localhost is really an arbitrary, there’s a file found on my mac at ~/etc/host that specifies what “points” to 127.0.0.1. As you can see, in addition to localhost I have the name yaya also pointing to 127.0.0.1 and yes we can go into the browser and type yaya:1337 and our homepage comes up.

Okay, I think I’ve beat that one to a pulp…but sometimes you know I just get carried away. In the next episode we’ll start building up the client ui of activityOverlord v2.0. So, see ya’ll in a bit.

Let’s Start a New Adventure

| Comments

Follow me on twitter here.

Subscribe to the sailscasts mailing list here.

Transcript

Howdy all…Happy New Year, and welcome.

Well for those of you who have listened to the previous activityOverlord screencasts the good news is I am armed with Keynote v6.5, which is filled with even more cheesey animations for me to abuse, the bad news is I’m even older than I was in the last series of screencasts. Seriously though I think I can overcome that and take you on another programming journey, this time updating Activity Overlord to v2.0. Now what’s in v2.0? The major updates include using a bunch of new features in v0.11 of Sails as well as updating the front-end UI to use AngularJS.

The current plan is to get the the original activityOverlord videos up to date ASAP. Time permitting I’ll continue making screencasts that take deeper dives into various concepts addressed in building the app. The videos will be hosted on youtube under the ponzicoder channel with transcripts available at the following gihub page. The source code is hosted in a repo on github here. If you like what you see and want to make an old guy happy, please follow me on twitter here and as important subscribe to the sailscasts mailing list here.

If you find bugs feel free to send a pull request to this github repo — http://github.com/irlnathan/activityoverlord20. If what I just said makes absolutely no sense, I’ll be covering git and github in future screencasts. If you have questions, there are a variety of forums to ask them. The Sails irc channel, Stackoverflow using the tag sailsjs, and the Sails google group are all active.

Needless to say I’m really excited about this new series…and as always thanks for watching.

sailsCasts Answers: Ep8 - How Do Blueprint: Actions and Blueprint: Routes Work in Sails?

| Comments

The repo for this episode can be found here: https://github.com/irlnathan/sails-crud-api-sailsAnswers8

Transcript

Howdy and welcome back. You’re watching the third and final installment of our three part series. In part one we learned how the http request/response protocol works with routes, controllers, actions and models to deliver a restful json CRUD api. In part two we took those concepts and built the api from scratch.

In this episode we’ll explore Sails blueprint: actions and routes, a powerful combination of functionality that are often used but not always fully understood. The goal of this episode is to show you what the blueprints are all about and how to use them to make your programming life a bit easier. So where are we going to start?

First a small warning here. This episode relies heavily on the previous two screencasts, so if you start to experience dizziness, tightness in the chest, or depression, I highly recommend you review those screencasts.

Let’s take a look at the roadmap of what we’ll be covering in this episode.

There are four pre-built blueprint: actions (e.g. find, create, update and destroy). As we’ll see in a minute, these four actions map directly to the CRUD api we buit in the last episode. There are also three blueprint: route types, blueprint: actions routes, blueprint: rest routes, and blueprint: shortcuts routes.

I want to make one thing very clear from the beginning. Blueprint: actions and blueprint: routes are virtualized in the sense that they are not explicitedly defined in controller files like SleepController.js or the routes.js file. Instead, they’re built-up when sails starts using sails lift. But as usual I’m getting ahead of myself.

Let’s start with seeing what happens when we combine blueprint: actions and blueprint: rest routes.

So in the last episode I went over in excrutiating detail, how to make a restful json CRUD api. The goal of api was to track our sleep patterns, specifically how much we sleep each night and the quality of that sleep. The best way to show you how blueprints help you automate the creation of an api is to repeat the process of building the restful json CRUD api, however, add the power of blueprints.

Let’s jump in here and create a new mySleep project. I’m in the terminal and will create the project using sails new mySleep --linker with the linker flag. Next, I’ll change into the mySleep folder and create a sleep model and controller using sails generate sleep. Finally, I’ll start the project using sails lift. Okay, we’re done, the api is complete.

But you DON’T believe me? After all we’ve been through. Well I guess I’ll have to prove it to you.

Seriously though, everything we did in the last episode was just built with those three commands.

I’ll open a browser and once again using the POSTMAN chrome extension I’ll make similar requests that we made in the last episode that relate to a CRUD api…

Let’s start with the Create portion of CRUD. I’ll add an instance to the sleep model using the http verb POST to the path /sleep adding two parameters hours_slept and sleep_quality. After sending the request the api returns our newly created record as json. In fact, I’ll make four more model instances inserting different values for hours_slept and sleep_quality.

Next we’ll try the Read portion of CRUD. Let’s get all of the model instances by making a GET request to the path /sleep. After sending the request the api returned all five instances of the model:

Next, let’s make a GET request to the path /sleep/2. The api returns a single instance of the model with an id of 2:

Now let’s try a request with some criteria. We’ll look for any model instances with an id not equal to 4, limited to 3 model instances and in descending order.

After making the request, the api returns three instances of the model in descending order.

Things are looking up for my assertion that our api was indeed complete.

Next up, we’ll try the Update portion of CRUD api. I’m going to make a put request to the path:

http://localhost:1337/sleep/3?added_attrib=12

After making the request, the api returns our instance of the model that has an id of 3 with our added attrib formatted as json.

Finally, we’ll try the Delete portion of our CRUD api. Once again within the POSTMAN chrome extension I’ll make a delete request to the path:

destroy http://localhost:1337/sleep/5

After sending the request the api responds with the model instance it just deleted formatted as json.

Let’s take a quick check of the controller we created in controllers/SleepController.js. Yep, nothing in it. Next let’s look at the config/routes.js file. Pretty much the same, nothing in there except the home route.

So at this point your probably wondering how in the heck are we able to use the same restful api end points that we used in the last episode BUT without any explicit actions are routes?

Is it mind control?

The really short answer is that we’re using blueprint: actions and blueprint: rest routes to automate the process of building the api. The next question is, how do they work?

When sails initially starts using sails lift, sails looks to see if you have any controller’s defined. In our example, we have one controller, the Sleep controller. Sails then provides access to blueprint: actions for this sleep controller as if we built them in the controller ourselves. Sails also automatically creates blueprint: rest routes that are identical to the routes we explicitedly created in the last episode. When combined together, the blueprint: actions and blueprint: rest routes, give us the exact functionality we had in our manually created json restful CRUD api without having to create anything other than a project, an empty controller, and an empty model.

So where are the blueprint: actions actually defined? Okay, nerd alert here, and when I say nerd alert, I’m starting with myself, you can see what the actions look like by taking a look at the sails source here on github. Looking at these actions they look very similar to the ones we created in the last episode. The difference is that sails handles all of this in the background, creating the necessary blueprint: rest routes that connect the actions to our controllers automatically. Again, this is all happening in memory without you having to explicitedly create anything.

Also, remember, that the mySleep project we’ve been working through uses a single controller. The blueprint: actions and blueprint: rest routes are not limited to a single controller so for a more complex project, with multiple controllers, sails blueprints: rest routes are built automatically for all of the controllers.

You can of course override any of the actions by explicitedly creating one of the actions in your controller. For example, I’ll go back to the SleepController, and create a find action that simply responds with “I’m your new explicit find action” upon request. Let’s restart the server and go back into the browser. I can still use the create, update, and delete actions, however, now when I make a get request to the path /sleep, I now receive our message .

So with sails you get the blueprint: actions and the blueprint: rest routes but wait there’s more! Sorry, couldn’t resist that. In addition, sails also provides blueprint: action routes, not to be confused with blueprint: actions.

blueprint: action routes speed up backend development and shorten the development workflow by eliminating the need to manually bind custom controller actions to requests through routes.

So when sails starts via sails lift, sails analyzes your controllers and if it finds an explicit action in a controller, it will bind GET, POST, PUT, and DELETE routes to the explicit action. For example, in our sleep project, I’ve added the action query to the sleep controller. So when sails starts, using sails lift, sails will automatically build the following blueprint: actions routes:

  • 'get /sleep/query/:id?: UserController.query'
  • 'post /sleep/query/:id?: UserController.query
  • 'put /sleep/query/:id?: UserController.query
  • 'delete /sleep/query:id?: UserController.query'

Let’s see it in action. I’ll open a browser and make a get request to the path /sleep/query. The query action responds with a view that can be found in views/sleep/query.ejs.

The important take-away here is that when I created a new action, in this case, the query action, I didn’t have to create a route to bind a request to that action. By using blueprint: actions routes sails did this for me automatically.

So let’s go back to our blueprint: roadmap. So far we’ve covered blueprint: CRUD actions as well as blueprint: rest routes and blueprint: actions routes. The final blueprint: route type is blueprint: shortcuts.

blueprint: shortcuts build routes that allow you to use the blueprint: actions from a browser. I use blueprint: shortcuts during development as a handy way to manipulate my underlying model.

The best way to see how this works is through an example.

I can grab a list of all my model instances using the url /sleep. I can create a new model instance using the url ‘/sleep/create’ and adding the parameters sleep_quality and hours_slept. I can also update that same instance using the url /sleep/update with the id of 8 and changing hours_slept from 10 to 9. Finally, I’ll delete the model instance by using the url /sleep/destroy/6.

One bit of caution. blueprint: shortcuts were not designed to be used in production. So how do we disable parts of the blueprints?

The blueprint: routes and blueprint: actions are completely configurable. That is, they can be disabled simply by setting the values of actions, rest, and/or shortcuts to false in the \config\controllers.js file. You can have even finer granularity by setting the same values in the _config object within each controller which will override what is in \config\controllers.js file.

Okay, we have a bunch of routes here, but how do they all fit together. So all of these different routes have an order of precedence or rank. When a request comes in sails first checks the explicit routes in routes.js. Next it will look to see if there’s a match in the blueprint: actions routes, followed by the blueprint: rest routes and finally, sails will see if there’s a match in the blueprint: shortcuts routes.

So if there’s a get request to the root route, sails will route it via routes.js to render views/home/index.ejs.

If there’s a get request to /sleep/query, sails, finding no match in routes.js, will look to the blueprint: action routes and finding a match will route the request to the explicit query action of the Sleep Controller.

If there’s a delete request to /sleep/5, sails, finding no match in routes.js or blueprint: action routes, will look to the blueprint: rest routes and finding a match will route the request to the blueprint: destroy action, returning a json object if successful.

Finally, if there’s a reqest to get /sleep/update/2 with some params, sails, finding no match in routes.js, blueprint: action nor rest routes, will look to the blueprint: shortcut routes and finding a match will route the request to the blueprint: CRUD update action, passing any params to the model’s update method returning a json object if successful.

In this series we’ve learned the concepts of how to use the http request/response protocol with routes, controllers, actions and models to deliver a restful json CRUD api. Using those concepts we’ve built the api from scratch. Hopefully, after this episode it becomes apparent that blueprint: actions and routes are really about automation. That is, eliminating the necessity of writing, at least initially, repetitive actions and routes during development.

We’ve covered a lot of material and I hope you’ve found it helpful. As always thanks for watching and if you get a chance follow me on twitter at irlnathan.

sailsCasts Answers: Ep7: How Do I Create a Restful Json CRUD Api in Sails From Scratch?

| Comments

The repo for this project can be found here: https://github.com/irlnathan/sails-crud-api

Transcript

Howdy and welcome back to part II of our three part series. In the last episode we learned how the http request/response protocol works with routes, controllers, actions and models to deliver a restful json CRUD api. In this episode we’ll take the concepts we learned and use them to build the api from scratch. In the final episode we’ll explore how sail’s blueprints: actions and routes can be used to create that same restful json CRUD api automatically for any of your controllers and models.

Let’s review what we’re trying to accomplish.

Our api will be used to access and update information that tracks our sleep patterns including how much we sleep each night and the quality of that sleep.

So we want the api to be able to respond to requests to find, create, update or delete instances of our sleep model. We’ll create actions that corresond to the requests and then build up routes that match the appropriate http verbs and paths with the corresponding controller and action.

  • So the find request will use the http verb get with the path /sleep/:id? and bind to the sleep controller and find action.
  • The create request will use the verb post with the path /sleep and bind to the sleep controller and the create action.
  • The update request will use the verb put with the path /sleep/:id? and bind to the sleep controller and the update action.
  • and finally, the delete request will use the verb delete with the path /sleep/:id? and bind to the sleep controller and destroy action.

The actions will then use the model methods to find, create, update or destroy the model as requested and use the parameters hours_slept and sleep_quality to pass any necessary information within the request through the action to the model. The action will then respond with the request status as well as any model instance or instances required.

So let’s get started. I’m going to bring up a terminal window and we’re going to create a new sails project called mySleep using sails new mySleep --linker. and I’ll change into the mySleep folder and generate a sleep controller and model using sails generate sleep.

So, here’s a roadmap of what we’re going to build. I’m going to start with the create action, building the action and then building the route that will bind the find request with the sleep controller and find action. I’m going to go through each action, create it, and then build the matching route that will bind our request to the controller and action. So let’s start with the create action.

I’ll open my sleep controller found in /api/controllers/SleepController.js and create my first action called create:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// a CREATE action  
create: function(req, res, next) {

    var params = req.params.all();

    Sleep.create(params, function(err, sleep) {

        if (err) return next(err);

        res.status(201);

        res.json(sleep);

    });
}

The action is straightforward, we’re going to grab the request’s parameters in the var params and then pass params into the create method of our sleep model. If there’s an error we’ll return it and if not I’ll send a 201 status code response with the newly created model instance formatted as json.

So that’s the create action, now I need to create a route that will bind this controller and action to our request. So let’s open the routes in /config/routes.js and I’ll add my route after the existing home route:

1
2
3
4
5
6
7
8
9
10
module.exports.routes = {

  '/': {
    view: 'home/index'
  },

  // Custom CRUD Rest Routes
  'post /sleep': 'SleepController.create'

};

The route consists of the verb post to the path /sleep which is bound to the sleep controller and the create action. So let’s make sure our create action is working. I’ll go into the terminal, start sails with sails lift. I’ll again be using the POSTMAN chrome extension to test our requests. We’ll be using the http verb POST to the path /sleep adding two parameters hours_slept and sleep_quality. When I click send, Sails returns my newly created record as json.

1
2
3
4
5
6
7
{
    "hours_slept": "8",
    "sleep_quality": "good",
    "createdAt": "2013-12-10T21:31:00.442Z",
    "updatedAt": "2013-12-10T21:31:00.442Z",
    "id": 1
}

So let’s take a look at our api roadmap. We’ve built the create action as the first of the four actions of our api. Next, we’ll build the find action and then we’ll build a route that will bind Sleep controller and find action to our request. For the action let’s go back into the SleepController.js file and look at the find action code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
   // a FIND action
find: function (req, res, next) {

  var id = req.param('id');

  var idShortCut = isShortcut(id);

  if (idShortCut === true) {
      return next();
  }

  if (id) {

      Sleep.findOne(id, function(err, sleep) {

          if(sleep === undefined) return res.notFound();

          if (err) return next(err);

          res.json(sleep);

      });

  } else {

      var where = req.param('where');

      if (_.isString(where)) {
              where = JSON.parse(where);
      }

      // This allows you to put something like id=2 to work.
      // if (!where) {

      //     // Build monolithic parameter object
   //    params = req.params.all();

   //    params = _.omit(params, function (param, key) {

   //        return key === 'limit' || key === 'skip' || key === 'sort'

   //    });

      //   where = params;

      //   console.log("making it here!");

      // }

      var options = {
                  limit: req.param('limit') || undefined,
                  skip: req.param('skip')  || undefined,
                  sort: req.param('sort') || undefined,
                  where: where || undefined
          };

          console.log("This is the options", options);
              
      Sleep.find(options, function(err, sleep) {

          if(sleep === undefined) return res.notFound();

          if (err) return next(err);

          res.json(sleep);

      });

  }

  function isShortcut(id) {
      if (id === 'find'   ||  id === 'update' ||  id === 'create' ||  id === 'destroy') {
      return true;
      }
  }

},

Let’s also take a look at the route that will bind our request to the sleep controller and find action in /config/routes.js:

'get /sleep/:id?': 'SleepController.find'

The route points to our find action but look the end of the path, what’s up with :id?, and the question mark? The question mark makes the id parameter optional. That way we capture both the request 'get /sleep' as well as 'get /sleep/:id'.

The find action will be our most complex action of the four in our api. This is because we have to provide for a request finding a single instance of the model, multiple instances of the model, as well as using criteria and options to narrow and/or limit the scope of the find request.

So within our find action, we’ll attempt to assign a parameter called id to the var id. The next line of code looks to see if the id is a shortcut. I’m going to skip over this part because shortcuts are part of sail’s blueprints which we’ll be discussing in the third episode.

So if the id exists we’re going to assume that the request is looking for a particular model instance. We’ll pass the id to the findOne model method and if we don’t get back an instance of sleep in the callback, we’ll return or respond with a status code of 404not found. On success, we’ll return and respond with the model instance formatted as json.

Checking for multiple model instances. If no id is provided we’ll start looking for other criteria or options that may have been passed as a parameter for finding one or more model instances. Criteria is placed in a where clause which is just the key name for a criteria object. For example, if your want to find all model instances where sleep_quality = good, your parameters would look like this: ?where={sleep_quality: "good"}. We’ll also check for options that further limit the result in some way. For example, let’s say we only want the first 5 model instances of our result. The parameters would look like this: ?where={sleep_quality: "good"}&limit=5.

So if where exists as a parameter and the value for it is a string, we’ll just parse it as json and assign it to the var where. Even if where doesn’t exist we’ll still look for the keys limit, skip, and sort and place them within the options object. Finally, we’ll pass the options object to the Find model method and if we don’t get back an instance of sleep in the callback, we’ll return or respond with a status code of 404not found. On success, we’ll return and respond with the model instance(s) formatted as json.

So we have the find action complete, let’s make sure all of this works. I’ll head back to the terminal and restart the sails server using sails lift and then open a browser with the POSTMAN chrome extension. I’ve added a few more instances of our sleep model. Let’s take a look by sending a get request to the path /sleep. After sending the request the api returned five instances of the model:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
[
    {
        "hours_slept": "8",
        "sleep_quality": "good",
        "createdAt": "2014-01-09T23:36:01.552Z",
        "updatedAt": "2014-01-09T23:36:01.552Z",
        "id": 1
    },
    {
        "hours_slept": "12",
        "sleep_quality": "great",
        "createdAt": "2014-01-11T05:08:52.398Z",
        "updatedAt": "2014-01-11T05:08:52.399Z",
        "id": 2
    },
    {
        "hours_slept": "4",
        "sleep_quality": "poor",
        "createdAt": "2014-01-11T05:09:10.319Z",
        "updatedAt": "2014-01-11T05:09:10.319Z",
        "id": 3
    },
    {
        "hours_slept": "6",
        "sleep_quality": "so-so",
        "createdAt": "2014-01-11T05:09:20.456Z",
        "updatedAt": "2014-01-11T05:09:20.456Z",
        "id": 4
    },
    {
        "hours_slept": "10",
        "sleep_quality": "good",
        "createdAt": "2014-01-11T05:09:30.885Z",
        "updatedAt": "2014-01-11T05:09:30.885Z",
        "id": 5
    }
]

Since we didn’t provide an id or any criteria or options, the api used the find model method and returned all instances of the model formatted as json.

Next, let’s make a get request to the path /sleep/2. After pressing send, the api returns a single instance of the model with an id of 2:

1
2
3
4
5
6
7
{
    "hours_slept": "12",
    "sleep_quality": "great",
    "createdAt": "2014-01-11T05:08:52.398Z",
    "updatedAt": "2014-01-11T05:08:52.399Z",
    "id": 2
}

Now let’s try a request with some criteria. We’ll look for any model instances with an id greater than 1:

1
2
3
4
localhost:1337/sleep?where={
    "id": {
        ">":  1}
}

After making the request, the api returns four of the five model instances with id’s greater than 1.

Finally, I’m going to combine the criteria with some options. I’m going to make a get request to the path /sleep for model instances with an id not equal to 4, limited to 3 model instances and in descending order.

1
2
3
4
localhost:1337/sleep?where={
    "id": {
        "!":  4}
}&limit=3&sort=id desc

After making the request, the api returns three instances of the model in descending order.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
[
    {
        "hours_slept": "10",
        "sleep_quality": "good",
        "createdAt": "2014-01-11T05:09:30.885Z",
        "updatedAt": "2014-01-11T05:09:30.885Z",
        "id": 5
    },
    {
        "hours_slept": "4",
        "sleep_quality": "poor",
        "createdAt": "2014-01-11T05:09:10.319Z",
        "updatedAt": "2014-01-11T05:09:10.319Z",
        "id": 3
    },
    {
        "hours_slept": "12",
        "sleep_quality": "great",
        "createdAt": "2014-01-11T05:08:52.398Z",
        "updatedAt": "2014-01-11T05:08:52.399Z",
        "id": 2
    }
]

Now that we know that our find action is battle tested, let’s go back to our api roadmap. By building the create action and route and the find action and route we’re half way through our api. Next, we’ll build the update action and then we’ll build a route that will bind the Sleep controller and update action to our request. Let’s head back into the SleepController.js file and look at the update action code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// an UPDATE action
    update: function (req, res, next) {

        var criteria = {};

        criteria = _.merge({}, req.params.all(), req.body);

        var id = req.param('id');

        if (!id) {
            return res.badRequest('No id provided.');
        }

        Sleep.update(id, criteria, function (err, sleep) {

            if(sleep.length === 0) return res.notFound();

            if (err) return next(err);

            res.json(sleep);

        });
    },

The update action consists of finding the id of the model instance to update coupled with the criteria that will be updated. If there’s no id as a parameter we respond with a 400 status— ‘No id provided’. Next we attempt to update the model instance using the id and criteria provided. If there’s an error we’ll return it and if not respond with the updated model instance formatted as json.

So now that we have the update action complete, we’ll bind that action to the request forming a new update route:

'put /sleep/:id?': 'SleepController.update'

The route points to our update action and uses the same :id? pattern that we used in the find route.

Let’s make sure all of this works. I’ll restart the sails server using sails lift and then open a browser with the POSTMAN chrome extension. I’m going to first make a put request to the path:

http://localhost:1337/sleep/3?added_attrib=12

After making the request, the api returns our instance of the model that has an id of 3 with our added attrib formatted as json.

Next, I’ll make a put request to:

1
2
3
4
http://localhost:1337/sleep/3
{
  "added_3": 42
}

…but instead of using query parameters, I’ll pass the update via the request body. After making the request, the api returns our instance of the model that has an id of 3 with our added_3 attribute formatted as json.

1
2
3
4
5
6
7
8
  // Custom Action Route
  'get /sleep/new': 'SleepController.new',

  // Custom CRUD Rest Routes
  'get /sleep/:id?': 'SleepController.find',
  'post /sleep': 'SleepController.create',
  'put /sleep/:id?': 'SleepController.update',
  'delete /sleep/:id?': 'SleepController.destroy',

Now that update action and route is complete it’s time to build the last action of our api the destroy action and then bind it to our request to form the delete route. Let’s head back into the SleepController.js file and look at the destroy action code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// a DESTROY action
    destroy: function (req, res, next) {

        var id = req.param('id');

        if (!id) {
            return res.badRequest('No id provided.');
        }

        Sleep.findOne(id).done(function(err, result) {
            if (err) return res.serverError(err);

            if (!result) return res.notFound();

            Sleep.destroy(id, function (err) {

                if (err) return next (err);

                return res.json(result);
            });

        });
    },

So we’ll attempt to assign the id param to a var called id. If it doesn’t exist I’ll return a 400— ‘No id provided’. If an id parameter was provided in the request, I’ll attempt to find it in the sleep model. If the model doesn’t exist I’ll respond with a status code of 404not found. If the mode instance does exist, I’ll pass the id to the destroy method of the model returning either an error if any, or the deleted model instance formatted as json.

Next I’ll bind the destroy action with the request in its own delete route:

'delete /sleep/:id?': 'SleepController.destroy'

Let’s check it out by restarting the sails server using sails lift. Once again within the POSTMAN chrome extension I’ll make a delete request to the path:

destroy http://localhost:1337/sleep/5

After sending the request the api responds with the model instance it just deleted formatted as json.

Congratulations, you’ve built a restful json CRUD api. Any client-side device that supports http requests can now hit our api’s endpoints and request and submit information about our sleep model.

In the next and final episode of this series I’ll show you how sail’s blueprints: actions and routes can be used to create this same restful json CRUD api we just created, automatically for any of your controllers and models.

Okay that was a lengthy one, As always, thanks for watching and if you get a chance, follow me on twitter @irlnathan.