Lesson 26. Adding an API to Your Application

In this lesson, you take a first look at reorganizing your routing structure and responding with data. First, you create new folders to house the routes you’ve built in main.js. The new structure follows some of the application programming interface (API) conventions you set up in earlier lessons. Next, you modify some controller actions to respond with Embedded JavaScript (EJS) and JSON, depending on the query parameters. Last, you test your new API connection by creating an Ajax GET request from your client-side JavaScript.

This lesson covers

  • Organizing your routes with namespacing
  • Creating API endpoints to respond with JSON
  • Making Ajax requests from your views
Consider this

Your recipe application renders many pages and offers specific functionality on each page. To make the user experience less complicated, you’d like to allow users to view available programs from their profile pages. To do so, you decide to conditionally serve data in JSON format and display that data through JavaScript and HTML on the client. When you modify your controller actions, your application can offer an API that goes beyond serving web pages.

26.1. Organizing your routes

As your application grows, the routes in your main.js file start to overwhelm other middleware and configurations. Routes are important parts of your application, and keeping your routes organized in a way that multiple developers can manage and understand is arguably as important.

To start this lesson, you break down the routing structure you’ve set up in an easy-to-follow directory structure. In units 4 and 5, you created routes to reflect CRUD functionality in what’s called a REST architecture. Representational state transfer (REST) is a way of programming your application to represent the involvement of its resources across the web. Your application’s resources are the users, subscribers, and courses stored in the database and displayed in the views. You implemented a RESTful structure by constructing your routes to contain the model name, HTTP method, action being performed, and model ID if necessary. router.get("users/:id/edit", usersController .edit) tells you that an HTTP GET request was made to the users/:id/edit path, for example.

These routes make it easy for users to know exactly what information is needed to get the data they want to see—in this case, an edit form for an existing user. From the path alone, you know that you’re trying to edit a specific user record. From there, you can connect to the appropriate action and redirect to another RESTful route.

Note

Redirecting is often a secondary action when you’re creating or updating information in the database. After arriving at the initial controller action to modify data, you redirect to another route to send the user to another page to view.

In this section, you reorganize your routes into individual modules to reflect the models that they use. This structure will be useful when you decide to expand the types of routes and response data you use in the application.

Start by creating a new folder called routes at the root level of your project and create the following new modules within that folder:

  • userRoutes.js
  • courseRoutes.js
  • subscriberRoutes.js
  • errorRoutes.js
  • homeRoutes.js
  • index.js

These six modules will divide the routes that are currently in main.js. For now, focus on the user routes.

Start by requiring the Express.js Router and the usersController at the top of the module. Then import the login routes and CRUD routes, and add them to the local router object. Doing so allows these routes to be handled by the same router. With all the working routes attached to the router, you can export the router object. Notice in this example that you’re leaving users out of the path. You’ll define that part of the path in index.js later.

Copy all the routes in main.js that pertain to the user (CRUD operations, login, and authentication), and move them into userRoutes.js, as shown in the next listing.

Listing 26.1. Moving user routes to userRoutes.js
const router = require("express").Router(),
  usersController = require("../controllers/usersController");  1

router.get("/", usersController.index,
 usersController.indexView);                                 2
router.get("/new", usersController.new);
router.post("/create", usersController.validate,
 usersController.create, usersController.redirectView);
router.get("/login", usersController.login);                    3
router.post("/login", usersController.authenticate);
router.get("/logout", usersController.logout,
 usersController.redirectView);
router.get("/:id/edit", usersController.edit);
router.put("/:id/update", usersController.update,
 usersController.redirectView);
router.get("/:id", usersController.show,
 usersController.showView);
router.delete("/:id/delete", usersController.delete,
 usersController.redirectView);

module.exports = router;                                        4

  • 1 Require Express.js Router and users controller.
  • 2 Add CRUD routes.
  • 3 Add login and authentication routes.
  • 4 Export the module router.

