Build News List With RESTful API & Fetch API

In this post, you will learn how to build a simple news list with RESTful API & Fetch API.

Prerequisites

We expect that you are familiar with the following:

  • JavaScript (Basic level includes DOM Manipulation)
  • CSS (Basic level)

Expected Learnings

Your key learnings from this project will be:

  • To learn how to use Fetch API to request & retrieve data from Third Party.
  • To learn how third-party APIs work, and how to use them to enhance your websites.

Quick Introduction to Promise based Data Fetching – Fetch API

Fetch API is a modern API that allows developer to request and fetch resources across the network.

The fetch() method takes one mandatory argument, the path to the resource you want to fetch. It returns a Promise that resolves to the Response to that request — as soon as the server responds with headers — even if the server response is an HTTP error status.

Quick Introduction to RESTful API

The RESTful API allows developer to retrieve data by making HTTP requests to specific URLs.

That data can be used to GET, PUT, POST and DELETE data types, which refers to the reading, updating, creating and deleting of operations concerning resources.

This is a common pattern you’ll encounter with API in the real world.

We will use New York Times API throughout this post, so please register & obtain a key for the Article Search API before proceed to the next step. https://developer.nytimes.com/

Step 1: Setting Up HTML & CSS

<!DOCTYPE html>
<html>
<head>
	<meta charset="utf-8">
	<meta name="viewport" content="width=device-width, initial-scale=1.0">
	<title>Build News List With RESTful API | onlyWebPro.com</title>
	<style>
		body {
			font-family: "Times New Roman", Times, serif;
		}
		#loading {
			position: absolute;
			top: 0;
			left: 0;
			background: rgba(0,0,0, .8);
			color: #fff;
			justify-content: center;
			align-items: center;
			height: 100vh;
			width: 100%;
		}
	</style>
</head>
<body>
	<h1>News Article Search</h1>
	<div class="wrapper">
		<div class="controls">
			<form>
				<p>
					<label for="search">Enter a SINGLE search term (required): </label>
					<input type="text" id="search" class="search" required>
				</p>
				<p>
					<label for="start-date">Enter a start date (format YYYYMMDD): </label>
					<input type="date" id="start-date" class="start-date" pattern="[0-9]{8}">
				</p>
				<p>
					<label for="end-date">Enter an end date (format YYYYMMDD): </label>
					<input type="date" id="end-date" class="end-date" pattern="[0-9]{8}">
				</p>
				<p>
					<button class="submit">Submit search</button>
				</p>
			</form>
		</div>
		<div id="loading">
			<div>Retrieving data... Please wait a moment.</div>
		</div>
		<div class="results">
			<nav>
				<button class="prev">Previous 10</button>
				<button class="next">Next 10</button>
			</nav>
			<section>
			</section>
		</div>
	</div>
	<script>
		// javascript code goes here
	</script>
</body>
</html>

The HTML file contains:

  • A <form> section that allows us to enter a search terms, start date & end date.
  • A loading UI that will be showed when user pressed on the Submit search button.
  • A pagination UI that allows us to navigate to next / previous page.
  • A <section> container that going host the news list that returned by NYTimes API.
  • <script> tag at the bottom of the page, we will put the code there in the next step.

Step 2: Setting Up The Interaction Base

Place the following script into the <script> tag at the bottom of the page:

const baseURL = 'https://api.nytimes.com/svc/search/v2/articlesearch.json';
const key = 'INSERT-YOUR-API-KEY-HERE';
let url;
const searchTerm = document.querySelector('.search');
const startDate = document.querySelector('.start-date');
const endDate = document.querySelector('.end-date');
const searchForm = document.querySelector('form');
const nextBtn = document.querySelector('.next');
const prevBtn = document.querySelector('.prev');
const section = document.querySelector('section');
const nav = document.querySelector('nav');
const loading = document.getElementById('loading');
loading.style.display = 'none';
nav.style.display = 'none';
prevBtn.style.display = 'none';
let pageNumber = 0;
searchForm.addEventListener('submit', submitSearch);

Find & replace following key with the actual API key that you obtained at NYTimes Developer account:

const key = 'INSERT-YOUR-API-KEY-HERE'; 

There’s a lot of code here, let’s explain it one by one:

  • The baseURL contains the URL that request & fetch resources from NYTimes.
  • The key contains the NYTimes API key.
  • The url, a variable that we will use it for constructing complete URL path, we will talk about this later.
  • A list of reference to all the DOM elements we will need to manipulate.
  • A list of styles that applied to loading UI & pagination UI.
  • The pageNumber is used to define the initial page number and status of the navigation being displayed.
  • An event listener that runs a function called submitSearch() when the form is submitted.

Step 3: Defining Submit Search Function

Add following function definition below previous line:

function submitSearch(e) {
	pageNumber = 0;
	fetchResults(e);
}
  • submitSearch function sets pageNumber back to 0 then called fetchResults(e) function.

Step 4: Defining Fetch Result Function

Add following function definition below previous line:

