sailsCasts

Learning about sails.js one screencast at a time.

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.

sailsCasts Answers: Ep6 - How Does the Http Request/response Protocol Work With Routes

| Comments

Transcript

Howdy and welcome back. This episode is the first of a three part series. We’ll start with how the http request/response protocol works with routes, controllers, actions and models to deliver a restful json CRUD api. By the end of the second episode you’ll use these concepts to build a restful json CRUD api from scratch. In the third episode we’ll explore how sail’s blueprint: actions and routes can be used to create that same restful json CRUD api automatically for any of your controllers and models.

In lieu of being able to download this episode directly to your brain matrix-style, I’ll instead provide a bird’s eye view of all of the concepts we’ll cover. Nothing is more confusing than trying to learn something new in the abstract. So let’s begin with a tangible scenario for our restful json CRUD api. 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 our client-side device to be able to make requests to a our api running on a server and for that api to respond to our client-side device’s requests. I’m using the term “client-side device” very loosely here because the device can reside on your lap, your phone, or even your refrigerator. If it can make a request it can use our api regardless of the type of client-side device.

So, what types of requests will we be making to the api? The request will interact with the api to either find, create, update or delete information about our sleep. The api will in turn be listening for our request and using a router, routes, controllers, and utlimately actions to interact with our sleep information in something called a model. The action will then respond to our client-side device with the status of our request and any additional information necessary to fulfill the request.

Now let’s break-down each concept in detail. First let’s strip down this diagram to the basics.

So how do request and responses work? The request and response are part of the http protocol. Don’t let the term protocol throw you. It’s just a set of agreed upon rules that make it possible for different types of devices to communicate together across a network.

So, why are we starting with the http protocol? Well, you’ve got to start somewhere and although we could have started at ohm’s law and worked our way up levels of abstraction to http, this would take an awfully long time and more importantly my knowledge once we go below the level of http drops rapidly to pretty much zero. As a tangent, in case you are a complete nerd like myself and would like to learn more about the very low levels of abstraction, there’s a great MIT course 6.002 available on youtube which introduces the fundamentals of the lumped circuit abstraction. But for our purposes anything below the http protocol we’ll just say consists of turtles all the way down.

Recall that our api will support requests to find, create, update, or delete sleep information.

But how will our api differentiate the requests? That is, how will the api know that we want to find versus create, update versus delete a set of sleep information?

The http protocol provides the means to accomplish this via the use of http verbs – get, post, put, and delete and each of the http verbs line up with the type of request.

Most likely you use http verbs every day. Each time you use your browser to open a web page, that browser is making a get request on your behalf.

As a quick aside you’ll often hear the term CRUD functions when working with a web api. CRUD stands for create, read, update and delete each of which match up nicely to our http verbs and actions.

The verb is the first of three essential parts of our request. The other elements are the path, which relates to our controller, and we’ll talk about in the next session and finally optional parameters which are just additional pieces of information we might want to send in our request.

The important take away here is we can now combine http verbs and paths to convey our intent to the api. By using 'post /sleep' the api knows ahh, create a new instance of sleep and by passing it parameters the api knows that we want to use those parameters as part of the sleep instance. How the api interprets this is the subject of the next section.

Before moving on though, you might be asking, how does my device generate a request. I’ve already said that when you request a web page in a browser you’re making a get request. Also most programming languages have a library associated with http. In javascript, the jquery library uses $.get to make requests.

Let’s take a quick field trip to check out a request in action.

I’ll open a chrome browser which is using a chrome extension called POSTMAN. POSTMAN allows me to make http requests using all of the verbs we were just talking about. Without it, browsers are limited to get requests from the url window or get and post requests from an html form. I’m making the request to a completed version of our sails api, but don’t worry about that for now. I just want to show you an actual request in action.