Namespaces

Namespacing is a way of defining routes, paths, and other application items under the umbrella of a specific string or path. Instead of defining dozens of routes with the same path prefix, /users, you can make that prefix a namespace for those routes.

Namespacing is particularly helpful in separating routes in your API based on the format of the content returned. If an iOS application wants to access the data in your recipe application, for example, you might create specific routes with the namespace /ios. Then you could define paths such as /ios/courses and /ios/subscribers. Through the routes defined under this namespace, the iOS application can access data.

Follow the same strategy for the other route files. Subscriber routes go in subscriberRoutes.js, and error routes go in errorRoutes.js.

The index.js module requires all route modules to be in one place. This convention makes it easier to identify all the route types in one file and requires only a single file into main.js. As with the route modules, you require the Express.js Router in index.js. Next, require each relative route module. With those modules added, tell the local router object to use those routes with specific namespaces.

For the home and error routes, no namespace is necessary. By adding the /users namespace for the user routes defined in listing 26.1, you return to the original functionality of your routes. The last step is requiring this index.js module in main.js. Add const router = require("./routes/index") to the top of main.js and app.use("/", router) after your middleware functions.

To tie all these routes to the same router used by your application, add the code in the next listing to index.js.

Listing 26.2. Importing all routes into index.js
const router = require("express").Router(),             1
  userRoutes = require("./userRoutes"),                 2
  subscriberRoutes = require("./subscriberRoutes"),
  courseRoutes = require("./courseRoutes"),
  errorRoutes = require("./errorRoutes"),
  homeRoutes = require("./homeRoutes");

router.use("/users", userRoutes);                       3
router.use("/subscribers", subscriberRoutes);
router.use("/courses", courseRoutes);
router.use("/", homeRoutes);
router.use("/", errorRoutes);

module.exports = router;                                4

  • 1 Require the Express.js Router.
  • 2 Require all the route modules within the same directory.
  • 3 Use the routes from the relative route modules with namespaces.
  • 4 Export the router from index.js.
Note

Order matters. Make sure to have the more-detailed routes closer to the top of index.js. Otherwise, the error routes will handle all incoming requests before they can reach the routes you intended.

The Express.js router object operates through middleware. Within it, you can define specific tasks that you want to perform on incoming requests. In this case, you’re using router to load routes under different namespaces. As with other middleware, if you want the router middleware to be part of the main application’s middleware flow, you need to add it with app.use. In main.js, remove all the controllers’ require statements, as well as the require statement for express.Router(). The rest of the middleware in main.js is used by the app object.

Note

It’s important to change all remaining middleware in main.js to be used by app instead of router because you’ll want the app to parse requests and use your templating engine before the request reaches your router at the bottom of the file. Order of middleware matters!

Restart your application, and confirm the original functionality of your application is intact. If you get any errors or if some routes aren’t found, make sure that all the route namespaces are defined correctly and that the resource name prefixes are stripped from the original paths. Under the new namespace, your user index route, for example, should read router.get("/", usersController.index, usersController.indexView) instead of router.get("/users", usersController.index, usersController.indexView).

In the next section, you learn how to use your existing routes to return two types of data formats.

Quick check 26.1

Q1:

Why do you add app.use("/", router) in main.js?

QC 26.1 answer

1:

When the router is defined in main.js, you need to tell the Express.js application to use it as middleware.

 

26.2. Creating an API

An API is a structure set up within your application to allow external sources to access your application data. In effect, you’ve already built an API by creating your Express.js web server. By serving HTML and EJS, you’ve provided an avenue through which users of your application can access your data: the web browser. Not every user, however, will want to see your application data exclusively through the browser on a web page with the styling and formatting you’ve applied.

