©  Caio Ribeiro Pereira 2016

Caio Ribeiro Pereira, Building APIs with Node.js , 10.1007/978-1-4842-2442-7_6

6. CRUDify API Resources

Caio Ribeiro Pereira

(1)São Vicente - SP, São Paulo, Brazil

In this chapter, we’ll continue to explore the new functions from Sequelize and also organize the API’s routes and some middlewares of Express. The create, read, update, delete (CRUD) will be built using the Tasks and Users models.

Organizing Task Routes

To start this refactoring, let’s explore the main HTTP methods to CRUDify our application. In this case, we are going to use the functions app.route("/tasks") and app.route("/tasks/:id") to define the path: "/tasks" and "/tasks/(task_id)".

These functions allow us to use nested functions, reusing the routes through all HTTP methods provided by the following Express functions.

  • app.all(): A middleware that is executed via any HTTP method call.

  • app.get(): Executes the method GET from HTTP , and is used to search and get some set of data from one or multiple resources.

  • app.post(): Executes the method POST from HTTP , and is semantically used to send and persist new data into a resource.

  • app.put(): Executes the method PUT from HTTP , and is used to update the data of a resource.

  • app.patch(): Executes the method PATCH to HTTP , and has similar semantics to the PUT method, but is recommended only to update some attributes of a resource, not the entire data of a resource.

  • app.delete(): Executes the method DELETE from HTTP , as its name implies, to delete particular data of a resource.

To better understand the use of these routes, we are going to edit routes/tasks.js, applying the needed functions to CRUDify the tasks resource.

 1   module.exports = app => {
 2     const Tasks = app.db.models.Tasks;
 3   
 4     app.route("/tasks")
 5       .all((req, res) => {
 6         // Middleware for preexecution of routes
 7       })
 8       .get((req, res) => {
 9         // "/tasks": List tasks
10       })
11       .post((req, res) => {
12         // "/tasks": Save new task
13       });
14   
15     app.route("/tasks/:id")
16       .all((req, res) => {
17         // Middleware for preexecution of routes
18       })
19       .get((req, res) => {
20         // "/tasks/1": Find a task
21       })
22       .put((r eq, res) => {
23         // "/tasks/1": Update a task
24       })
25       .delete((req, res) => {
26         // "/tasks/1": Delete a task
27       });
28   };

With this basic structure, we already have an outline of the needed routes to manage tasks. Now, we can implement their logics to correctly deal with each action of our tasks CRUD.

On both endpoints ("/tasks" and "/tasks/:id") we are going to deal with some rules inside their middlewares via the app.all() function to avoid some invalid access if somebody sends the task id inside the request’s body. This is going to be a simple modification, which should look like this.

 1   app.route("/tasks")
 2     .all((req, res, next) => {
 3       delete req.body.id;
 4       next();
 5     })
 6     // Continuation of routes...
 7   
 8   app.route("/tasks/:id")
 9     .all((req, res, next) => {
10       delete req.body.id;
11       next();
12     })
13     // Continuation of routes...

We are practically ensuring the exclusion of the id attribute within the request’s body, via delete req.body.id. This happens because, on each request function, we are going to use req.body as a parameter for Sequelize.js functions, and the attribute req.body.id could overwrite the id of a task, for example, on update or create for a task.

To finish the middleware, warning that it should perform a corresponding function to an HTTP method, you just need to include at the end of the callback the next() function to warn the Express router that now it can execute the next function on the route or the next middleware below.

Listing Tasks Via GET

We already have a basic middleware for treating all the task’s resources, now let’s implement some CRUD functions, step by step. To start, we’ll use the app.get() function, which is going to list all data from tasks models, running the Tasks.findAll() function.

 1   app.route("/tasks")
 2     .all((req, res, next) => {
 3       delete req.body.id;
 4       next();
 5     })
 6     .get((req, res) => {
 7       Tasks.findAll({})
 8         .then(result => res.json(result))
 9         .catch(error => {
10           res.status(412).json({msg: error.message});
11         });
12   })

In this first implementation, we are listing all the tasks running: Tasks.findAll({}). Although it is a bad practice to list all data, we are only using it for didactic matters. Throughout the chapters that follow, however, some arguments will be implemented to make this list of tasks more specific. The search results occur via the then() function, and any problems that happen can be handled via the catch() function. An important detail about an API’s development is to treat all or the most important results using the correct HTTP status code, in this case a successful result will return the 200 - OK status by default. On our API, to simplify things, several common errors will use the 412 - Precondition Failed status code.

About HTTP Status

There are no rules to follow in the HTTP status definition; however, it’s advisable to understand the meaning of each status to make your server’s response more semantic. To learn more about HTTP status codes, visit www.w3.org/Protocols/rfc2616/rfc2616-sec10.html .

Creating Tasks Via POST