function fetchResults(e) {
	e.preventDefault();
	loading.style.display = 'flex';
	url = baseURL + '?api-key=' + key + '&page=' + pageNumber + '&q=' + searchTerm.value + '&fq=document_type:("article")';
	if(startDate.value != ''){
		url += '&begin_date=' + startDate.value;
	}
	if(endDate.value != '') {
		url += '&end_date=' + endDate.value;
	}
	fetch(url).then(response => {
		return response.json();
	}).then(json => {
		displayResults(json);
		loading.style.display = 'none';
	}).catch(e => {
		console.log(e);
	});
}
  • First calls preventDefault() on the event object, to stop the form actually submitting.
  • Reveal the loading UI to notify end user your page is fetching the data from server.
  • Assemble full URL path into url variable.
  • Next, use IF statement to check whether the startDate and endDate have had values filled in on them. If they do, we append their values to the URL, specified in begin_date and end_date URL parameters respectively.
  • So the a complete URL could end up something like this:

https://api.nytimes.com/svc/search/v2/articlesearch.json?api-key=YOUR-API-KEY-HERE&page=0&q=cats
&fq=document_type:(“article”)&begin_date=20170301&end_date=20170312

  • Then we use Fetch API to request the data. Passing url variable to fetch() method, convert the response body to JSON using the json() function, then pass the resulting JSON to the displayResults() function so the data can be displayed in our UI.

Step 5: Displaying The Data

Add the following function below your fetchResults() function:

function displayResults(json) {
	while(section.firstChild) {
		section.removeChild(section.firstChild);
	};
	const articles = json.response.docs;
	if(articles.length === 10) {
		nav.style.display = 'block';
	} else {
		nav.style.display = 'none';
	}
	if(articles.length === 0) {
		const para = document.createElement('p');
		para.textContent = 'No results found.';
		section.appendChild(para);
	} else {
		for(var i = 0; i < articles.length; i++) {
			const article = document.createElement('article');
			const heading = document.createElement('h2');
			const link = document.createElement('a');
			const img = document.createElement('img');
			const para1 = document.createElement('p');
			const para2 = document.createElement('p');
			const hr = document.createElement('hr');
			let current = articles[i];
			console.log(current);
			link.href = current.web_url;
			link.textContent = current.headline.main;
			para1.textContent = current.snippet;
			para2.textContent = 'Keywords: ';
			for(let j = 0; j < current.keywords.length; j++) {
				const span = document.createElement('span');
				span.textContent += current.keywords[j].value + ' ';
				para2.appendChild(span);
			}
			if(current.multimedia.length > 0) {
				img.src = 'http://www.nytimes.com/' + current.multimedia[0].url;
				img.alt = current.headline.main;
			}
			article.appendChild(heading);
			heading.appendChild(link);
			article.appendChild(img);
			article.appendChild(para1);
			article.appendChild(para2);
			article.appendChild(hr);
			section.appendChild(article);
		}
	}
}
  • Before displaying the data that retrieved from NYTimes server, use while loop to remove all DOM elements under the <section>.
  • Next, we set the articles variable to equal json.response.docs — this is the array holding all the objects that represent the articles returned by the search. This is done purely to make the following code a bit simpler.
  • Use IF statement to check when to display pagination UI. (Note: NYTimes API return up to 10 articles at a time.
  • Use IF statement to check if no articles are returned, then display a “No results found.”
  • Else create all the elements that we want to use to display each news story, insert the right contents into each one, and then insert them into the DOM at the appropriate places.

Step 6: Adding Interactivity To Pagination

Add following code below displayResults() function:

nextBtn.addEventListener('click', nextPage);
prevBtn.addEventListener('click', prevPage);
function nextPage(e) {
	pageNumber++;
	fetchResults(e);
	prevPageVisibility();
}
function prevPage(e) {
	if(pageNumber > 0) {
		pageNumber--;
	} else {
		return;
	}
	fetchResults(e);
	prevPageVisibility();
}
function prevPageVisibility() {
	if(pageNumber > 0) {
		if(prevBtn.style.display == 'none') {
			prevBtn.style.display = 'inline';
		}
	} else {
		prevBtn.style.display = 'none';
	}
	
}
  • Add event listeners to previous & next button. The nextPage() and previousPage() functions to be invoked when the relevant buttons are clicked.
  • Inside nextPage() function, increment pageNumber variable, calls fetchResults() function & prevPageVisibility() function.
  • Inside prevPage() function, works in the same way in reverse, just do extra step to check is current pageNumber is more than zero, before decrementing pageNumber variable.
  • Inside prevPageVisibility() function, we use IF statement to check when to display or hide the previous page button.

Final Piece:

<!DOCTYPE html>
<html>
<head>
	<meta charset="utf-8">
	<meta name="viewport" content="width=device-width, initial-scale=1.0">
	<title>Build News List With RESTful API | onlyWebPro.com</title>
	<style>
		body {
			font-family: "Times New Roman", Times, serif;
		}
		#loading {
			position: absolute;
			top: 0;
			left: 0;
			background: rgba(0,0,0, .8);
			color: #fff;
			justify-content: center;
			align-items: center;
			height: 100vh;
			width: 100%;
		}
	</style>
