APIs Demo

  • To talk to APIs, we need to install a new package called superagent

    • ​npm install superagent
  • In server.js, require the superagent dependency (Line 7)
  • The data received from APIs will be difficult to read. Use console.log regularly to understand data throughout today's lecture.
'use strict';

// Application Dependencies
require('dotenv').config();
const express = require('express');
const cors = require('cors');
const superagent = require('superagent');

// Application Setup
const app = express();
const PORT = process.env.PORT;
app.use(cors());

// Route Definitions
app.get('/', rootHandler);
app.get('/location', locationHandler);
app.get('/yelp', restaurantHandler);
app.use('*', notFoundHandler);
app.use(errorHandler);

// Route Handlers
function rootHandler(request, response) {
  response.status(200).send('City Explorer back-end');
}

function locationHandler(request, response) {
  const city = request.query.city;
  const locationData = require('./data/location.json');
  const location = new Location(city, locationData);
  response.status(200).send(location);
}

function restaurantHandler(request, response) {
  const restaurantsData = require('./data/restaurants.json');
  const arrayOfRestaurants = restaurantsData.nearby_restaurants;
  const restaurantsResults = [];
  arrayOfRestaurants.forEach(restaurantObj => {
    restaurantsResults.push(new Restaurant(restaurantObj));
  });
  response.send(restaurantsResults);
}

function notFoundHandler(request, response) {
  response.status(404).send('Not found');
}

function errorHandler(error, request, response, next) {
  response.status(500).json({ error: true, message: error.message });
}

// Constructors
function Location(city, locationData) {
  this.search_query = city;
  this.formatted_query = locationData[0].display_name;
  this.latitude = parseFloat(locationData[0].lat);
  this.longitude = parseFloat(locationData[0].lon);
}

function Restaurant(obj) {
  this.name = obj.restaurant.name;
  this.url = obj.restaurant.url;
  this.rating = obj.restaurant.user_rating.aggregate_rating;
  this.price = obj.restaurant.price_range;
  this.image_url = obj.restaurant.featured_image;
}

// App listener
app.listen(PORT,() => console.log(`Listening on port ${PORT}`));

Get Real Data

  • Instead of getting fake data we can use the LocationIQ API

  • Find the request URL from the API documentation.

    • https://us1.locationiq.com/v1/search.php?key=YOUR_PRIVATE_TOKEN&q=SEARCH_STRING&format=json

    • Every API is different.

  • Create a variable that contains the URL string (Line 28)

  • Use superagent's get method to call that URL (Line 29)

  • Use superagent's query method to pass all parts of the query string string as an object (Lines 30-34)

    • This will act similar to $.ajax('./data/whatever.json', ajaxSettings);

'use strict';

// Application Dependencies
require('dotenv').config();
const express = require('express');
const cors = require('cors');
const superagent = require('superagent');

// Application Setup
const app = express();
const PORT = process.env.PORT;
app.use(cors());

// Route Definitions
app.get('/', rootHandler);
app.get('/location', locationHandler);
app.get('/yelp', restaurantHandler);
app.use('*', notFoundHandler);
app.use(errorHandler);

// Route Handlers
function rootHandler(request, response) {
  response.status(200).send('City Explorer back-end');
}

function locationHandler(request, response) {
  const city = request.query.city;
  const url = 'https://us1.locationiq.com/v1/search.php';
  superagent.get(url)
    .query({
      key: YOUR_PRIVATE_TOKEN,
      q: city,
      format: 'json'
    });
  const location = new Location(city, locationData);
  response.status(200).send(location);
}

function restaurantHandler(request, response) {
  const restaurantsData = require('./data/restaurants.json');
  const arrayOfRestaurants = restaurantsData.nearby_restaurants;
  const restaurantsResults = [];
  arrayOfRestaurants.forEach(restaurantObj => {
    restaurantsResults.push(new Restaurant(restaurantObj));
  });
  response.send(restaurantsResults);
}

function notFoundHandler(request, response) {
  response.status(404).send('Not found');
}

function errorHandler(error, request, response, next) {
  response.status(500).json({ error: true, message: error.message });
}

