Chapter 7. Communicating with the server

This chapter covers

  • The server’s role in an SPA environment
  • How MV* frameworks communicate with the server
  • Handling results with callback functions and promises
  • Consuming RESTful services

In chapter 1, you learned how the adoption of the XMLHttpRequest (XHR) API and the AJAX movement eventually led to the emergence of SPAs. After XHR was supported in the browser—as a COM component at first and then natively—developers could use it to asynchronously load both the application’s scaffolding and its data without refreshing the page. This opened many new avenues for the ways that web pages could be constructed.

Until now, you’ve been focusing on creating the SPA itself. In doing so, you’ve used XHR to dynamically retrieve the templates used to construct your views but restricted the data in your sample applications to local stub data. In this chapter, you’ll take another important step forward. You’re going to move the source data to the server and learn how to remotely access it from your SPA.

We’ll kick things off with a brief look at the communication process between the SPA client and the server. After you’re clear on the overall process, we’ll look at the details of what happens on the client.

On the client side, I’ll focus on how MV* frameworks try to make your life easier when you need to talk to the server. MV* frameworks that have built-in support for persistence enhance the XMLHttpRequest API with their own expanded set of features. But because each one has to go through XHR, there are certain commonalities I can point out.

What’s the optimal way for an SPA to communicate with the server?

Generally, the most optimal way to communicate with the server from your SPA is to use the objects provided by your MV* framework—provided that the framework supports server communication. Because its objects are built specifically to work within the framework, they provide request and response methods that are ready-made to work with the rest of the framework. You’ll need to customize these objects for your specific needs, either through configuration or by extending them in some fashion. Typically, you don’t need to supplement the framework with any additional libraries.

