©  Caio Ribeiro Pereira 2016

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

4. Building an API

Caio Ribeiro Pereira

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

Now that we have the Node.js environment installed and ready to work, we are going to explore the creation of our REST API application. This kind of application is currently being developed by many projects and companies, because it has the advantage of an application focused only on feeding any client-side application with data. Nowadays, it’s quite common to create web and mobile apps that consume data from one more APIs. This means that several kinds of client applications consult the same server focused on dealing with data. Besides, it also allows each application—client or server—to be worked with by different teams.

First, we are going to build an API . Throughout to the last chapters, however, we will also build a simple web client-side application to consume data from the API. To start developing the API, we are going to use a very popular web framework called Express .

Introduction to Express

Express is a minimalist web framework that was highly inspired by the Sinatra framework from the Ruby language. With this module, you can create anything from small applications to large, complex ones. This framework allows you to build APIs and also to create simple web sites. The home page for Express is shown in Figure 4-1.

A435096_1_En_4_Fig1_HTML.jpg
Figure 4-1. The Express home pag e at expressjs.com

It is focused on working with views, routes, and controllers, only models are not handled by this framework, giving you the freedom to use any persistence framework without creating any incompatibility or conflict in the application. This is an important advantage, because there are many object data mappers (ODMs) and also object relational mapping (ORMs) available. You can use any of them with Express without a problem; you just need to load this kind of module, write some models, and use them inside the controllers, routes, or views.

Express allows developers to freely arrange the project’s code; that is, there are no inflexible conventions in this framework. Each convention can be created and applied by yourself. This makes Express flexible to be used on both small and large applications, because it’s not always necessary to apply many conventions to a small project.

You can also reapply conventions from other frameworks. Ruby On Rails is an example of a framework full of conventions that are worth reapplying for some of their technical features. This independence forces the developer to fully understand how each application structure works.

To sum up, here is a list of the main features of Express .

  • Robust routing.

  • Easily integratable with a lot of template engines.

  • Minimalist code.

  • Works with middlewares concept.

  • A huge list of third-party middlewares to integrate.

  • Content negotiation .

  • Adopt standards and best practices of REST APIs.

Getting Started on the Pilot Project

What about creating a project in practice? From this chapter on, we are going to explore some concepts for creating a REST API using some Node.js frameworks.

Our application is going to be a simple task manager that will be divided into two projects: API and web app.

Our API will be called NTask (Node Task) and it will have the following features:

  • List of tasks.

  • Create, delete, and update a task.

  • Create, delete, and update user data.

  • User authentication.

  • API documentation page.

Pilot Project Source Code

If you are curious to check out the source code of all projects that are going to be explored in this book, you can access it online at https://github.com/caio-ribeiro-pereira/building-apis-with-nodejs .

In this application, we’ll explore the main resources to create an REST API on Node.js and, throughout the chapters, we include new important frameworks to help with the development effort.

To begin, let’s create our first project named ntask-api, running these commands:

1   mkdir ntask-api
2   cd ntask-api
3   npm init

You need to answer the npm init command wizard as you like or you can copy the result shown in Figure 4-2.

A435096_1_En_4_Fig2_HTML.jpg
Figure 4-2. Describing the project using npm init

In the end, the package.json will be created with these attributes:

 1   {
 2     "name": "ntask-api",
 3     "version": "1.0.0",
 4     "description": "Task list API ",
 5     "main": "index.js",
 6     "scripts": {
 7       "test": "echo "Error: no test specified" && exit 1"
 8     },
 9     "author": "Caio Ribeiro Pereira",
10     "license": "ISC"
11   }

The current Node.js version does not support ES6 perfectly, but we can use a module that emulates some of the resources from ES6/ES7 to make our codes better. To do this, we are going to install babel, a JavaScript compiler responsible for converting ES6/ES7 codes to an ES5 code only when the JavaScript runtime doesn’t recognize some ES6/ES7 features.

To use all features from ES6/ES7, we are going to install the babel-cli and babel-preset-es2015 modules in the project, running this command:

1   npm install [email protected] [email protected] --save

Now you need to link the preset babel-preset-es2015 to be recognized by the babel-cli. To do this, just create the file .babelrc with this simple code:

1   {
2     "presets": ["es2015"]
3   }

After that, we’ll turbocharge package.json. First we are going to remove the fields license and scripts.test. Then, include the field scripts.start to enable the alias command npm start. This command will be responsible for starting our API server via the Babel compiler using the command babel-node index.js. Our package.json is going to look like this:

 1   {
 2     "name": "ntask-api",
 3     "version": "1.0.0",
 4     "description": "Task list API ",
 5     "main": "index.js",
 6     "scripts": {
 7       "start": "babel-node index.js"
 8     },
 9     "author": "Caio Ribeiro Pereira",
10     "dependencies": {
11       "babel-cli": "^6.5.1",
12       "babel-preset-es2015": "^6.5.0"
13     }
14   }