// Constructors
function Location(city, locationData) {
  this.search_query = city;
  this.formatted_query = locationData[0].display_name;
  this.latitude = parseFloat(locationData[0].lat);
  this.longitude = parseFloat(locationData[0].lon);
}

function Restaurant(obj) {
  this.name = obj.restaurant.name;
  this.url = obj.restaurant.url;
  this.rating = obj.restaurant.user_rating.aggregate_rating;
  this.price = obj.restaurant.price_range;
  this.image_url = obj.restaurant.featured_image;
}

// App listener
app.listen(PORT,() => console.log(`Listening on port ${PORT}`));

Asynchronous Operations

  • After we request the data, we use .then() with a promise to instruct what to do after we receive the data (Lines 35-37).

    • .then() is the standard way to wait for something to come back  before doing something else. This is called a promise.

  • At this point, if we requested '/location?city=chicago' the server will never respond. What went wrong?

  • In addition to .then() we can use .catch() to respond to errors. (Lines 38-41)

    • Our error handler displays that there is an "Invalid key"

'use strict';

// Application Dependencies
require('dotenv').config();
const express = require('express');
const cors = require('cors');
const superagent = require('superagent');

// Application Setup
const app = express();
const PORT = process.env.PORT;
app.use(cors());

// Route Definitions
app.get('/', rootHandler);
app.get('/location', locationHandler);
app.get('/yelp', restaurantHandler);
app.use('*', notFoundHandler);
app.use(errorHandler);

// Route Handlers
function rootHandler(request, response) {
  response.status(200).send('City Explorer back-end');
}

function locationHandler(request, response) {
  const city = request.query.city;
  const url = 'https://us1.locationiq.com/v1/search.php'
  superagent.get(url)
    .query({
      key: 'XXX', 
      q: city, 
      format: 'json'
    })
    .then(locationIQResponse => {
    	console.log(locationIQResponse);
    })
    .catch(err => {
      console.log(err);
      errorHandler(err, request, response);
    });
}

function restaurantHandler(request, response) {
  const restaurantsData = require('./data/restaurants.json');
  const arrayOfRestaurants = restaurantsData.nearby_restaurants;
  const restaurantsResults = [];
  arrayOfRestaurants.forEach(restaurantObj => {
    restaurantsResults.push(new Restaurant(restaurantObj));
  });
  response.send(restaurantsResults);
}

function notFoundHandler(request, response) {
  response.status(404).send('Not found');
}

function errorHandler(error, request, response, next) {
  response.status(500).json({ error: true, message: error.message });
}

// Constructors
function Location(city, locationData) {
  this.search_query = city;
  this.formatted_query = locationData[0].display_name;
  this.latitude = parseFloat(locationData[0].lat);
  this.longitude = parseFloat(locationData[0].lon);
}

function Restaurant(obj) {
  this.name = obj.restaurant.name;
  this.url = obj.restaurant.url;
  this.rating = obj.restaurant.user_rating.aggregate_rating;
  this.price = obj.restaurant.price_range;
  this.image_url = obj.restaurant.featured_image;
}

// App listener
app.listen(PORT,() => console.log(`Listening on port ${PORT}`));

API Keys

  • DO NOT put API keys directly in the code. Put them in .env and on the individual Heroku pipeline app Settings > Config Vars.

    • You'll have to stop and restart nodemon for .env changes

  • Add "process.env.LOCATION_KEY" to the key (Line 31)

  • Request '/location?city=chicago' again to get the correct response

'use strict';

// Application Dependencies
require('dotenv').config();
const express = require('express');
const cors = require('cors');
const superagent = require('superagent');

// Application Setup
const app = express();
const PORT = process.env.PORT;
app.use(cors());

// Route Definitions
app.get('/', rootHandler);
app.get('/location', locationHandler);
app.get('/yelp', restaurantHandler);
app.use('*', notFoundHandler);
app.use(errorHandler);

// Route Handlers
function rootHandler(request, response) {
  response.status(200).send('City Explorer back-end');
}

function locationHandler(request, response) {
  const city = request.query.city;
  const url = 'https://us1.locationiq.com/v1/search.php'
  superagent.get(url)
    .query({
      key: process.env.LOCATION_KEY, 
      q: city, 
      format: 'json'
    })
    .then(locationIQResponse => {
    	console.log(locationIQResponse);
    })
    .catch(err => {
      console.log(err);
      errorHandler(err, request, response);
    });
}

function restaurantHandler(request, response) {
  const restaurantsData = require('./data/restaurants.json');
  const arrayOfRestaurants = restaurantsData.nearby_restaurants;
  const restaurantsResults = [];
  arrayOfRestaurants.forEach(restaurantObj => {
    restaurantsResults.push(new Restaurant(restaurantObj));
  });
  response.send(restaurantsResults);
}

function notFoundHandler(request, response) {
  response.status(404).send('Not found');
}

function errorHandler(error, request, response, next) {
  response.status(500).json({ error: true, message: error.message });
}

// Constructors
function Location(city, locationData) {
  this.search_query = city;
  this.formatted_query = locationData[0].display_name;
  this.latitude = parseFloat(locationData[0].lat);
  this.longitude = parseFloat(locationData[0].lon);
}

function Restaurant(obj) {
  this.name = obj.restaurant.name;
  this.url = obj.restaurant.url;
  this.rating = obj.restaurant.user_rating.aggregate_rating;
  this.price = obj.restaurant.price_range;
  this.image_url = obj.restaurant.featured_image;
}

// App listener
app.listen(PORT,() => console.log(`Listening on port ${PORT}`));
PORT=3000
LOCATION_KEY=XXXXXXXXXXXXXX

Construct Verified Data

  • Use console.log to view LocationIQ's response. We will use the body property from the data received. (Line 36)
    • We'll keep the first location in the body array
  • We want to construct a new Location object (Line 37) passing the city and location object.

  • Update the constructor as necessary (Lines 65-70)

  • Send that to the client (Line 38).

    • DO NOT place response.send() outside of .then().

'use strict';

// Application Dependencies
require('dotenv').config();
const express = require('express');
const cors = require('cors');
const superagent = require('superagent');

// Application Setup
const app = express();
const PORT = process.env.PORT;
app.use(cors());

// Route Definitions
app.get('/', rootHandler);
app.get('/location', locationHandler);
app.get('/yelp', restaurantHandler);
app.use('*', notFoundHandler);
app.use(errorHandler);

// Route Handlers
function rootHandler(request, response) {
  response.status(200).send('City Explorer back-end');
}

function locationHandler(request, response) {
  const city = request.query.city;
  const url = 'https://us1.locationiq.com/v1/search.php'
  superagent.get(url)
    .query({
      key: process.env.LOCATION_KEY,
      q: city, 
      format: 'json'
    })
    .then(locationIQResponse => {
      const topLocation = locationIQResponse.body[0];
      const myLocationResponse = new Location(city, topLocation);
      response.status(200).send(myLocationResponse);
    })
    .catch(err => {
      console.log(err);
      errorHandler(err, request, response);
    });
}

function restaurantHandler(request, response) {
  const restaurantsData = require('./data/restaurants.json');
  const arrayOfRestaurants = restaurantsData.nearby_restaurants;
  const restaurantsResults = [];
  arrayOfRestaurants.forEach(restaurantObj => {
    restaurantsResults.push(new Restaurant(restaurantObj));
  });
  response.send(restaurantsResults);
}

function notFoundHandler(request, response) {
  response.status(404).send('Not found');
}

function errorHandler(error, request, response, next) {
  response.status(500).json({ error: true, message: error.message });
}

// Constructors
function Location(city, location) {
  this.search_query = city;
  this.formatted_query = location.display_name;
  this.latitude = parseFloat(location.lat);
  this.longitude = parseFloat(location.lon);
}

function Restaurant(obj) {
  this.name = obj.restaurant.name;
  this.url = obj.restaurant.url;
  this.rating = obj.restaurant.user_rating.aggregate_rating;
  this.price = obj.restaurant.price_range;
  this.image_url = obj.restaurant.featured_image;
}

// App listener
app.listen(PORT,() => console.log(`Listening on port ${PORT}`));

Test the App and Continue

'use strict';

// Application Dependencies
require('dotenv').config();
const express = require('express');
const cors = require('cors');
const superagent = require('superagent');

// Application Setup
const app = express();
const PORT = process.env.PORT;
app.use(cors());

// Route Definitions
app.get('/', rootHandler);
app.get('/location', locationHandler);
app.get('/yelp', restaurantHandler);
app.use('*', notFoundHandler);
app.use(errorHandler);

// Route Handlers
function rootHandler(request, response) {
  response.status(200).send('City Explorer back-end');
}

function locationHandler(request, response) {
  const city = request.query.city;
  const url = 'https://us1.locationiq.com/v1/search.php'
  superagent.get(url)
    .query({
      key: process.env.GEO_KEY, 
      q: city, 
      format: 'json'
    })
    .then(locationResponse => {
    	const topLocation = locationIQResponse.body[0];
      	const myLocationResponse = new Location(city, topLocation);
      	response.status(200).send(myLocationResponse);
  	})
    .catch(err => {
    	console.log(err);
    	errorHandler(err.request.response);
  	});
}

function restaurantHandler(request, response) {
  const queryString = request.query;
  console.log(queryString);
  const lat = parseFloat(request.query.latitude);
  const lon = parseFloat(request.query.longitude);
  const currentPage = request.query.page;
  const numPerPage = 4;
  const start = ((currentPage - 1) * numPerPage + 1);
  const restaurantData = require('./data/restaurants.json');
  const arrayOfRestaurants = restaurantData.businesses;
  const restaurantsResults = [];
  arrayOfRestaurants.forEach(restaurantObj => {
    restaurantsResults.push(new Restaurant(restaurantObj));
  });
  response.send(restaurantsResults);
}

function notFoundHandler(request, response) {
  response.status(404).send('Not found');
}

function errorHandler(error, request, response, next) {
  response.status(500).json({ error: true, message: error.message });
}

// Constructors
function Location(city, location) {
  this.search_query = city;
  this.formatted_query = location.display_name;
  this.latitude = parseFloat(location.lat);
  this.longitude = parseFloat(location.lon);
}

function Restaurant(obj) {
  this.name = obj.restaurant.name;
  this.url = obj.restaurant.url;
  this.rating = obj.restaurant.user_rating.aggregate_rating;
  this.price = obj.restaurant.price_range;
  this.image_url = obj.restaurant.featured_image;
}

// App listener
app.listen(PORT,() => console.log(`Listening on port ${PORT}`));
  • Test your server: http://localhost:3001/location?city=cedar+rapids,+iowa

  • Go to the LocationIQ dashboard and see requests made.

  • Continue to process the remaining requests (yelp, weather).

  • Go through the Yelp API documentation to determine what data it need
  • Test your server: http://localhost:3001/yelp?latitude=41.9758872&longitude=-91.6704053&page=1
    • The latitude and longitude are retrieved from the location query
  • Display the query information (Lines 47-48)
  • After viewing the console, store the data you want (Lines 49-53)

Get Real Data

  • Find the request URL from the Yelp API documentation.

  • Create a variable that contains the URL string (Line 52)

  • Use superagent's get method to call that URL (Line 53)

  • Use superagent's query method to pass all parts of the query string string as an object (Lines 54-59)

  • Note that Yelp's authentication process is different from LocationIQs (Line 60).

    • Add your YELP_KEY to .env and the Heroku Environment variables.

'use strict';

// Application Dependencies
require('dotenv').config();
const express = require('express');
const cors = require('cors');
const superagent = require('superagent');

// Application Setup
const app = express();
const PORT = process.env.PORT;
app.use(cors());

// Route Definitions
app.get('/', rootHandler);
app.get('/location', locationHandler);
app.get('/yelp', restaurantHandler);
app.use('*', notFoundHandler);
app.use(errorHandler);

// Route Handlers
function rootHandler(request, response) {
  response.status(200).send('City Explorer back-end');
}

function locationHandler(request, response) {
  const city = request.query.city;
  const url = 'https://us1.locationiq.com/v1/search.php'
  superagent.get(url)
    .query({
      key: process.env.GEO_KEY, 
      q: city, 
      format: 'json'
    })
    .then(locationResponse => {
    	const topLocation = locationIQResponse.body[0];
      	const myLocationResponse = new Location(city, topLocation);
      	response.status(200).send(myLocationResponse);
  	})
    .catch(err => {
    	console.log(err);
    	errorHandler(err.request.response);
  	});
}

function restaurantHandler(request, response) {
  const lat = parseFloat(request.query.latitude);
  const lon = parseFloat(request.query.longitude);
  const currentPage = request.query.page;
  const numPerPage = 4;
  const start = ((currentPage - 1) * numPerPage + 1);
  const url = 'https://api.yelp.com/v3/businesses/search';
  superagent.get(url)
    .query({
      latitude: lat,
      longitude: lon,
      limit: numPerPage,
      offset: start
    })
    .set('Authorization', `Bearer ${process.env.YELP_KEY}`)
  	
  const restaurantsData = require('./data/restaurants.json');
  const arrayOfRestaurants = restaurantsData.nearby_restaurants;
  const restaurantsResults = [];
  arrayOfRestaurants.forEach(restaurantObj => {
    restaurantsResults.push(new Restaurant(restaurantObj));
  });
  response.send(restaurantsResults);
}

function notFoundHandler(request, response) {
  response.status(404).send('Not found');
}

function errorHandler(error, request, response, next) {
  response.status(500).json({ error: true, message: error.message });
}

// Constructors
function Location(city, location) {
  this.search_query = city;
  this.formatted_query = location.display_name;
  this.latitude = parseFloat(location.lat);
  this.longitude = parseFloat(location.lon);
}

function Restaurant(obj) {
  this.name = obj.restaurant.name;
  this.url = obj.restaurant.url;
  this.rating = obj.restaurant.user_rating.aggregate_rating;
  this.price = obj.restaurant.price_range;
  this.image_url = obj.restaurant.featured_image;
}

// App listener
app.listen(PORT,() => console.log(`Listening on port ${PORT}`));

Yelp Links

  • https://www.yelp.com/login?return_url=/developers/v3/manage_app
  • https://docs.developer.yelp.com/reference/v3_business_search
  • https://docs.developer.yelp.com/docs/fusion-intro
  • https://stackoverflow.com/questions/51433786/yelp-api-http-request-authorization-bearer

Asynchronous Operations

  • Use .then() with a promise to instruct what to do after we receive the data (Lines 61-68).

    • Move all remaining code from yesterday's class into .then(). Remove the require() for restaurants.json (Lines 62-67)

  • Use .catch() to respond to errors. (Lines 69-72)

  • Intentionally create errors so you can read and understand error messages.

'use strict';

// Application Dependencies
require('dotenv').config();
const express = require('express');
const cors = require('cors');
const superagent = require('superagent');

// Application Setup
const app = express();
const PORT = process.env.PORT;
app.use(cors());

// Route Definitions
app.get('/', rootHandler);
app.get('/location', locationHandler);
app.get('/yelp', restaurantHandler);
app.use('*', notFoundHandler);
app.use(errorHandler);

// Route Handlers
function rootHandler(request, response) {
  response.status(200).send('City Explorer back-end');
}

function locationHandler(request, response) {
  const city = request.query.city;
  const url = 'https://us1.locationiq.com/v1/search.php'
  superagent.get(url)
    .query({
      key: process.env.GEO_KEY, 
      q: city, 
      format: 'json'
    })
    .then(locationResponse => {
    	const topLocation = locationIQResponse.body[0];
      	const myLocationResponse = new Location(city, topLocation);
      	response.status(200).send(myLocationResponse);
  	})
    .catch(err => {
    	console.log(err);
    	errorHandler(err.request.response);
  	});
}

function restaurantHandler(request, response) {
  const lat = parseFloat(request.query.latitude);
  const lon = parseFloat(request.query.longitude);
  const currentPage = request.query.page;
  const numPerPage = 4;
  const start = ((currentPage - 1) * numPerPage + 1);
  const url = 'https://api.yelp.com/v3/businesses/search';
  superagent.get(url)
  	.query({
      latitude: lat,
      longitude: lon,
      limit: numPerPage,
      offset: start
    })
    .set('Authorization', `Bearer ${process.env.YELP_KEY}`)
  	.then(yelpResponse => {
            const arrayOfRestaurants = restaurantsData.nearby_restaurants;
            const restaurantsResults = [];
            arrayOfRestaurants.forEach(restaurantObj => {
              restaurantsResults.push(new Restaurant(restaurantObj));
            });
            response.send(restaurantsResults);
  	})
    .catch(err => {
      console.log(err);
      errorHandler(err, request, response);
    });
}

function notFoundHandler(request, response) {
  response.status(404).send('Not found');
}

function errorHandler(error, request, response, next) {
  response.status(500).json({ error: true, message: error.message });
}

// Constructors
function Location(city, location) {
  this.search_query = city;
  this.formatted_query = location.display_name;
  this.latitude = parseFloat(location.lat);
  this.longitude = parseFloat(location.lon);
}

function Restaurant(obj) {
  this.name = obj.restaurant.name;
  this.url = obj.restaurant.url;
  this.rating = obj.restaurant.user_rating.aggregate_rating;
  this.price = obj.restaurant.price_range;
  this.image_url = obj.restaurant.featured_image;
}

// App listener
app.listen(PORT,() => console.log(`Listening on port ${PORT}`));

Construct Verified Data

  • Use console.log for the response received (Line 62). Yelp is using a "body" property with a "businesses" property inside it. (Line 63)

  • We want to construct new Restaurant objects passing in each raw data object from the array. (Lines 65-67)

  • Update the constructor as necessary (Lines 92-98)

  • Send the restaurantsResults array as our response to the client (Line 68).

'use strict';

// Application Dependencies
require('dotenv').config();
const express = require('express');
const cors = require('cors');
const superagent = require('superagent');

// Application Setup
const app = express();
const PORT = process.env.PORT;
app.use(cors());

// Route Definitions
app.get('/', rootHandler);
app.get('/location', locationHandler);
app.get('/yelp', restaurantHandler);
app.use('*', notFoundHandler);
app.use(errorHandler);

// Route Handlers
function rootHandler(request, response) {
  response.status(200).send('City Explorer back-end');
}

function locationHandler(request, response) {
  const city = request.query.city;
  const url = 'https://us1.locationiq.com/v1/search.php'
  superagent.get(url)
    .query({
      key: process.env.GEO_KEY, 
      q: city, 
      format: 'json'
    })
    .then(locationResponse => {
    	const topLocation = locationIQResponse.body[0];
      	const myLocationResponse = new Location(city, topLocation);
      	response.status(200).send(myLocationResponse);
  	})
    .catch(err => {
    	console.log(err);
    	errorHandler(err.request.response);
  	});
}

function restaurantHandler(request, response) {
  const lat = parseFloat(request.query.latitude);
  const lon = parseFloat(request.query.longitude);
  const currentPage = request.query.page;
  const numPerPage = 4;
  const start = ((currentPage - 1) * numPerPage + 1);
  const url = 'https://api.yelp.com/v3/businesses/search';
  superagent.get(url)
  	.query({
      latitude: lat,
      longitude: lon,
      limit: numPerPage,
      offset: start
    })
    .set('Authorization', `Bearer ${process.env.YELP_KEY}`)
  	.then(yelpResponse => {
            console.log(yelpResponse);
            const arrayOfRestaurants = yelpResponse.body.businesses;
            const restaurantsResults = [];
            arrayOfRestaurants.forEach(restaurantObj => {
              restaurantsResults.push(new Restaurant(restaurantObj));
            });
            response.send(restaurantsResults);
  	})
    .catch(err => {
      console.log(err);
      errorHandler(err, request, response);
    });
}

function notFoundHandler(request, response) {
  response.status(404).send('Not found');
}

function errorHandler(error, request, response, next) {
  response.status(500).json({ error: true, message: error.message });
}

// Constructors
function Location(city, location) {
  this.search_query = city;
  this.formatted_query = location.display_name;
  this.latitude = parseFloat(location.lat);
  this.longitude = parseFloat(location.lon);
}

function Restaurant(obj) {
  this.name = obj.name;
  this.url = obj.url;
  this.rating = obj.rating;
  this.price = obj.price;
  this.image_url = obj.image_url;
}

// App listener
app.listen(PORT,() => console.log(`Listening on port ${PORT}`));

Test the App and Continue

  • Test your server

  • Continue to process the remaining requests (weather).

  • Look at front-end templates or Trello cards for expected responses

  • Remove the data folder that contains location.json and restaurants.json

  • Remove any extra console.log statements when finished.

    • The console.log(err) statements might be helpful to keep