Tuesday, May 11, 2021

RESTful Web Services: Consuming a Web Service API

In this series on RESTful Web services, we’ve been learning how to implement both a RESTful Web Service and client component using Node.js, Express.js, and JavaScript. The first three installments focused mainly on the server-side Web Services code. Now it’s time to shift our attention to the client-side of the equation, i.e., the consumer of our service. RESTful services are stateless so that any client that utilizes the HTTP protocol can access the data. The client can be a Browser, Native application, IoT device, or any other technology that supports the HTTP or HTTPS protocol. Since we are working with JavaScript, it only makes sense that we employ a browser to access our service.

Displaying Object Data

In Supporting CRUD Operations, we fetched object data via HTML links. That worked well enough, but the data was not processed on the client-side in any way, so that the JSON data was displayed in its raw form:

JSON data for 2nd vehicle

Let’s update the index.html page to display the vehicle data in a more meaningful way.

To begin with, we’ll create a whole new index.html file where the links point to the server that we created in the Supporting CRUD Operations tutorial. Here’s the markup for that so far:

<!DOCTYPE html>
<html lang="en">
  <head>
    <!-- Required meta tags -->
    <meta charset="utf-8" />
    <meta
      name="viewport"
      content="width=device-width, initial-scale=1, shrink-to-fit=no"
    />

    <title>RESTful Web Services Demo</title>
    <script
      src="https://code.jquery.com/jquery-3.6.0.min.js"
      integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4="
      crossorigin="anonymous"
    ></script>
    <script>
      $(() => {
           //document ready event
        });
      });
    </script>
  </head>
  <body>
    <!-- Bootstrap CSS -->
    <link
      rel="stylesheet"
      href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css"
      integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T"
      crossorigin="anonymous"
    />
    <div class="container">
      <h1>RESTful Web Services</h1>
      <h2>Consuming RESTful Web Services Demo</h2>

      <ul id="getterLinks" style="list-style-type: none; padding: 0; margin: 0;">
        <li><a href="https://5t6ml.sse.codesandbox.io/vehicles/1">Get 1st Vehicle Data</a></li>
        <li><a href="https://5t6ml.sse.codesandbox.io/vehicles/2">Get 2nd Vehicle Data</a></li>
        <li><a href="https://5t6ml.sse.codesandbox.io/vehicles/3">Get 3rd Vehicle Data</a></li>
        <li><a href="https://5t6ml.sse.codesandbox.io/vehicles/bad">Get Bad Vehicle Data (Fail Test)</a></li>
      </ul>
    </div>
  </body>
</html>

Important: if you did not enable CORS on the server, you will get a “CORS header ‘Access-Control-Allow-Origin’ missing” error when you contact the server on a different domain. The following code lets the server tell the browser it’s permitted to use an additional origin:

const cors = require('cors');
app.use(cors());

Next, we’ll intercept the link navigation and call the server endpoint via AJAX so that we can remain on the page and display the vehicle data in a form:

$(() => {
  $('#getterLinks a').click(evt => {
    evt.preventDefault();
    const link = evt.target.href;
    // clear the form
    $("#vehicleForm :input").val("");
    $.getJSON(link, (data) => {
      $("#vehicleForm :input").each((index, elm) => {
        const $elm = $(elm);
        $elm.val(data[$elm.attr('name')]);
      });
      $lblResults.text("");
      $buttons.prop('disabled', false);
    }).fail((response, textStatus, xhr) => {
      console.log("Couldn't load vehicle!");
      $buttons.prop('disabled', true);
    });
  });
});

Here is the markup for our form:

<form id="vehicleForm" action="https://5t6ml.sse.codesandbox.io/vehicles">
  <div class="form-group">
    <label for="id">ID</label>
    <input readonly class="form-control" name="id" />
  </div>

  <div class="form-group">
    <label for="make">Make</label>
    <input class="form-control" name="make" />
  </div>

  <div class="form-group">
    <label for="model">Model</label>
    <input class="form-control" name="model" />
  </div>

  <div class="form-group">
    <label for="year">Year</label>
    <input class="form-control" name="year" />
  </div>

  <div class="form-group">
    <label for="color">Color</label>
    <input class="form-control" name="color" />
  </div>

  <div class="form-group">
    <label for="plateNumber">Plate Number</label>
    <input
      class="form-control"
      name="plateNumber"
    />
  </div>

  <div class="form-group">
    <label for="vin">VIN Number</label>
    <input
      class="form-control"
      name="vin"
    />
  </div>