So I’m going to make a request using the verb get to the path /sleep. And when I send the request via POSTMAN I get back all 5 instances of our 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": "12",
        "sleep_quality": "great",
        "createdAt": "2014-01-03T00:29:55.921Z",
        "updatedAt": "2014-01-03T00:29:55.921Z",
        "id": 1
    },
    {
        "hours_slept": "8",
        "sleep_quality": "good",
        "createdAt": "2014-01-03T00:30:03.544Z",
        "updatedAt": "2014-01-03T00:30:03.544Z",
        "id": 2
    },
    {
        "hours_slept": "6",
        "sleep_quality": "so-so",
        "createdAt": "2014-01-03T00:30:13.998Z",
        "updatedAt": "2014-01-03T00:30:13.998Z",
        "id": 3
    },
    {
        "hours_slept": "5",
        "sleep_quality": "bad",
        "createdAt": "2014-01-03T00:30:21.294Z",
        "updatedAt": "2014-01-03T00:30:21.294Z",
        "id": 4
    },
    {
        "hours_slept": "3",
        "sleep_quality": "bad",
        "createdAt": "2014-01-03T00:30:25.998Z",
        "updatedAt": "2014-01-03T00:30:25.998Z",
        "id": 5
    }
]

So that’s it, that’s a request.

Now, by making the http request to a server, we’re indicating that some bit of code should be executed on the server as a result of making the request. The chunk of code that is executed is known as an action and these actions can be grouped together into a controller.

But how do we link the request to the controller and action? Well, that’s where routes come in.

Routes are the instructions that tie the request to the controller and ultimately to the action.

So from our earlier example, the route consists of the verb— post, the path— /sleep which forms the request and pairs it with the sleep controller and the create action.

Although not part of the route per se, as we’ve already seen parameters can also be part of the request and these parameters can pass information to the action.

So let’s review, we now understand how the request and response works with the http protocol, that the router uses routes to tie the requests to the controller and action.

So what does the action actually do? The action is where the code resides to find one or more model instances, create a new model instance, update an existing model instance, or delete a model instance.

What is a model? A model is a representation of the attributes that describe the data your api will be managing. Our sleep model consists of hours_slept, and sleep_quality and sails automatically adds an id, createdAt and updatedAt attributes. The sleep model is an object so in addition to model attributes like hours_slept, the sleep model also has methods like find, create, update, and destroy. These are the methods that are called within our actions. Now don’t be confused by the fact that the actions and model methods have the same name, they share the same name because the action name relates to the ultimate model method the action will use. As you’ll see when building the api in the next episode, the actions combine code necessary to complete the request including calls to the model methods.

Having this seperation between the model and the place where your data resides is important because you might want to store your model in a sql database at first but later move it to a mongo database. Or you might be using data that doesn’t come from a database but instead from some other api. That’s really the power of a framework like sails in that we can learn one way of finding, creating, updating and destroying our model, and then let sails worry about how it actually accesses and/or stores it at the level of a database or other data source. In fact, the sails community provides us with different adapters like posgresql and mongo from which we can pick and choose where our data ultimately resides at will.

What is the model versus model instances. Think of the model as the instructions for building something whereas an instance of that model is one of the things you’ve built.

So let’s finish this episode with looking at the response. Depending upon the request, our action is responsible for finding, creating, updating or deleting instances of our model. We’ve learned that the model methods are called by the action. The action then responds to the request with the status of the request along with any model instance or instances associated with the action.

If you’re new to this your head might start to spin a bit but don’t panic in the next episode we’ll use all of this conceptual knowledge to build our own restful json CRUD api.

if you get a chance, follow me on twitter @irlnathan and as always thanks for watching.

sailsCasts Answers: Ep5 - Where Should I Put the Assets in My Sails Project?

| Comments

Transcript

Howdy and welcome back.

So I’ve seen a bunch of questions asking where should you put your assets in your sail’s project. In this episode I’m going to talk specficially about javascript and css assets.

So, Sails uses grunt to manage assets. And Grunt generically provides ways for you to automate repeititve tasks. Sails uses grunt for tasks like syncing files between a project folder structure and the server’s public folder, but as always, I’m getting ahead of myself.

The configuration of grunt is based upon a file called Gruntfile.js found in the root of your sails project. There’s a lot going on in this file, however, again I’m going to concentrate on the javascript and css assets.

Your Project’s Assets

When you first create a project, you have the option of using the --linker flag. I’m going to actually create two projects, one with the linker flag and one without. Let’s compare the two. Both have an /assets folder. But the project with linker, not surprisingly has a /linker folder and that’s within the /assets folder. In the linker example, an additional /templates folder is also created.

USING the --linker flag

1
2
3
4
5
6
/assets
  /images
  /linker
    /js
    /styles
    /templates

NOT USING the --linker flag

1
2
3
4
/assets
  /images
  /js
  /styles

The Server’s Public Folder