There is no secret in the implementation of the POST method. Just use the Tasks.create(req.body) function and then treat their Promises callbacks: then() and catch().

One of the most interesting things on Sequelize is the sanitization of parameters you send to a model. This is very good, because if the req.body has some attributes not defined in the model, they will be removed before inserts or updates are made to a model. The only problem is the req.body.id that could modify the id of a model. However, we already handled this when we wrote a simple rule into the app.all() middleware. The implementation of this route will be like this piece of code.

 1   app.route("/tasks")
 2     .all((req, res, next) => {
 3       delete req.body.id;
 4       next();
 5     })
 6     .get((req, res) => {
 7       Tasks.findAll({})
 8         .then(result => res.json(result))
 9         .catch(error => {
10           res.status(412).json({msg: error.message});
11         });
12     })
13     .post((req, res) => {
14     Tasks.create(req.body)
15       .then(result => res.json(result))
16       .catch(error => {
17         res.status(412).json({msg: error.message});
18       });
19   });

Finding a Task Via GET

Now we are going to treat the /tasks/:id endpoints. To do this, let’s start with the implementation of the app.route("/tasks/:id") function, which has the same middleware logic as the last app.all() function.

To finish, we’ll use the function Tasks.findOne({where: req.params}), which executes, for example, Tasks.findOne({where: {id: "1"}}). It does a single search of tasks based on the task id. If there isn’t a task, the API will respond with the 404 - Not Found status code via the res.sendStatus(404) function. It should look like this.

 1   app.route("/tasks/:id")
 2     .all((req, res, next) => {
 3       delete req.body.id;
 4       next();
 5     })
 6     .get((req, res) => {
 7       Tasks.findOne({where: req.params})
 8         .then(result => {
 9           if (result) {
10             res.json(result);
11           } else {
12             res.sendStatus(404);
13           }
14         })
15         .catch(error => {
16           res.status(412).json({msg: error.message});
17         });
18   })

Updating a Task Via PUT

Now we are going to implement a function to update a task in the database. To do this, there is no secret: Just use the Task.update() function with a first parameter in which you have included an updated object and, in the second one, an object with parameters to find the current task to be updated. These functions will return a simple array with a number of changes made. This information won’t be very useful as a response, though, so to simplify things, let’s force the 204 - No Content status code using the res.sendStatus(204) function, which means that the request was successful and no content will be returned in the response. This implementation looks like this.

 1   app.route("/tasks/:id")
 2     .all((req, res, next) => {
 3       delete req.body.id;
 4       next();
 5     })
 6     .get((req, res) => {
 7       Tasks.findOne({where: req.params})
 8        .then(result => {
 9          if (result) {
10            res.json(result);
11          } else {
12            res.sendStatus(404);
13          }
14        })
15        .catch(error => {
16          res.status(412).json({msg: error.message});
17        });
18     })
19     .put((req, res) => {
20       Tasks.update(req.body, {where: req.params})
21         .then(result => res.sendStatus(204))
22         .catch(error => {
23           res.status(412).json({msg: error.message});
24         });
25     })

Just like the Tasks create() function, the Tasks.update cleans the fields that are not on its own model, so there is no problem sending req.body directly as a parameter.

Deleting a Task Via DELETE

To finish, we have to implement a route to delete a task, and once more, there is no secret here. You just have to use the Tasks.destroy() function, which uses as arguments an object containing data to find and delete a task. In our case, we’ll use req.params.id to remove a single task and, as a successful response, uses 204 - No Content as status code via the res.sendStatus(204) function:

 1   app.route("/tasks/:id")
 2     .all((req, res, next) => {
 3       delete req.body.id;
 4       next();
 5     })
 6     .get((req, res) => {
 7       Tasks.findOne({where: req.params})
 8         .then(result => {
 9           if (result) {
10             res.json(result);
11           } else {
12             res.sendStatus(404);
13           }
14         })
15         .catch(error => {
16           res.status(412).json({msg: error.message});
17         });
18     })
19     .put((req, res) => {
20       Tasks.update(req.body, {where: req.params})
21         .then(result => res.sendStatus(204))
22         .catch(error => {
23           res.status(412).json({msg: error.message});
24         });
25     })
26     .delete((req, res) => {
27       Tasks.destroy({where: req.params})
28         .then(result => res.sendStatus(204))
29         .catch(error => {
30           res.status(412).json({msg: error.message});
31         });
32     });

So, we have finish to CRUDifying the task’s resources of our API.

Refactoring Some Middlewares