</head>
<body>
	<h1>News Article Search</h1>
	<div class="wrapper">
		<div class="controls">
			<form>
				<p>
					<label for="search">Enter a SINGLE search term (required): </label>
					<input type="text" id="search" class="search" required>
				</p>
				<p>
					<label for="start-date">Enter a start date (format YYYYMMDD): </label>
					<input type="date" id="start-date" class="start-date" pattern="[0-9]{8}">
				</p>
				<p>
					<label for="end-date">Enter an end date (format YYYYMMDD): </label>
					<input type="date" id="end-date" class="end-date" pattern="[0-9]{8}">
				</p>
				<p>
					<button class="submit">Submit search</button>
				</p>
			</form>
		</div>
		<div id="loading">
			<div>Retrieving data... Please wait a moment.</div>
		</div>
		<div class="results">
			<nav>
				<button class="prev">Previous 10</button>
				<button class="next">Next 10</button>
			</nav>
			<section>
			</section>
		</div>
	</div>
	<script>
		// The Interaction Base
		const baseURL = 'https://api.nytimes.com/svc/search/v2/articlesearch.json';
		const key = '...';
		let url;
		const searchTerm = document.querySelector('.search');
		const startDate = document.querySelector('.start-date');
		const endDate = document.querySelector('.end-date');
		const searchForm = document.querySelector('form');
		const nextBtn = document.querySelector('.next');
		const prevBtn = document.querySelector('.prev');
		const section = document.querySelector('section');
		const nav = document.querySelector('nav');
		const loading = document.getElementById('loading');
		loading.style.display = 'none';
		nav.style.display = 'none';
		prevBtn.style.display = 'none';
		let pageNumber = 0;
		searchForm.addEventListener('submit', submitSearch);
		function submitSearch(e) {
			pageNumber = 0;
			fetchResults(e);
		}
		function fetchResults(e) {
			e.preventDefault();
			loading.style.display = 'flex';
			url = baseURL + '?api-key=' + key + '&page=' + pageNumber + '&q=' + searchTerm.value + '&fq=document_type:("article")';
			if(startDate.value != ''){
				url += '&begin_date=' + startDate.value;
			}
			if(endDate.value != '') {
				url += '&end_date=' + endDate.value;
			}
			fetch(url).then(response => {
				return response.json();
			}).then(json => {
				displayResults(json);
				loading.style.display = 'none';
			}).catch(e => {
				console.log(e);
			});
		}
		function displayResults(json) {
			while(section.firstChild) {
				section.removeChild(section.firstChild);
			};
			const articles = json.response.docs;
			if(articles.length === 10) {
				nav.style.display = 'block';
			} else {
				nav.style.display = 'none';
			}
			if(articles.length === 0) {
				const para = document.createElement('p');
				para.textContent = 'No results found.';
				section.appendChild(para);
			} else {
				for(var i = 0; i < articles.length; i++) {
					const article = document.createElement('article');
					const heading = document.createElement('h2');
					const link = document.createElement('a');
					const img = document.createElement('img');
					const para1 = document.createElement('p');
					const para2 = document.createElement('p');
					const hr = document.createElement('hr');
					let current = articles[i];
					console.log(current);
					link.href = current.web_url;
					link.textContent = current.headline.main;
					para1.textContent = current.snippet;
					para2.textContent = 'Keywords: ';
					for(let j = 0; j < current.keywords.length; j++) {
						const span = document.createElement('span');
						span.textContent += current.keywords[j].value + ' ';
						para2.appendChild(span);
					}
					if(current.multimedia.length > 0) {
						img.src = 'http://www.nytimes.com/' + current.multimedia[0].url;
						img.alt = current.headline.main;
					}
					article.appendChild(heading);
					heading.appendChild(link);
					article.appendChild(img);
					article.appendChild(para1);
					article.appendChild(para2);
					article.appendChild(hr);
					section.appendChild(article);
				}
			}
		}
		nextBtn.addEventListener('click', nextPage);
		prevBtn.addEventListener('click', prevPage);
		function nextPage(e) {
			pageNumber++;
			fetchResults(e);
			prevPageVisibility();
		}
		function prevPage(e) {
			if(pageNumber > 0) {
				pageNumber--;
			} else {
				return;
			}
			fetchResults(e);
			prevPageVisibility();
		}
		function prevPageVisibility() {
			if(pageNumber > 0) {
				if(prevBtn.style.display == 'none') {
					prevBtn.style.display = 'inline';
				}
			} else {
				prevBtn.style.display = 'none';
			}
			
		}
	</script>
</body>
</html>

That’s it! 👏

Save your code and check your news list in the browser!

Follow onlyWebPro.com on Facebook now for latest web development tutorials & tips updates!

More Modern Web Development Tutorials


Posted

in

by

Post’s tag:

Advertisement