Okay, great we have a /linker folder, but so what? Well, let’s add some javascript and css to both projects and see what happens. I’ve replaced the index.ejs file in the `/views/home’ folder of both projects with some very basic mark-up.

1
2
3
<h1>Custom View</h1>
<div class="custom">
</div>

Next, I added jquery to the /js folder of both projects. I also added my own javascript file — custom.js to the /js/ folder.

1
2
3
4
$(document).ready(function() {
    $('.custom').append("<h1>Yes Irl, javascript is here and if I'm red css is working as well.</h1>");
    $('.custom').addClass('working');
});

Finally I added custom.css in the /styles folder:

1
2
3
.working {
  color: red;
}

If the javascript and css are being linked properly, this is what our page should look like.

The custom.js file appends some html to the div in the index.js file. Then it will add a new class working to the div which will trigger the css located in the custom.css file.

Let’s take a look at the project where we used the --linker flag first. I’ll start sails using sails lift. The first thing you’ll notice is that a hidden .tmp folder is created. This is the server’s public folder. Opening up this folder reveals files that have been sync’d with the /assets folder. So we can see that jquery.js, custom.js, and custom.css files have all been copied into the public folder. Next, I’ll open up a browser and enter localhost:1337. Our heading showed up, but something’s up with my javascript because the appended html isn’t showing up. Let’s take a look at the console. Okay, there’s our problem. Jquery isn’t being loaded. Let’s take a look at the page’s source. Jquery is there, however, it’s being loaded in the wrong order. You might be asking yourself, how, did those links make it into page in the first place. That’s where some addition Grunt magic happens. Let’s take a look at our layout.ejs file.

Here is where the javascript links have been placed. Notice the two tags SCRIPTS and SCRIPTS END.

1
2
<!--SCRIPTS-->
<!--SCRIPTS END-->

This is where Grunt will place links to any javascript files placed in the /assets/linker/js folder. Grunt will also do this for our css between thse two tags, the STYLES and STYLES END:

1
2
<!--STYLES-->
<!--STYLES END-->

Now you may have noticed these socket.io.js, sails.io.js, and app.js files that are being injected into layout.ejs. Not surprisingly these files have to do with socket.io and sails, however, for the purposes of this screencast we can ignore those files.

But we still have this ordering problem with jquery. To fix this we can go back into the Grunfile.js and towards the top of this file is where Grunt is cofigured to inject css and javascript into layout.ejs. I’m going to put a path to jquery below some other javascript files but before this default “catch-all” link and I say catch-all link because as you can see with these astericks, grunt is going to look at the linker folder and any folder underneath the linker folder it’s going to look for any files with the .js ending. It’s going to take those files and place them in between these two tags.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
var jsFilesToInject = [

    // Below, as a demonstration, you'll see the built-in dependencies 
    // linked in the proper order order

    // Bring in the socket.io client
    'linker/js/socket.io.js',

    // then beef it up with some convenience logic for talking to Sails.js
    'linker/js/sails.io.js',

    // A simpler boilerplate library for getting you up and running w/ an
    // automatic listener for incoming messages from Socket.io.
    'linker/js/app.js',

    // *->    put other dependencies here   <-*

    'linker/js/jquery.js'

    // All of the rest of your app scripts imported here
    'linker/**/*.js'
  ];

So now jquery will be loaded before any other javascript files located in the assets/linker/js folder. Let’s restart the sails server and go back to the browser and reload the page. Great now links to both our javascript and css files are being injected correctly. And just to make sure let’s go back to the source. And yes, we can see here that jquery is now being loaded before custom.js.

So to sum it up, having the linker folder provides for the automatic injection of links to any javascript or css files that are placed in the ‘assets/linker/js’ or ‘assets/linker/styles’ folders. Grunt based upon Gruntfile.js injects the links in layout.ejs.

Now let’s look at the project that doesn’t have a linker folder. When I start the sails server for this project using sails lift, the hidden .tmp folder is also created. The javascript and css files are copied into the public folder this time without the /linker folder. I’ll open up a browser and again go to localhost:1337.

This time, however, we don’t have the appended text from the custom.js file nor the red color to the text from the custom.css file. Why is that? If we look at the layout.ejs file, you’ll notice that the STYLE and SCRIPT tags are missing. These are automatically inserted when using the linker flag. Therefore, grunt doesn’t have these tags to tell it where to put the javascript and css file paths.

We can “upgrade” this project simply by adding the tags in layout.ejs. We also need to remove the paths that were initially added to layout.ejs when the project was created. Now let’s go into Gruntfile.js and insert the path to jquery like we did in the linker example. Finally we’ll need to move the css and javascript assets we currently have under a /linker folder. Now when I start sails and open a browser, the appended html is displayed in red, just as we expected. Looking at the page source, the css and javascript were injected properly and in the correct order into the page.

I hope you found this helpful and thanks as always for watching.

sailsCasts Answers: Ep4 - Creating a More Attractive Url in Sails With slugs…really?

| Comments

Transcript

Howdy and welcome to another thought provoking, informative sailscasts answers. Okay, maybe that’s a bit of a stretch but welcome all the same. I’ve been asked a number of times how to implement a more attractive url system in sails…commonly using slugs.

Let’s take a look at an example. In activityOverlord when you’re on the user profile page, the url is something like this http://localhost:1337/user/show/5220fa7b8764043122000001. The ending part here is a mongoid. And that id is not very human friendly. What would be better is to have something like the person’s username. I’ll be doing a separate episode incorporating attrative urls into activityOverlord, however, in this screencast I’m going to show you how to do it generically for any project.

So I’ll create a new project called slugsville by entering sails new slugsville --linker with the linker flag. Next I’ll change into the slugsville folder and generate a user controller and model using sails generate user. So, let’s take a look at the user model. I’m going to paste in attributes for name, company, email, and phone as well as an attribute called slug.

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
module.exports = {

  attributes: {

      name: {
          type: 'string',
          required: true
      },

      slug: {

          type: 'string',
      },

      email: {
          type: 'string',
          email: true,
          required: true,
          unique: true
      },

      company: {
          type: 'string'
      },

      phone: {
          type: 'string'
      }

  }
};

Let’s switch to the user controller. I have a fairly generic create action that creates a user with params I’ll send through the Postman chrome extension and then we’ll return a json object. When the user is created, however, we need to put some logic in that will process the username removing any spaces and lower casing the string before saving the value into the slug model attribute. We’ll do this by adding a beforeCreate() method to our User model.

So going back to the User model, I’ll add the beforeCreate() method first chekcing whether name exists and then assigning the slug attribute the value of name with no spaces and all lowercase. Finally we’ll use the next() method to continue. Let’s see if that worked.

I’ll go into the terminal and start the sails server using sails lift. Next, we’ll go into the browser and using the postman chrome extension, I’ll create a new user with the following attributes. And great both the user and more importantly the slug were created.

So now let’s use this slug as a route parameter. I’ll head over into the `/config/routes.js file located in the config folder where we’ll create two routes.

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

  'get /:slug': 'UserController.profile',
  'get /user/:slug': 'UserController.profile',

  // By default, your root route (aka home page) points to a view
  // located at `views/home/index.ejs`
  // 
  // (This would also work if you had a file at: `/views/home.ejs`)
  '/': {
    view: 'home/index'
  }
};