To avoid code duplication, we’ll apply a simple refactoring migrating the business logic used inside the app.all() function to an Express middleware, using the app.use() function, into the lib-s/middlewares.js file. To apply this refactoring, first, remove the function app.all() from the routes/tasks.js file, leaving the code with the following structure:

 1   module.exports = app => {
 2     const Tasks = app.db.models.Tasks;
 3   
 4     app.route("/tasks")
 5       .get((req, res) => {
 6         // GET /tasks callback...
 7       })
 8       .post((req, res) => {
 9         // POST /tasks callback...
10       });
11   
12       app.route("/tasks/:id")
13         .get((req, res) => {
14           // GET /tasks/id callback...
15         })
16         .put((req, res) => {
17           // PUT /tasks/id callback...
18         })
19         .delete((req, res) => {
20           // DELETE /tasks/id callback...
21         });
22   };

To enable a JSON parse inside all the API’s routes, we must install the body-parser moduleby running this command.

1   npm install [email protected] --save

After this installation, open and edit the libs/middlewares.js file to insert the bodyParser.json() function as middleware, and in the end, include the middleware exclusion logic using the app.use() callback, following this code.

 1   import bodyParser from "body-parser";
 2   
 3   module.exports = app => {
 4     app.set("port", 3000);
 5     app.set("json spaces", 4);
 6     app.use(bodyParser.json());
 7     app.use((req, res, next) => {
 8       delete req.body.id;
 9       next();
10     });
11   };

Creating Users’ Endpoints

We also have to create routes to manage users in the application. After all, without them, it becomes impossible to manage tasks.

Our CRUD of users is not going to have anything new. In fact, it won’t be a complete CRUD, because we just need to create, find, and delete a user, so it won’t be necessary to include the app.route() function. Each route will be directly called by its corresponding HTTP method. The code will follow a similar treatment as the task’s routes has. The only new thing that will be used is the attributes: ["id", "name", "email"] parameter inside the Users.findById() function. This parameter allows us to return some selected fields from a model’s result instead of the full data of a model. It’s similar to running the SQL query:

1   SELECT id, name, email FROM users;

To code the users’ routes, creates the routes/users.js file, using this code.

 1   module.exports = app => {
 2     const Users = app.db.models.Users;
 3   
 4     app.get("/users/:id", (req, res) => {
 5       Users.findById(req.params.id, {
 6         attributes: ["id", "name", "email"]
 7       })
 8       .then(result => res.json(result))
 9       .catch(error => {
10         res.status(412).json({msg: error.message});
11       });
12     });
13   
14     app.delete("/users/:id", (req, res) => {
15       Users.destroy({where: {id: req.params.id} })
16         .then(result => res.sendStatus(204))
17         .catch(error => {
18           res.status(412).json({msg: error.message});
19         });
20      });
21   
22     app.post("/users", (req, res) => {
23       Users.create(req.body)
24         .then(result => res.json(result))
25         .catch(error => {CRUDify API resources 45
26           res.status(412).json({msg: error.message});
27         });
28     });
29   };
Note

The reason we’re not using the function app.route() here is because in the next chapter, we are going to modify some specific points on each user’s route, to find or delete only if the user is logged into the API .

Testing Endpoint Access Using Postman

To test these modifications, restart the application, open the browser and use some REST client application, because it will be necessary to test the POST, PUT, and DELETE HTTP methods. To simplify, I recommend you use Postman, which is a useful Google Chrome extension that is easy to use. To install it go to http:///www.getpostman.com.

After the installation, open the Chrome Apps page and click the Postman icon, shown in Figure 6-1.

A435096_1_En_6_Fig1_HTML.jpg
Figure 6-1. Postman REST client app

A login page is displayed, as shown in Figure 6-2, but you do not need to log in to use it. To skip this step and go straight to the main page, click Go to the app.

A435096_1_En_6_Fig2_HTML.jpg
Figure 6-2. Opening Postman

To test the endpoints, let’s perform the following tests:

  1. Choose the method POST via the address http://localhost:3000/tasks.

  2. Click on the menu Body, choose the option raw, and change the format from Text to JSON (application/json).

  3. Create the JSON: {"title": "Sleep"} and click Send (see Figure 6-3).

    A435096_1_En_6_Fig3_HTML.jpg
    Figure 6-3. Registering the task “Sleep”
  4. Modify the same JSON to {"title": "Study"} and click Send again (see Figure 6-4).

    A435096_1_En_6_Fig4_HTML.jpg
    Figure 6-4. Registering the task “Study”

With this procedure , you created two tasks, and now, you can explore the other task’s routes. To test them, you can follow this simple list:

  • Method GET, route http://localhost:3000/tasks

  • Method GET, route http://localhost:3000/tasks/1

  • Method GET, route http://localhost:3000/tasks/2

  • Method PUT, route http://localhost:3000/tasks/1 using the body {"title": "Work"}

  • Method DELETE, route http://localhost:3000/tasks/2

You can also test the user’s endpoints, if you like.

Conclusion

Congratulations! Now that you have reached this point, you have an outline of a RESTful API, which means now it’s possible to build a client-side app to consume the tasks or users resources. Don’t stop reading! In the next chapter we’ll write some important things to make sure that our users will be authenticated by the API.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset