Section 8: Accessing API from Browser

54. The Query String

We saw last time that we can register a function handler with an endpoint using app.get() as shown below.

app.get('/weather', (req, res) => {
  res.send({
    location: "Briery Branch",
    forecast: "Cold"
  })
})

When a user goes to the /weather page of our website they receive an object containing values for the location and forecast keys.

The user can pass  data to our server using a query string in the url.  The query string is appended after the endpoint and consists of a ? followed by one or more (key=value) pairs separated by semicolons.

For example, the user can pass an address in the query string using the following url.

localhost:3000/weather?address=briery branch

Express automatically sets req.query equal to an object that holds the key/value pairs found in the query string.  Thus inside app.get() we can check to see if a particular key exists in the query and act accordingly.

The code below sends and error message to the client if an address is not passed in the query string of the url.

app.get('/weather', (req, res) => {
  if (!req.query.address) {
    return res.send({
      error: 'must specify addess in query string'
    })
  }
  res.send({
    location: req.query.address,
    forecast: "Cold"
  })
})

55. Building a JSON HTTP Endpoint

In this module, we simply copy the geocode and forecast modules to our web server and use them in our /weather endpoint.

56. ES6 Aside: Default Function Parameters

When we define a function we specify zero or more parameters.  It may be the case that the function is called with fewer arguments than the number of parameters specified.  When an argument is missing, the matching parameter’s value is set to undefined.

When defining the function we can supply default values for parameters so that when an argument is missing, rather than having the parameter’s value set to undefined, the parameter’s value set to the default value.  In the function foo, defined below, the age parameter is set to 0 if an argument is not passed in for the age parameter.

let foo = (name, age=0) => {     
    // age has the value 0 if less than 2 arguments are passed to foo
    ... 
}

If a parameter is an object we can assign the empty object as a default value.  In the example below info is set to {} (the empty object) if no arguments are passed to foo.

let foo = (info={}) => {     
    ... 
}

If we destructure the object parameter we can assign default values for the object properties as well.  Below, the age parameter has the value 0 if the object passed to foo does not have an age property.

let foo = ({name, age=0}={}) => {  
    ...
}

57. Browser HTTP Requests With Fetch

Our website’s home page was defined in the index.hbs view.  In that file we included a client side JavaScript file named app.js using the <script> tag.  Previously, the client side app.js simply printed a message to the console.  In this section we add a call to fetch to retrieve the weather infomation from our weather endpoint.

To do so, we add the following code to the client side app.js file.

fetch('/weather?address=boston')
  .then((response) => {
    response.json()
      .then((data) => {
        if (data.error) {
          console.log(data.error);
        }
        else {
          console.log(data)
        }
    })
})

Since the fetch() function is asynchronous it returns an object that contains a then() method.  When the data is received from the endpoint the callback function that is passed to then() is called.

Inside the callback method that is passed to then() which is chained to fetch(), we convert the response to json by calling response.json().  Since json() is also asynchronous we also chain a call to then() to response.json() and pass a callback to json().

When this callback that is passed to then() which is chained to json() is executed, we check to see if the object which is passed to the callback contains an error property.  If it does, we print to the console the error.  If not, we print the data.

58. Creating a Search Form

We can create a form in our index.hbs file using the following html.

<form>
    <input placeholder='Location'>
    <button>Search</button>
</form>

Inside app.js we’ll retrieve references to the form and button nodes in the DOM and then register an event handler to handle submit events on the form.

To receive the node references we can use the document.querySelector().

const weatherForm = document.querySelector('form')
const search = document.querySelector('input')

To register the submit event handler we’ll call weatherForm.addEventListener() and pass to it the name of the event (submit) and the callback function in the form of a lambda function.

weatherForm.addEventListener('submit', (e) => {
  e.preventDefault()

  const location = search.value

  fetch('/weather?address=' + location)
    .then((response) => {
      response.json()
        .then((data) => {
          if (data.error) {
            console.log(data.error);
          }
          else {
            console.log(data)
          }
        })
    })

})

In the event handler we call e.preventDefault() to prevent the browser from refreshing when the user hits the submit button.

Next we get the value that the user type into the input box and call fetch using the input value in the URL string.

59. Wiring up the User Interface

Rather than writing the weather data to the console, we need to render the data on the web page.  To do so we create two paragraph elements in the web page and render the location in the first and the temperature in the second.

So that we can identify the individual <p> elements we give them ids.

<p id="messageOne"></p>
<p id="messageTwo"></p>

Next, in app.js we first retrieve references to the two <p> elements using document.querySelector().

const messageOne = document.querySelector('#messageOne')
const messageTwo = document.querySelector('#messageTwo')

Then in the callback function passed to response.json().then() we put the appropriate text in the two <p> elements based on whether there was an error or not.

if (data.error) {
  messageOne.textContent = data.error
  messageTwo.textContent = ''
} 
else { 
  messageOne.textContent = data.city + ", " + data.region     
  messageTwo.textContent = 'Temperature: ' + data.temperature + ' degrees' 
}

© 2021 – 2024, Eric McGregor. All rights reserved. Unauthorized use, distribution and/or duplication of this material without express and written permission from the author is strictly prohibited.