Both /:slug and /user/:slug will bind themselves to the profile action of the User controller. In the User controller, I’ll create an action called profile. Next I’ll grab the slug param and assign it to the var slug. I want to let anything we catch with the slug param that has a dot in it like image.png to pass through without hitting our find method. That way we reduce the overhead of searching for a user for params we know are not a name. So if the param has a dot in it, we’ll return next() which will continue to the next piece of middleware (if any).

Next, we’ll try to find a user by the slug attribute passing in the slug variable we obtained form the param. If we don’t find a user, we’ll again return next(). If we do have a user I’m going to pass the entire user object to the view, in this case profile.ejs.

profile.ejs is a simple view template that displays the user name, company, email, and phone. Finally, I’m going to go back to the User controller and add a foo action to make sure that my action blueprint routes still work. If your not familiar with blueprint routes, I’m currently working on episode explaining blueprint routes, the first of which is devoted to action routes. Here I’m just adding the action foo that will return a 200 response.

Okay, let’s see if all of this worked? First, I’m going to create a few other users within postman. Here’s a list of our users. I can still access them via the id using /user/1. But now I can access them by their username either at /username or /user/username. I can also access my foo action.

I think that’s a much better approach. I’ve left a link to this project’s repo here if your interested and I hope that it was helpful and as always thanks for watching.