Think of your current Express.js application as being like a restaurant menu. It’s likely that most people will refer to the printed menu to find out what food items a restaurant offers. Getting access to the hard-copy menu requires traveling to the restaurant itself. By providing a phone number to call to inquire about menu items and a website to display the restaurant’s menu, you give customers more options to get the information they need. Similarly, a robust API provides application data in different formats that you access in different ways.

In this section, you reconstruct some of your application routes and actions to respond with data in JSON format in addition to rendered EJS views. Responding with JSON is simple in Express.js. Change the res.render("courses/index") line in the indexView action of coursesController.js to res.json(res.locals.courses). When you restart your application and visit http://locatlhost:3000/courses, your browser should display all the courses in your database in JSON format (figure 26.1).

Figure 26.1. Display of JSON course results in browser

This output should resemble the output from your MongoDB server when you run mongo in a new terminal window: use recipe_db and db.courses.find({}), as shown in figure 26.2. Running these commands starts your MongoDB environment and lists all the courses in your recipe database. In the application, you’re essentially showing the full database documents in the browser.

Figure 26.2. Display of courses in MongoDB

You can further improve the index action by responding with JSON only when requested. You can accomplish this task in many ways. One way is to use query params. In this code, you perform a check for the format query param. If it exists and equals json, respond with the course data in JSON format. Otherwise, respond with a rendered EJS view as usual. Change the courses indexView action to the code in the next listing.

Listing 26.3. Responding with JSON when query param exists in usersController.js
indexView: (req, res) => {
  if (req.query.format === "json") {
    res.json(res.locals.courses);          1
  } else {
    res.render("courses/index");           2
  }
}

  • 1 Respond with JSON if the format query param equals json.
  • 2 Respond with an EJS view if the format query param doesn’t equal json.

Restart your application, and visit http://localhost:3000/courses to ensure that your original EJS index view is still rendering. To display JSON data instead of the normal view, append ?format=json to the end of your URL: visit http://localhost:3000/courses? format=json. This additional query parameter tells your courses controller to render data in JSON format instead of EJS.

With this change in place, if an external application wants to access the list of courses, it can make a request to the URL with the query parameter. External applications are only one group of consumers that can benefit from this implementation, though. You can use this data endpoint from within your own application in many ways. (An API endpoint is a reference to one or more application paths whose routes accept web requests.)

Quick check 26.2

Q1:

What method do you use on the response to send data as JSON back to the client?

QC 26.2 answer

1:

In Express.js, you can use res.json followed by the parameters you’d like to send in JSON format.

 

26.3. Calling your API from the client

In the restaurant analogy, a menu’s items could be made available through different media: print, phone, or web. This variety makes it easier for customers to learn more about the food served in the restaurant and also could make it easier for restaurant staff to access the menu items more quickly. After all, pulling up a web page is a convenient alternative to finding a menu on a busy night.

In many places within your application, you could benefit from application routes that return JSON data. Primarily, you could benefit by making Ajax requests from the client to access data from pages you don’t want to refresh. What if you want users to be able to view the course listings without having to change their current page, for example?

Implement a solution by populating a modal (a window that overlays the main browser screen with some instruction or content) with course data via an Ajax request. To start, create a partial view called _coursesModal.ejs in the views/courses folder. Use a simple bootstrap modal, as shown in the next listing.

In this modal, you have a button that triggers a modal to appear. The modal has a tag with the modal-body class. Target this class to populate course data.

Listing 26.4. Simple bootstrap modal in _coursesModel.ejs
<button id="modal-button" type="button" data-toggle="modal"
 data-target="#myModal">Latest Courses</button>

<div id="myModal" class="modal fade" role="dialog">
  <div class="modal-dialog">
    <div class="modal-body">                          1
    </div>
    <div class="modal-footer">
      <button type="button" data-dismiss="modal">Close</button>
    </div>
  </div>
</div>

  • 1 Add a modal where you’ll populate modal-body.