Now we have a basic configuration and description of our project, and each piece of information is inside package.json. To start off, let’s install the express framework:

1   npm install [email protected] --save

By installing Express, we’ll create our first code. This code is going to load the express module, create a simple endpoint using the GET / via function app.get("/"), and start the server on the port 3000 using the function app.listen(). To do this, create the index.js file using this code:

1   import express from "express";
2
3   const PORT = 3000;
4   const app = express();
5
6   app.get("/", (req, res) => res.json({status: "NTask API "}));
7
8   app.listen(PORT, () => console.log(`NTask API - Port ${PORT}`));

To test this code and especially check if the API is running, start the server running:

1   npm start

Your application must display the message shown in Figure 4-3.

A435096_1_En_4_Fig3_HTML.jpg
Figure 4-3. Starting the API

Next, open your browser and navigate to localhost:3000. If nothing goes wrong, a JSON status message will be displayed, similar to Figure 4-4.

A435096_1_En_4_Fig4_HTML.jpg
Figure 4-4. JSON status message

Implementing a Simple and Static Resource

RESTful APIs work with the concept of creating and manipulating resources. These resources are entities that are used for queries, entries, and updating and deleting data, and everything is based on manipulating data from resources.

For example, our application will have as its main resource the entity named tasks, which will be accessed by the endpoint /tasks. This entity is going to have some data that describes what kind of information can be persisted in this resource. This concept follows the same line as data modeling; the only difference is that the resources abstract the data source. Therefore, a resource can return data from different sources, such as databases, static data, or any data from external systems, and it can return data from another external API , too.

An API aims to address and unify the data to, in the end, build and show a resource. Initially, we are going to work with static data, but throughout the book we will perform some refactoring to integrate a database.

For a while, static data will be implemented only to mold an endpoint. To mold our API , we are going to include a route via the app.get("/tasks") function, which is going to return only a static JSON via the res.json() function, which is responsible for rendering JSON content as output. Here is how these modifications are going to look in the index.js file:

 1   import express from "express";
 2
 3   const PORT = 3000;
 4   const app = express();
 5
 6   app.get("/", (req, res) => res.json({status: "NTask API "}));
 7
 8   app.get("/tasks", (req, res) => {
 9     res.json({
10       tasks: [
11         {title: "Buy some shoes"},
12         {title: "Fix notebook"}
13       ]
14     });
15   });
16
17   app.listen(PORT, () => console.log(`NTask API - Port ${PORT}`));

To test this new endpoint, restart the application by typing CTRL + C or Control + C (if you are a MacOSX user) and then run npm start again.

Caution

Always follow this same step to restart the application correctly.

Now we have a new endpoint available to access via the address localhost:3000/tasks.

Once you open it, you will have the result shown in Figure 4-5.

A435096_1_En_4_Fig5_HTML.jpg
Figure 4-5. Listing tasks

If you want your results to return as formatted and tabbed JSON output, include in index.js the following configuration: app.set("json spaces", 4); as shown here.

 1   import express from "express";
 2
 3   const PORT = 3000;
 4   const app = express();
 5
 6   app.set("json spaces", 4);
 7
 8   app.get("/", (req, res) => res.json({status: "NTask API "}));
 9
10   app.get("/tasks", (req, res) => {
11     res.json({
12       tasks: [
13         {title: "Buy some shoes"},
14         {title: "Fix notebook"}
15       ]
16     });
17   });
18
19   app.listen(PORT, () => console.log(`NTask API - Port ${PORT}`));

Now, restart the server to see the more elegant result shown in Figure 4-6.

A435096_1_En_4_Fig6_HTML.jpg
Figure 4-6. Listing tasks with formatted JSON

Arranging the Loading of Modules

Indeed, writing all endpoints into index.js won’t be a smart move, especially if your application has a lot of them. Let’s therefore arrange the directories and the loading of all codes according to their responsibilities.

We are going to apply the Model-View-Router (MVR ) pattern to arrange this. To do it, we’ll use the consign module, which will allow our project to autoload models, routers, middlewares, configs, and more, as this module injects dependencies easily. Let’s install.

1   npm install [email protected] --save

With this new module installed, let’s migrate the endpoints from the index.js file, creating two new files in the new directory named routes. To do this, create the file routes/index.js.

1   module.exports = app => {
2     app.get("/", (req, res) => {
3       res.json({status: "NTask API "});
4     });
5   };