If your framework doesn’t have built-in support for communicating with the server, you can opt to work directly with the low-level methods of the XMLHttpRequest object itself, use a general utility library (such as jQuery), or go for a library that has fewer, more specialized components (such as AmplifyJS, http://amplifyjs.com).

After learning the basics of communicating with the server, you’ll turn your attention to dealing with the results. You’ll start with traditional callback functions, which describe what you want to happen when calls succeed or fail. Next, you’ll learn about the use of promises. Promises are fast becoming the preferred means of dealing with XHR results by many of today’s MV* frameworks. More important, though, they’re part of the ECMAScript 6 version of JavaScript. They’re generally considered a cleaner, more elegant way of dealing with asynchronous processes than simple callback functions.

This chapter wraps up with a look at consuming RESTful services with your SPA. REST is an architectural style for both websites and web services that has gained widespread popularity in recent years—so much so that many MV* frameworks support it out of the box. Some even use the REST style as a default.

I won’t go into great detail about designing RESTful services, because that server-side topic is beyond the scope of this book. I’ll talk about what REST is in the philosophical sense and discuss some of the ways in which MV* frameworks approach REST.

In the example for this chapter, you’ll continue the preceding chapter’s used video game store project by adding a shopping cart to it. A shopping cart is a standard feature for most sites selling goods and/or services online. It’s also the perfect venue for demonstrating the server communication concepts in this chapter. You’ll explore the details of your shopping cart later in the book. That being said, the sample project has some new requirements that we need to discuss.

7.1. Understanding the project requirements

Unlike in previous chapters, to run the code in this chapter you’ll need a server. Because most MV* frameworks, including the one we’re using (AngularJS), are server agnostic, you can pick any server you want. You can also use any server-side language you want. So whether you like JavaScript, PHP, Python, Ruby, .NET, Java, or any other of the multitude of languages out there for server-side development, that’s perfectly OK.

Here are the only two hard requirements for whichever server/language combination you prefer:

  • Support of RESTful services, because the example uses REST
  • JSON support, either built in or via an add-on

The example’s server code was developed using Spring MVC (version 4), which is a Java-based MVC framework. Don’t worry, though, if you don’t know Java or Spring. In our discussions within the chapter, I’ll refer to the server-side code only conceptually. A guide to the server-side code’s configuration is available in appendix C. If you prefer a different server-side tech stack, the appendix begins with a summary of the server-side objects and tasks so you can structure your own server-side code accordingly. The entire source for the project is available online for download.

Now that you’ve been introduced to the project, let’s look at how your SPA can communicate with the server.

7.2. Exploring the communication process

Though many concepts around communicating with the server are the same for any type of web application, the next few sections present some of the basics within the context of a single-page application. I’ll also highlight some specific ways in which the MV* framework supports the communication process.

7.2.1. Choosing a data type

In order for the SPA running in the browser to communicate with a server, both need to speak the same language. The first order of business is deciding on the type of data that will be sent and received. To illustrate, I’ll use the example of a shopping cart, as I do at the end of the chapter.

When the user interacts with your shopping cart—whether it’s adding an item, updating the quantity of an item, or viewing the current state of the cart’s contents—you’re sending and receiving JSON-formatted text. JSON is commonly used by SPAs when communicating with servers, though the data type can be anything from plain text, to XML, to a file.

Even though you’re using JSON-formatted text as a common data exchange format, it’s merely a representation of a system’s native object or objects. For the text to be useful, conversions are happening at both ends. You’ll learn about these a little later. To ensure that the conversions to native objects work, each side must do its part to make sure the agreed-upon JSON format is used in the call.

When a call is made to the server, requests can include information about the internet media types that are acceptable, because a resource can be available in a variety of languages and media types. The server can then respond with a version of the requested resource that it deems a best fit. This is called content negotiation. For this project, you’re interested only in JSON. To express this, you can explicitly declare an internet media type of application/json for the exchange.

Internet media types

An internet media type (formerly a MIME type) is a standard way to identify the data that’s being exchanged between two systems. It’s used by many internet protocols, including HTTP. Internet media types have the format of type/subtype. In this case, you’re using a media type of application/json: the type is application, and the subtype is json.

Optional parameters can also be added by using a semicolon, if required. For example, to specify a media type of text, with a subtype of html and a character encoding of UTF-8, you use text/html; charset=UTF-8.

Internet media types are specified using HTTP headers, which are the fields sent in the transmission that provide information about the request, the response, or what’s contained in the message’s body. The Content-type header tells the other system what to expect in the request and response. The Accept header can also be specified in the request to let the server know the media type or types that are acceptable to return.

After a data type has been selected, an appropriate request method must be used for the call to be successful. The next section presents common request methods for an SPA.

7.2.2. Using a supported HTTP request method

When a client makes a request, it can indicate the type of action it would like the server to perform by specifying the request method. In order for the request to be successful, though, the HTTP request method specified in the request must be supported by the server-side code for that call. If it isn’t, the server may respond with a 405 Method Not Allowed status code.

Because the HTTP request method describes what should happen to the resource represented in the request, it’s often called the verb of the call. A request method that doesn’t modify a resource, such as GET, is considered safe. Any request method that ends in the same result, no matter how many times its call is executed, is considered idempotent. For example, you’ll use PUT when the user wants to update the count of a particular item that’s in the cart. Because PUT is idempotent, you can tell the server that you want two copies of Madden NFL 10 times in a row, but after the tenth time, you still have only two copies in the cart.

Table 7.1 defines a few common HTTP request methods used in our shopping cart example. Although it’s not a comprehensive list, it does represent the ones most commonly used in single-page applications.

Table 7.1. Common HTTP methods used in an SPA

Method

Description

Example

Safe?

Idempotent?

GET Typically, GET is used to fetch data. View the shopping cart Yes Yes
POST This method is most commonly used for creating a resource or adding an item to a resource. Add an item to the cart No No
PUT Typically, PUT is used like an update-or-create action, updating the existing resource or optionally adding it if it doesn’t exist. Update the quantity of an item in the cart No Yes
DELETE This is used to remove a resource. Remove an item from the cart No Yes

Other HTTP methods are specified in the HTTP protocol. For a full list, see http://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Request_methods.

The final part of the communication process is the conversion of the data to and from the internet media type sent and received.

7.2.3. Converting the data

After the data type is agreed upon, both the client and the server must be configured to send and receive that particular type. For your shopping cart, you’re using JSON exclusively, so both the code in the browser and the code on the server must be able to convert to and from this text format.

On the client, the ability to convert a JavaScript object to JSON may be built into the MV* framework. If that’s the case, it’s likely the default, and the conversion will happen automatically when you use the framework to make the server request. If automatic conversion is not built in, the framework may offer a utility for the conversion of its custom types. For the conversion of JavaScript POJOs, you can use the native JavaScript command JSON.stringify():

var cartJSONText = JSON.stringify(cartJSObj);

On the server, the JSON-formatted text is converted into a native object of the server-side language by a JSON parser that’s either built in or available via a third-party library. Like the HTTP method, the exact method for executing the conversion process on the server will vary.

To illustrate the process end to end, let’s use the shopping cart update example again. Let’s say that the user has increased the quantity of an item in the cart. For the modification to be verified and processed, you’ll send the updated cart to the server. Figure 7.1 paints a picture of the conversions that happen at both ends.

Figure 7.1. JavaScript objects are converted to JSON and added to the request body for the request. In response, the server sends back the updated cart as JSON via the response body.

After the update function is called, your JavaScript cart object is converted into JSON-formatted text by the MV* framework. Next, the MV* framework passes the data to the XMLHttpRequest API. Then the JSON payload is sent in the body of the request to the server.

On the client, after the response is received, the returned text is converted once again. This time it’s converted back into a native JavaScript object. Often this is also handled automatically for you by the MV* framework. If not, you can use the native JavaScript command JSON.parse():

var cartJSObj = JSON.parse(returnedCartJSONText);

Now that we’ve discussed the communication process as a whole, let’s go back to the client to talk about how MV* frameworks help simplify this process.

7.3. Using MV* frameworks

One thing MV* frameworks are great at is simplifying complex tasks by abstracting away a lot of the boilerplate code involved. This is certainly true when it comes to communicating with the server. This section specifically covers making requests and dealing with the responses. In our discussion, I’ll point out some of the ways in which MV* frameworks help with the heavy lifting.

7.3.1. Generating requests

If server communication is supported by the framework, it may expose the XHR object directly or abstract some or all of the XHR functionality with its own proprietary objects. These custom objects act as wrappers around the XMLHttpRequest object either directly or indirectly via another library such as jQuery. They add value by hiding many of the tedious, repetitive tasks in making calls and processing the results.

Before you look at any MV* examples, let’s put things into perspective by using vanilla JavaScript and the XMLHttpRequest object to make a server call. If you need a refresher on XHR, refer to appendix B.

We’ll use the shopping cart again as an example. As you did earlier, you’ll update the quantity of an existing item in the cart. Because you’re updating the quantity of an item, you’ll use the PUT HTTP request method. As you learned in the preceding section, PUT is commonly used in an update situation. To keep things simple, you’ll use an abbreviated version of the cart data used in the project:

var cartObj = {
   cartId : 123,
   items : [ {
        productId : "madden_nfl_15",
        quantity : 2
   } ]
};

The following listing illustrates the plain JavaScript version of an update to the shopping cart using the XMLHttpRequest object directly.

Listing 7.1. Shopping cart update using PUT and XHR directly

In this example, you’re not even handling the results. You’ll tackle that in the next section. Even so, you have to deal with several of the low-level details. You have to manually set the content type and any other headers you need (such as Accept). Additionally, you have to manually convert the JavaScript cart object to JSON-formatted text.

Generally, if an MV* framework has out-of-the-box support for server communication, you’ll most likely be generating requests from one of two types of objects: a model or some type of utility/service object. If the framework requires you to create an explicitly defined data model, you’ll most likely perform server operations by calling functions on the model itself. If the framework doesn’t have an explicit data model (the framework considers any source of data an implied model), you’ll probably work through the framework’s utility/service. AngularJS, for example, provides a couple of services for server communication: $http and $resource. You’re be using $resource in the project, and you’ll see it in action a little later.

Making requests via a data model

With some frameworks (Backbone.js, for example), you explicitly define a data model by extending a built-in model object from the framework. By extending the framework’s model, you inherit many capabilities automatically. This includes the built-in capability to perform the full range of CRUD (create, read, update, and delete) operations on a remote resource (see figure 7.2).

Figure 7.2. With MV* frameworks, where your model extends those of the framework, you automatically inherit abilities from the parent, such as the ability to make server requests.

Don’t worry, though, if you need to make custom calls. Most frameworks let you override and customize their out-of-the box behavior.

Listing 7.2 extends Backbone.Model to define your shopping cart, passing in the name of the attribute you want to use as its ID. You’re also defining a base URL for all server requests. You have to do this only once, because this is just the model’s definition.

After the model has a definition, you can create new instances of it anytime you need to use it. All new instances of your shopping cart will then inherit everything you need for server communication.

Listing 7.2. Backbone.js version of your shopping cart update

The Backbone.js code is certainly less verbose. It’s also doing several things under the covers. For starters, it assumes you’re dealing with JSON (unless you tell it otherwise) and automatically converts the object passed into its constructor to JSON-formatted text. In addition, it automatically sets the Content-type and Accept headers for JSON. Finally, it can automatically decide whether to use PUT or POST based on whether the object of the request has an ID yet. Again, any of these features can be customized or overridden.

Making requests through data source objects

The other manner in which MV* frameworks make requests to the server is through a separate data source object. This is typical when a framework, such as AngularJS, allows you to use anything you want as a data model. With no parent to extend, there are no canned abilities to inherit. When this is the case, the framework provides a data source object that you’ll pass your model into when making a call (see figure 7.3).

Figure 7.3. Frameworks that provide server communication, but don’t provide a model to extend, will most likely provide a data source object instead.

Let’s see an example of this alternative MV* approach. Listing 7.3 uses an Angular-JS $resource object to perform your shopping cart update. I mentioned earlier that $resource is one of AngularJS’s services that can be used when communicating with the server. It has many features for easily modeling requests and dealing with the server’s response. When you get to this chapter’s project, you’ll delve into the use of $resource in detail to understand the example. For now, let’s see this style of MV* code as a comparison with your original, vanilla JavaScript server call.

Listing 7.3. AngularJS version of your shopping cart update

Even though you’re not extending anything, the overall concept is the same as our first MV* example. You can lean on the MV* framework to help you generate the request. Like Backbone.js, under the covers AngularJS sets the appropriate headers, converts the JavaScript object into JSON, and uses the HTTP method you defined. As you saw, though, the authors of this particular framework chose to not include a method to update the cart (PUT) out of the box. It’s easy enough, though, to customize the data source object to add this behavior.

Another feature that MV* frameworks provide is an easy way to deal with the results of a call to the server. Some frameworks support using callback functions, whereas others rely on promises. Promises are becoming more and more prevalent with MV* frameworks, but I’ll make sure you understand using callbacks with asynchronous requests first.

7.3.2. Processing results with callbacks

When you’re processing an asynchronous task, such as your server call to update the shopping cart, you don’t always want the application to hang while you wait for the server to respond. You sometimes need it to continue in the background while your application handles other tasks. So instead of the update function returning a value when it’s done, callbacks are passed in to handle the results when it completes. You can do this because functions can be passed around. This allows any function to take other functions as arguments.

When callbacks are passed into a function as arguments, they become like an extension of it. They can be passed control and continue processing from there. Using callbacks in this way is called continuation-passing style.

Let’s take a look, then, at using the continuation-passing style of programming to process the results of a server call. Because Backbone.js supports callbacks, I’ll use that framework to illustrate. Let’s add some handlers to your previous shopping cart update. Figure 7.4 gives an overview.

Figure 7.4. With callbacks, control passes from the save() function to either the success() function or the error() function after the process has completed.

If the call is successful, the save() function invokes the success() function via the XHR object, passing to it the returned cart data. If the call fails, save() invokes error(), passing in the details for the failure. In either case, processing is continuing from the model’s save() function to one of these callback functions.

Now let’s take a look at some code. You’ll make exactly the same request that you did earlier with Backbone.js, but this time you’ll do something with the results (see the following listing).

Listing 7.4. Processing a shopping cart update via callbacks

Not only is this code a little easier to read, but you’re also able to pass in a configuration object to the save() function itself. In this object, you can define success and error callback functions and any other configuration options supported by save() that are needed. Backbone.js also helps out by automatically passing the results of the server call to the callback functions you’ve defined.

In a successful call, you have access to the updated cart object as well as the response from the server. When the call fails, you can use the response to find out the reason for the failure. Moreover, if you need any low-level details about the call, the save() method also returns a jQuery jqXHR object, which is a wrapper for XHR. For more details about jqXHR, see http://api.jquery.com/jQuery.ajax/#jqXHR.

Callbacks are easy to work with and great for simple results, but continuation-passing style can sometimes become cumbersome if you have multiple tasks to perform when the call completes.

Fortunately, a trend with many MV* frameworks is to return a promise instead of relying on continuation-passing style callbacks. Like callback functions, promises are nonblocking: the application doesn’t have to stop and wait for the call to finish. This makes them also ideal for asynchronous processing. As you’ll see in the next section, they have additional properties and behaviors that make your life much easier when you have complex requirements for handling results.

7.3.3. Processing results with promises

A promise is an object that represents the outcome of a process that hasn’t yet completed. When an MV* framework supports promises, its functions that perform asynchronous server calls will return a promise that serves as a proxy for the call’s eventual results. It’s through this promise that you can orchestrate complex result-handling routines. To understand how to use a promise, you must first understand its internal state before and after the call is made.

Working with promise states

The good news about working with promises is that they exist in only one of the following three states:

  • Fulfilled —This is the state of the promise when the process resolves successfully. The value contained within the promise is the result of the process that ran. In your shopping cart update, this would be the updated cart contents returned by the server.
  • Rejected —This is the promise’s state when the process fails. The promise contains a reason for the failure (usually an Error object).
  • Pending —This is the initial state of the promise before the process completes. In this state, the promise is neither fulfilled nor rejected.

These three states are mutually exclusive and final. After the promise has been fulfilled or rejected, it’s considered settled and can’t be converted into any other state. Figure 7.5 uses the shopping cart project to illustrate the three states of a promise.

Figure 7.5. A promise has three mutually exclusive states: pending, fulfilled, and rejected.

A variable assigned to a promise doesn’t remain a null reference while it waits for the function to return. Instead, a full-fledged object gets returned immediately in a pending state with an undetermined value. When the process finishes, the promise’s state changes to either fulfilled, with its value containing the results of the call, or rejected, with the reason for the failure.

Accessing the results of your process

I haven’t talked about what you do with a promise after it’s returned, in order to access a process’s results. The Promise API has several useful methods, but the one you’ll use the most is its then() method.

The then() method lets you register callback functions that allow the promise to hand you back a process’s results. The functions you define here are called reactions. The first reaction function represents the case in which the promise is fulfilled. The second is optional and represents the case in which the promise is rejected:

promise.then(
   function (value) {
      // reaction to process the success value
   },
   function (reason) {
      // reaction to optionally deal with the rejection reason
   }
);

Because the rejected reaction is optional, the then() method can be written in shorthand:

promise.then(function (value) {
   // process the success value, ignore rejection
});

Here’s the point to remember about reaction functions: no matter how the code is formatted, only one of the two functions will ever be executed—never both. It’s one or the other. In this regard, it’s somewhat analogous to a try/catch block. It’s also worth noting that the parameter of the reaction function is what the promise hands you back (with either the fulfilled value or the rejection reason). When that happens, you have your results.

Let’s take a look at the then() function in action. The following listing updates your shopping cart and uses a promise instead of a callback function to process the results.

Listing 7.5. Processing a shopping cart update via a promise

Having a promise returned is built into AngularJS’s $resource methods. As you can see in the example, you’re writing out the results of the call to the console as you did before—only this time you’re able to use the returned promise object instead of diverting control over to a callback function. The then() method passes the success results or the rejected reason to the functions you give it.

Another perk of using promises is that you can chain multiple then() methods together if more than one thing needs to happen after your call has been made.

Chaining promises

Often after a process has run, you want several things to happen after the fact. In addition, you may need these things to happen in order, ensuring that the next event happens only if the one before it succeeds. This is not only possible but also easy to do with promises.

Note

jQuery’s implementation of promises doesn’t support every scenario described in this section. See https://blog.domenic.me/youre-missing-the-point-of-promises for more details.

So far in your shopping cart update, you’ve been printing the results to the console. In a real application, you want to perform the following tasks after the server call finishes:

1.  Recalculate the cart’s total, applying necessary discounts.

2.  Update the view with the results.

3.  Reuse the message service to update the user that the call was a success.

Moreover, you want these tasks performed in order, and only if each task is successful should the next one begin. This ensures that the user won’t be erroneously notified that everything went swimmingly if an error happens to occur along the way (see the following listing).

Listing 7.6. Using promises to force control flow

This works because each then() returns a promise. If the reaction of the previous then() returns a promise, its value is used in the subsequent promise handed to the next then(). If the reaction returns a simple value, this value becomes the value in the promise passed forward. This allows you to chain them all together and makes for a straightforward and clean approach.

Being able to chain together multiple tasks in sequence in a few lines of code is amazing, but chaining can help you in other ways. Another amazing thing about chaining promises is that you can have more than one asynchronous process in the chain.

Chaining multiple asynchronous processes in sequence

Sometimes when you need several tasks to run in order, more than one may be asynchronous. Because you don’t know when asynchronous processes will finish, trying to place one into a sequence with other tasks might be pretty challenging. It’s easy, though, using promises. Because each then() is resolved before the next one is executed, the entire chain executes sequentially. This is still true even if multiple asynchronous processes are in the chain.

To demonstrate, let’s pretend that the server APIs require you to use the cart ID that’s returned by the shopping cart update in a subsequent GET call in order to properly display the cart onscreen. The following listing illustrates how to use promises to do this.

Listing 7.7. Executing more than one server call in order

In this chain, your update happens first. Then, after it returns, your next server call fires. Because the GET call from $resource already creates a promise, its value will be used in the promise passed to the next then().

Before finishing this discussion of promises, let’s get a quick overview of error handling. You saw error handling in some of the examples, but I didn’t go over any details.

7.3.4. Promise error handling

You can handle rejected promises in two ways. You saw the first way early on. Option 1 is to use the second reaction function of the promise’s then() method. The second reaction is the one triggered when there’s a rejection:

promise.then(
   function (value) {
   },
   function (reason) {
      // deal with the rejection
   }
);

Option 2 is to add an error-handling method called catch() to the end of your chain:

.catch(function (errorResult) {
    // deal with the rejection
});

Some browsers take issue with a method called catch(), because it’s a preexisting term in the JavaScript language. Alternatively, you can use this syntax:

["catch"](function(errorResult) {
    // deal with the rejection
});
Tip

Writing .catch() as ["catch"] looks strange but will help you avoid potential issues for any older browsers that don’t support ECMAScript 5. If you use this syntax, as shown in these examples, notice that it doesn’t have a dot in front of it.

You saw the second option being used with your shopping cart call. It used the message service to log the error and broadcast a user-friendly message to the user:

["catch"](function(errorResult) {
   messageSvc.displayError(errorResult);
});

With either method of error handling, rejections are passed down the chain to the first available error handler. This behavior seems obvious with the catch() method. What’s less obvious is that this is true even when using the optional reaction function for error handling. If a rejection occurs somewhere up the chain, and either type of error-handling method is encountered somewhere down the chain (even if it’s several then()s later), that error handler will be triggered and passed the error thrown.

As illustrated in our shopping cart examples, promises are powerful yet easy to use if you understand them. Frameworks and libraries sometimes add even more functionality, on top of what this chapter covers on promises. See their documentation for specific details.

Even if you have a project that requires you to support older browser versions, you can still use promises via your MV* framework if promises are supported or via a third-party library. The following are a few of the many popular promise third-party libraries at the time of this writing:

Aside from all of these being promise libraries, they also conform to the current preferred promise standard called Promise/A+. This is the same standard that native Java-Script promises are based on. If you’d like to read more about the Promise/A+ specification, a good resource is https://github.com/promises-aplus/promises-spec.

As an aside, jQuery also has its own version of promises, but as of this writing they aren’t Promise/A+ compliant. With jQuery, promise functionality is done via its Deferred object. If you’re interested, a great resource is the jQuery site itself: http://api.jquery.com/category/deferred-object.

Promises are also being implemented into the ECMAScript 6 (Harmony) version of JavaScript. Even before the specifications have been finalized, they already have limited support in many of today’s browsers.

At this point, you’re almost ready for our project. But you need to review the consumption of RESTful services first.

7.4. Consuming RESTful web services

This section covers consuming RESTful web services from your SPA. In many single-page applications today, these types of services are extremely common.

7.4.1. What is REST?

REST stands for Representational State Transfer. REST isn’t a protocol or even a specification but an architectural style for distributed hypermedia systems. It has gained such widespread popularity that many MV* frameworks not only provide out-of-the-box support for it but also favor this style by default.

In a RESTful service, APIs define the media types that represent resources and drive application state. The URL and the HTTP method used in the API define the processing rules for a given media type. The HTTP method describes what’s being done, and the URL uniquely identifies the resource affected by the action. REST can best be defined by describing its set of guiding principles.

7.4.2. REST principles

This section presents a few of the REST principles that most affect how you consume RESTful web services. This will also give you a good idea of what REST is about.

Everything is a resource

One of the fundamental concepts in REST is that everything is a resource. A resource is represented with a type and conceptually maps to an entity or set of entities. A resource could be a document, an image, or information that represents an object such as a person. The notion of a resource could also extend to a service such as today’s weather or, in our case, a shopping cart.

Every resource needs a unique identifier

Each resource in a RESTfull service should have a unique URL to identify it. This often entails creating and assigning unique IDs to the resource. You want to make sure that any ID you use in a URL in no way jeopardizes the security or integrity of your application. A common security measure is to assign a randomly generated ID for any resource that’s personal or confidential. To ensure that the ID is used by only the intended user, the server-side code makes sure the requester is the authenticated user assigned to the resource and has the proper authorization to perform the action on the resource.

REST emphasizes a uniform interface between components

You’ve already seen how HTTP methods are considered the verb of a web service call. Resource identifiers and HTTP methods are used to provide a uniform way of accessing resources. Table 7.2 gives some examples from the project.

Table 7.2. URLs in REST uniquely identify a resource, and the HTTP method describes that action being performed on the resource.

REST

URL: /shopping/carts/CART_ID_452 Method: GET Purpose: Fetch cart
URL: /shopping/carts/CART_ID_452/products/cod_adv_war Method: POST Purpose: Add an item to the cart
URL: /shopping/carts/CART_ID_452 Method: PUT Purpose: Update the entire cart’s contents
URL: / shopping/carts/CART_ID_452/products/cod_adv_war Method: DELETE Purpose: Remove all instances of a particular product from the cart

It’s important to note that the style of URL used isn’t part of REST, even though you sometimes see the phrase RESTful URL used in articles about REST.

Interactions are stateless

Session state for your application should be held in your SPA and shouldn’t rely on client context being stored on the server between requests. Each request made by the SPA to the server should convey all the information needed to fulfill the request and allow the SPA to transition to a new state.

Again, we’ve barely scratched the surface of REST here. For more information about REST and REST architecture, see http://en.wikipedia.org/wiki/Representational_state_transfer.

7.4.3. How MV* frameworks help us be RESTful

Thinking in terms of REST can take a little getting used to. Fortunately, MV* frameworks such as Backbone.js and AngularJS support REST right out of the box. For example, when you used Backbone.js for your shopping cart update, it automatically added the ID from your model to your URL so that the URL uniquely identifies the resource in the request. Frameworks that don’t have explicit models, such as AngularJS, might allow you to use path variables in a URL template to create a RESTful URL. You’ll see examples of path variables in a moment, when you look how AngularJS’s $resource object is used in your project.

MV* frameworks also help you consume RESTful services by making it easy to send the correct HTTP request method. They usually either come with canned functions for GET, POST, PUT, and DELETE or allow you to effortlessly generate them via configuration.

Now that you have a general idea of RESTful services and their guiding principles, you’re finally ready to tackle the project. In this project, you’ll get to see firsthand how promises and REST work together to maintain a shopping cart.

7.5. Project details

You’ll continue building on the preceding chapter’s used video game project by adding a shopping cart. As usual, you’ll use AngularJS for your MV* framework. It has built-in support for both promises and the consumption of RESTful services. As indicated at the beginning of this chapter, we discuss the server side of the application only conceptually here.

Because many server-side languages and frameworks might be used instead of what you’re using, I include a small summary of the tasks the server will need to perform for each call in appendix C. This way, you can create the server-side code by using a different tech stack if you wish. As always, the complete code is available for download. Let’s begin by walking through the setup of your data source.

7.5.1. Configuring REST calls

Earlier in this chapter, you learned about the $resource object from AngularJS. It makes consuming RESTful services easier, and its methods all return promises. You’ll use it for every server call in your project. Although I try to keep our discussions framework neutral, you’ll have to take a moment to further review how $resource works. It can be a little intimidating at first. After you walk through how it works, though, you’ll see how easy it is to use. After a gentle introduction to $resource, you’ll proceed with how it’ll be configured in your example SPA’s shopping cart service.

Creating URLs with AngularJS’s $resource

Like some of the other MV* frameworks, AngularJS offers support for RESTful service web service consumption out of the box by using its $resource object. This object adds a lot of sugar coating for the underlying XMLHttpRequest object to hide much of the boilerplate code you’d have to otherwise write yourself.

The main goal of $resource is to make it easy to work with RESTful services. Having a consistent and uniform way to represent resources is one of the principles of REST. After a URL style has been established, the $resource factory will help you create URLs that conform to this style easily.

The $resource factory enables you to define a template that will create resource URLs for each type of REST call you need to make. To use $resource, you can pass a URL, optional default parameters, and an optional set of actions to its constructor:

$resource(DEFAULT URL, DEFAULT URL PARAMS, OPTIONAL ACTIONS)

The following will serve as your default URL:

"controllers/shopping/carts"

The default will be used if you don’t override it. But in this project, you’re defining custom functions that will override it with their own URLs. Each custom action can have its own. To construct the URLs in the structure needed by your RESTful web services, you can use URL path parameters. As with routes, using a colon in front of a string in the URL indicates a parameter. Here’s an example URL from your configuration that includes URL path parameters:

"controllers/shopping/carts/:cartId/products/:productId"

The next argument, the optional parameter list, acts like a data map. It tells the $resource object that in one or more of these calls, this optional parameter list may be used. This list is in the form of key-value pairs. The left side is the name of a parameter in the URL. The right side is the value for the parameter. The @ symbol tells $resource that the value is a data property name, not just a string. With it present, the data object passed in will be scanned for a property with that name, and its value will be used in the URL’s path.

{
   cartId : "@cartId",
   productId : "@productId "
}

For example, if you passed in an object called myCart for the call, then the value for the URL parameter cartId would come from myCart.cartId. The value for the URL parameter productId would come from myCart.productId.

The nice thing about using $resource as a REST URL template is that you get a set of REST calls out of the box that are preconfigured with the following HTTP methods:

get()GET

query()GET (intended for a list; by default it expects an array)

save()POST

delete()DELETE

remove()DELETE (identical to delete(), in case the browser has a problem with the delete() action)

If you want to customize your calls as we’re doing, you can pass in the optional set of named functions (or actions in Angular-speak). You can use the action to create a completely customized call or override one of the out-of-the-box functions. For example, to create a custom action called updateCart(), you can include the following in your set of actions:

updateCart : {
   method : "PUT",
   url : "controllers/shopping/carts/:cartId"
}

After you have the $resource object configured, any calls you make with it automatically return a promise. You’ve already seen how to use them to work with the results of your calls.

In this chapter’s examples, you’re using $resource inside your shopping cart service because you have additional processes taking place before the data is returned to the controller. For simple data returns, you might want to wrap the $resource in another AngularJS object (such as a factory) and include it directly in your controller.

To see the complete documentation for $resource, visit the AngularJS site at https://docs.angularjs.org/api/ngResource/service/$resource.

Now that you’ve looked at $resource basics, let’s look at the entire code for the $resource instance used for your shopping cart (see the following listing). This will give you a picture of the type of calls that will be made inside the shopping cart service.

Listing 7.8. Configuration for your REST calls

With getCart(), you can get the cart’s content anytime you need it. You’ll use addProductItem() to add a new product to the cart or use removeAllProductItems() to remove all quantities of a given product type. You can use updateCart() to update the entire cart.

Tip

Though you’re not implementing security in this application, usually each call you make is validated for security and data integrity in the server-side code.

Because the previous chapter covered the application, in this section you’ll focus only on the code around your server calls and how to process the results. Let’s begin with adding new product items to the cart.

7.5.2. Adding product items to the cart

Following the URL format chosen earlier, you include the cart ID and the product ID in your RESTful service call to add a product item to the shopping cart (see table 7.3). If the product already exists in the cart, the quantity increases.

Table 7.3. RESTful call to add a product to the shopping cart

Method

URL

HTTP method

Request

Response

Cart.addProductItem() /shopping /carts/ CART_ID_89/products/ cod_adv_war POST Products Cart

You’ve modified the product display page to include a new button that will make the call to add a product item to the cart. When this button is clicked, it calls the addItem() action in your shopping cart’s $resource’s configuration. The following listing shows the modified view containing the new button.

Listing 7.9. Updated product display view

Figure 7.6 shows what the finished view looks like.

Figure 7.6. The product display page now features a button to add the item to the shopping cart.

Using an ng-click binding, you’ve bound the button click to a function called addToCart() on the $scope (ViewModel) in the controller to handle the new user action. In turn, this function calls the addToCart() function of your shopping cart service (see listing 7.10). As a reminder, the AngularJS $stateParams object allows you to access parameters from the route that was executed.

Listing 7.10. Application’s data holds cart ID

After the function call is made, the addToCart() function in the shopping cart service makes the RESTful call to the server for processing (see the following listing).

Listing 7.11. Function to make the addItem() call

The product ID and the cart ID get mapped to the default parameters of your add-ProductItem() custom action in the $resource configuration that you saw earlier. After the user has added items to the cart, a new view needs to display the cart’s contents. For this, you’ve added a brand-new view to the application.

7.5.3. Viewing the cart

In this call, you use the cart ID that was generated locally when the user landed on the welcome page. You can use it to get the current state of the cart. Table 7.4 lists this call’s properties.

Table 7.4. RESTful call to get the shopping cart to display its contents in the view

Method

URL

HTTP method

Request

Response

Cart.getCart() /shopping/carts/CART_ID_89 GET Empty Cart

To be able to view the cart from anywhere, a new link is added to the header. Clicking the link executes the viewCart route, which takes you to the shopping cart view:

<a id="viewCartLink" ui-sref="viewCart">View Cart</a>

When the controller behind the shopping cart view is called, the first thing it does is make a GET call to retrieve the cart from the server. You’ll look at this call from the controller first and then the shopping cart service.

In the controller where the call originates, the getCart() function returns the promise generated by the $resource call. As you may remember from our discussion of promises, the promise referenced here will be pending until the call completes:

var promise = shoppingCartSvc.getCart();
handleResponse(promise, null);

You’re also handing off the promise to a generic JavaScript function in the shopping cart controller that will handle the promise returned. The nice thing about promises is that they can be passed around like any other JavaScript object. In each call, whether it’s fetching the cart, updating it, or removing an item, you’ll process the promise in the same way every time (see the next listing).

Listing 7.12. Generic function to handle all cart promises

In the shopping cart service, your call to get the cart becomes a one-liner thanks to the magic of the out-of-the-box support for REST in your MV* framework. Here you’re passing an object with the ID of your cart as the payload of your call. The object will be scanned by $resource for a property name that matches the cartId URL parameter. Because you’ve stored the cart ID in the cartData object in the client, you can use it when you need the ID in the URL:

function getCart() {
  return Cart.getCart({cartId : cartData.cartId})
  .$promise;
}

Also remember that Cart is the variable name assigned to the $resource object you created. When the Cart.getCart() call completes, the promise is returned to the controller for the processing you saw previously. If all the promises are fulfilled in the promise chain when the call completes, the view displays all the items currently in the cart. It also shows the original price of each item, its used price, as well as the cost savings. At the top of the cart is a running total of all items and their used prices (see figure 7.7).

Figure 7.7. The shopping cart view allows the user to modify the cart’s contents.

With your cart returned, the user can use the UI controls to update it or delete items from it.

7.5.4. Updating the cart

When you update the cart, you’re not sending only the new items; you’re sending and receiving the entire cart. The RESTful URL identifies the cart you’re updating, and the request body has the updated cart data. Table 7.5 has this call’s properties.

Table 7.5. RESTful call to update the shopping cart with new input from the user

Method

URL

HTTP method

Request

Response

Cart.updateCart() /shopping/carts/CART_ID_89 PUT Cart Cart

For each entry, you provide an input control to let the user enter a new item count. You also have a button that will update the entire cart by each item. Each update button updates the entire cart in the same manner. It’s repeated beside each item only for convenience.

<span class="cartQuantityLabel">Quantity: </span>

<input type="text" ng-model="game.quantity" size="4">

<button class="cartItemButton" ng-click="updateQuantity()">
   Update
</button>

The updateQuantity() function needs no parameters because it always passes the entire cart. In the controller, you rely on the shopping cart service to make the update and pass the promise returned to your generic promise handler (see the following listing).

Listing 7.13. Controller code for cart updates

In the shopping cart service, you have a JavaScript function to create a cart object to send to the server. To make the request leaner, in the next listing you include only IDs and updatable properties.

Listing 7.14. Building the update request object

When the request object is ready, you can make the update request. Again, thanks to our MV* framework’s support for REST, you have a one-liner:

function updateCart(cart) {
   return Cart.updateCart(cart).$promise;
};

Like the other call, the update returns the promise to the controller so the promise chaining can process the results.

The last thing you need to do in the cart is provide the ability to remove items from it. In the next section, you’ll examine how to remove all quantities of a particular product type from the cart.

7.5.5. Removing products from the cart

To remove all items of a product from the cart, the most obvious choice in HTTP methods is DELETE. You need to make sure that you’re identifying both the cart and the product, just as you did when you added it. Table 7.6 has this call’s properties.

Table 7.6. RESTful call to remove all items of a product from the shopping cart

Method

URL

HTTP method

Request

Response

Cart.removeAllProductItems() /shopping/carts/CART_ID_89/products/cod_adv_war DELETE Empty Cart

In addition to users having the ability to update the quantity, the Delete button next to each product enables users to remove it completely. In the view, you’ve bound the button’s click to the removeItem() function on the controller. The function call passes forward the product ID of the product that’s being deleted.

<button class="cartItemButton"
ng-click="removeItem(game.productId)">Delete</button>

In the controller, as with getting the cart or updating it, you make the call and pass the returned promise to the generic promise handler (see the following listing).

Listing 7.15. Controller code for cart deletes

Finally, you get to the matching code in the shopping cart service where the call is made (see the following listing). The cart’s ID from your cart data object is mapped to the cartId URL parameter, and the product ID passed in is mapped to the productId parameter.

Listing 7.16. Controller code for cart deletes

Don’t forget that if you want to create the project in your own environment, the server-side supplement in appendix C begins with a summary of the objects and tasks. This is included in case you’re using a different tech stack than the example’s code. Also, as usual, the complete source code is available for download.

7.6. Chapter challenge

Now here’s a challenge for you to see what you’ve learned in this chapter. In the preceding chapter’s challenge, you created a movie search. You displayed movie titles that matched wholly or partially the text the user typed into an input field. A key-up event was bound to a function that published the field’s contents with each keystroke. A search module subscribed to that topic and performed a search accordingly. The search also used pub/sub to publish the results, which were displayed in an unordered list below the input field.

Extend this exercise by putting the stub data and the search logic on the server. You’ll still have a client-side module listening for the input field contents to be published. In turn, it will fire the server call every time it hears the topic. On the server, you can use any technologies you’re comfortable with. Make the server call a RESTful service call. Use a RESTful URL and an appropriate HTTP request method for this type of request. Use a promise to process the server call. Upon success, publish the results of the search. Write any errors to the console.

7.7. Summary

We covered a lot of ground in this chapter. Let’s review:

  • The server is still important to the single-page application, providing features such as security, validation, and standard APIs for accessing back-end data.
  • Having an agreement between the client and the server on the data type and the HTTP methods is essential for a call to be successful.
  • Native objects on both ends are converted to the agreed-upon data format in both the request and the response.
  • MV* frameworks that support server communication often take care of routing tasks, such as providing standard request types out of the box and handling data conversions.
  • MV* frameworks typically support server communication either through extending a parent model or via a data source object.
  • Call results are handled either through callbacks using continuation-passing style or through promises, depending on the framework or library used.
  • A promise represents the outcome of a pending asynchronous process. It starts as pending but eventually transitions to either fulfilled or rejected when the call completes.
  • A promise has several methods, but the most commonly used one is then(). This method allows you to register two functions (called reactions) to process a fulfilled or rejected promise.
  • The reaction for a fulfilled promise gives you access to the result data of a process. The reaction for a rejected promise contains the rejected reason (usually an Error object).
  • Reactions can be chained together to control the flow of a group of processes, even if other asynchronous calls are in the chain.
  • Promise errors can be handled either through the rejection reaction or via a catch() method.
  • REST stands for Representational State Transfer and is an architectural style for developing web services.
  • In REST, everything is a resource and should have a unique URL representing it.
  • HTTP methods describe the action for the resource. The four most common are GET, POST, PUT, and DELETE.
..................Content has been hidden....................

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