Include this partial view in your layout.ejs file so that you can access it from anywhere in your application by adding <li><%- include courses/_coursesModal %></li> as an item in your layout’s navigation. To get this modal to work, you also need to have the bootstrap client-side JavaScript as well as jQuery. You can get the minified code for jQuery.min.js at https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js and bootstrap.min.js at https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js.

Note

I recommend copying the code from this content delivery network and saving the code locally to files with the same name in public/js.

Then, in layout.ejs, link to these JavaScript files, as shown in the following listing.

Listing 26.5. Import jquery and bootstrap into layout.ejs
<script type="text/javascript" src="/js/jquery.min.js"></script>
<script type="text/javascript" src="/js/bootstrap.min.js"></script> 1

  • 1 Add local JavaScript files from public/js.

With a few styling changes, you can restart your application. You should see a button in your top navigation bar that opens a modal, as shown in figure 26.3.

Figure 26.3. Simple modal button in navigation bar

To give this modal some data, create recipeApp.js in your public folder’s js folder. This JavaScript file will run on the client side. Make sure that this file is linked in your layout .ejs file below bootstrap and jQuery by adding <script type="text/javascript" src="/js/recipeApp.js"></script>.

Within recipeApp.js, add the code in listing 26.6. You wrap the code block in $(document) .ready to ensure that no JavaScript is run until the DOM is loaded and ready. Then you add a click listener on the modal-button ID. When that button is clicked in the navigation bar, perform an Ajax GET request, using $.get to the /courses?format=json path. With the added query param, you expect the response to include data as an array in JSON. Then you loop through that array to access individual course records and use $(".modal-body").append to add some HTML with each course’s title and description.

Listing 26.6. Ajax function to load data in modal in recipeApp.js
$(document).ready(() => {                           1
  $("#modal-button").click(() => {                  2
    $(".modal-body").html('');                      3
    $.get("/courses?format=json", (data) => {       4
      data.forEach((course) => {                    5
        $(".modal-body").append(
          `<div>
          <span class="course-title">
          ${course.title}
          </span>
          <div class="course-description">
          ${course.description}
          </div>
          </div>`                                   6
        );
      });
    });
  });
});

  • 1 Wait for DOM to load.
  • 2 Listen for a click event on the modal button.
  • 3 Clear the modal from any previous content.
  • 4 Request data from /courses?format=json asynchronously.
  • 5 Loop through array of data in the response.
  • 6 Append each course to the modal.

With this Ajax request in place, restart the application and load course data into the modal. Clicking the modal button fetches new data from the server, as shown in figure 26.4.

Figure 26.4. Populating course data within modal

Now users can view the list of courses from any page. Even if new courses are added to the database, clicking the modal button fetches that new list.

Ajax

Asynchronous JavaScript and XML (Ajax) is a technology that allows client-side requests to be made asynchronously without interfering with any behavior or display of the application page. Ajax uses JSON and XML to format data and requests to be sent to a server. By managing only the data layer of an application on your browser, Ajax allows you to make a request asynchronously and handle data in the resulting response through a callback function.

Because of the way that Ajax interacts with a backend server without the need to reload your web page, it’s widely used to update content dynamically in real time. Through multiple Ajax requests, a web page theoretically might never have to reload.

Quick check 26.3

Q1:

What do you expect will happen if there are no courses in the database when you make an Ajax request?

QC 26.3 answer

1:

The Ajax request returns an array of items from the database. If there are no records, the response contains an empty array.

 

Summary

In this lesson, you learned about modifying your application route structure to make room for an extensive API. First, you reorganized your routes into individual modules. Next, you added a way to respond with JSON data from your controller action. Last, you added client-side JavaScript to make asynchronous requests to your server from within a view. In lesson 27, you explore namespacing further and see ways in which you can enroll users in courses from the modal itself.

Try this

With one action modified to respond with JSON data, try applying the same technique to other actions. Start by adding the query param condition to the other model index actions; then implement it for the show actions.

Keep in mind that the show actions return individual records, not arrays.

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

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