sailsCasts

Learning about sails.js one screencast at a time.

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.

Comments