</form>

As it happens, I mapped the form field names to those of the JSON object so that we can simply iterate over each to fetch the corresponding JSON attribute:

$.getJSON(link, (data) => {
  $("#vehicleForm :input").each((index, elm) => {
    $elm = $(elm);
    $elm.val(data[$elm.attr('name')]);
  });
}).fail((err) => {
  console.log( err );
});

Displaying Messages to the User

Sometimes it’s not enough to set the form contents. For updates and exceptions, you will want to notify the user of success or failure. We’ll add a label above the form for that purpose, along with a function that sets the message:

<style>
  #lblResults {
    font-weight: bold;
    font-size: 1.4rem;
  }
</style>
    
<h2>Consuming RESTful Web Services Demo</h2>
<label id="lblResults"></label>

In addition to setting the label value, the showUserMessage() function, shown below, also clears the message after 5 seconds, so that the message is only shown while it is relevant to the previous action:

$(() => {
  const $lblResults = $('#lblResults');
  const $buttons    = $(':input[type="button"]');
  
  const showUserMessage = (msg, disableButtons) => {
    $('.container').get(0).scrollIntoView(200);
    $lblResults.text(msg);
    setTimeout(() => $lblResults.text(''), 5000);
    $buttons.prop('disabled', disableButtons);
  };
// ...

Now we can call showUserMessage() from our AJAX success and failure handlers:

$.getJSON(link, (data) => {
  $("#vehicleForm :input").each((index, elm) => {
    const $elm = $(elm);
    $elm.val(data[$elm.attr('name')]);
  });
  showUserMessage("", false);
}).fail((response, textStatus, xhr) => {
  showUserMessage("Couldn't load vehicle!", true);
});

In a real application, one might set different classes on the message label to distinguish errors from success notifications, but that’s beyond the scope of this article.

js api

Modifying Object Data

This time around, form fields – with the exception of the ID – are editable, so we can update their values in place. When we’re ready we can submit the form contents by clicking a button that we can place at the bottom of the form:

<button id="btnUpdate" type="button" class="btn btn-primary" disabled>Update</button>

The button click invokes the following handler:

$("#btnUpdate").click((evt) => {
  const form = evt.target.form;
  const id   = $("#vehicleForm input[name=id]").val();
  $.ajax({
    url: `${form.action}/${id}`,
    type: "POST",
    data: $(form).serialize(),
    success: (data, textStatus, xhr) => {
      showUserMessage('Vehicle successfully updated.', false);
    },
    error: (xhr, textStatus, errorThrown) => {
      showUserMessage(xhr.responseText, true);
    }
  });
});

Deleting Object Data

Rather than delete a specific vehicle, as we did previously, now the Delete button applies to whatever vehicle is currently displayed in the form. Here is the button markup:

<button id="btnDelete" type="button" class="btn btn-primary" disabled>Delete</button>

The code that invokes the API is very similar to that of the update, with two major differences:

  1. The HTTP request type is DELETE.
  2. The form contents are cleared after a successful deletion.
$("#btnDelete").click((evt) => {
  const form = evt.target.form;
  const id   = $("#vehicleForm input[name=id]").val();
  $.ajax({
    url: `${form.action}/${id}`,
    type: "DELETE",
    success: (data, textStatus, xhr) => {
      showUserMessage(xhr.responseText, true);
      // clear the form
      $("#vehicleForm :input").val("");
    },
    error: (xhr, textStatus, errorThrown) => {
      showUserMessage(xhr.responseText, true);
    }
  });
});

Conclusion

And that concludes this series on working with RESTful web services in JavaScript. Here’s a demo that showcases today’s functionality:

Popular Articles

Featured