Move the endpoint function app.get("/tasks") from index.js to this new file, routes/tasks.js:

 1   module.exports = app => {
 2     app.get("/tasks", (req, res) => {
 3       res.json({
 4         tasks: [
 5           {title: "Buy some shoes"},
 6           {title: "Fix notebook"}
 7         ]
 8       });
 9     });
10   };

To finish this step, edit index.js to be able to load those routes via the consign module and start the server.

 1   import express from "express";
 2   import consign from "consign";
 3
 4   const PORT = 3000;
 5   const app = express();
 6
 7   app.set("json spaces", 4);
 8
 9   consign()
10     .include("routes")
11     .into(app);
12
13   app.listen(PORT, () => console.log(`NTask API - Port ${PORT}`));

That’s it: We have just arranged the loading of all routes. Note that at this point we are only focusing on working with VR (view and router) from the MVR pattern. In our case, the JSON outputs are considered views, which are provided by the routes. The next step will be arranging the models.

Let’s create the models directory and return to index.js to add one more include() function inside consign() to allow the loading of the models before the routes. To make this modification clearer, edit the index.js.

 1   import express from "express";
 2   import consign from "consign";
 3
 4   const PORT = 3000;
 5   const app = express();
 6
 7   app.set("json spaces", 4);
 8
 9   consign()
10     .include("models")
11     .then("routes")
12     .into(app);
13
14   app.listen(PORT, () => console.log(`NTask API - Port ${PORT}`));

At this moment, the consign() function won’t load any model, because the directory models doesn’t exist. To fill this gap, let’s temporarily create a model with static data just to finish this step. To do this, create the file models/tasks.js and fill it with these codes.

 1   module.exports = app => {
 2     return {
 3       findAll: (params, callback) => {
 4         return callback([
 5           {title: "Buy some shoes"},
 6           {title: "Fix notebook"}
 7         ]);
 8       }
 9     };
10   };

Initially, this model is going to only have the function Tasks.findAll(), which is going to receive two arguments: params and callback. The variable params will not be used here, but it serves as a base to send some SQL query filters, something that we are going to cover in detail in the next chapters. The second argument is the callback function , which returns asynchronously a static array of tasks.

To call it in the routes/tasks.js file, you are going to load this module via the app variable. After all, the modules added by the consign() function are injected into a main variable, in our case, injected into app via consign().into(app);. To see how to use a loaded module, in this case, we’ll use the app.models.tasks module and edit the routes/tasks.js following this code.

1   module.exports = app => {
2     const Tasks = app.models.tasks;
3     app.get("/tasks", (req, res) => {
4       Tasks.findAll({}, (tasks) => {
5         res.json({tasks: tasks});
6       });
7     });
8   };

Note that the function Tasks.findAll()has in the first parameter an empty object. This is the value of the params variable. In this case, there is no need to include parameters to filter the tasks list.

The callback of this function returns the tasks variable, which was created to return some static values from his model. So, at this point, we are sure that tasks is going to return a static array with some task descriptions.

To finish these refactorings, let’s create a file that is going to load all the middlewares and specific settings of Express. Currently, we only have a simple configuration of JSON’s format, which occurs via the function app.set("json spaces", 4). However, we are going to include another setting, in this case, the server port calling the app.set("port", 3000) function.

Throughout this book, we are going to explore a lot of new middlewares and settings to customize our API server. So, here I recommend preparing the house for these new visitors.

Create the file libs/middlewares.js and write this code.

1   module.exports = app => {
2     app.set("port", 3000);
3     app.set("json spaces", 4);
4   };

To simplify the server boot, create the file libs/boot.js, which is going to be responsible for the server initialization via the app.listen() function. This time, we’ll use app.get("port") instead of the old constant PORT.

1   module.exports = app => {
2     app.listen(app.get("port"), () => {
3       console.log(`NTask API - Port ${app.get("port")}`);
4     });
5   };

To finish this chapter, let’s load the libs/boot.js inside the module’s structure provided by consign and remove the old const PORT, app.set(), and app.listen() declarations to simplify this main file. To do this, just edit the index.js.

 1   import express from "express";
 2   import consign from "consign";
 3
 4   const app = express();
 5
 6   consign()
 7     .include("models")
 8     .then("libs/middlewares.js")
 9     .then("routes")
10     .then("libs/boot.js")
11     .into(app);

To test everything, restart the server and access the endpoint again: localhost:3000/tasks. To make sure everything is okay, no errors should occur and all the data must be displayed normally, as shown in Figure 4-7.

A435096_1_En_4_Fig7_HTML.jpg
Figure 4-7. Listing loaded modules

Conclusion

Mission complete! In the next chapter we’ll expand our project by implementing new functions to manage tasks dynamically using a database and the Sequelize.js framework.

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

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