My fourth challenge for the Vanilla JS Academy was to build a news feed using the Top Stories API from The New York Times. Let’s look at how I tackled it.
Today’s top stories
Before we do anything else, let’s look at the HTML I started with.
Instead of leaving the
div#app element empty, I added a paragraph with a link to today’s top stories on the New York Times website.
This is important because if the script fails to work for whatever reason (e.g. a bad network connection), the user will still get something useful. We call this principle progressive enhancement.
I added some very basic styles to make my project look a bit nicer.
The universal (
*) rule makes the box model a bit easier to work with. The
body rule centers the page and increases its line height for better readability.
As always, I started with an immediately-invoked function expression (IIFE). This keeps my code outside the global scope and allows me to refer to the
d for short. I also opted into strict mode.
I then created my variables.
I saved the API endpoint to a variable so I can easily change it later without having to find it in multiple places. I also got a reference to the
Next, I created a series of functions to pass into the
catch() methods. But first, I added a helper function to sanitize the third-party data.
This is important for preventing XSS attacks.
The first function I created gets the JSON data from a Fetch request.
If the request is successful (i.e. its
ok property is
true), I call the
json() method on the
Otherwise, I need to create a rejected promise. I do this by calling the static
Promise.reject() method, passing in the
Once the JSON comes back, I need to get the actual data. The New York Times API stores it in a
results property, so I created a function to return that.
I could have renamed my
getJSON() function to
getData(), and instead returned
response.json().results directly. This would have eliminated one call to the
then() method. I like the separation of concerns between getting the JSON and the actual results, though.
After the call to the
getStories() function, I’m left with an array of stories. Each item in the array is an object containing information about that story. I created a function,
buildListItem(), to create a list item for each one.
Notice that I’m passing the third-party data to the
sanitizeHTML() function to prevent XSS attacks.
This function only works on a single array item, though, so I’ll pass it into the
Array.prototype.map() method next.
This function replaces the
#app element’s contents with the data from the API.
I first set the
innerHTML property to an opening
I then pass my
buildListItem() function into the
map() method before calling the
Array.prototype.join() method. This converts each object in the array to an HTML string.
I add this to the
innerHTML property, along with the closing
</ul> tag. The result is a complete unordered list element.
Finally, I created a function to handle any errors in my chain of promises. I’ll pass this into the
This just replaces the
#app element’s contents with a generic error message.
Initialize the app
All that’s left to do now is initialize the app! I called the global
fetch() method, adding a chain of promises and passing in each of my functions.
It’s so good to use named functions like this because you can literally just read what’s happening, line by line, in plain English.
My work thus far shows the top stories for the day, but it would be nice if it could show the top stories from a few different categories. I’d like to know what’s happening in movies, science, and technology.
Update the variables
First up, I created a new
categories variable at the top of my script. It’s just an array containing each of my desired categories as strings.
I also split my endpoint and API key into two separate variables. You’ll soon see why this is necessary.
Change the functions
The biggest change is the logic of my functions. Instead of making a single request, I need to make multiple, passing in the desired category each time.
I renamed my
buildListItem() function to
buildArticle(). It’s mostly the same, but I tweaked the HTML string a bit.
I changed the title from an
<h2> to an
<h3>, because the
<h2> elements will now need to be the category names. I also added some more semantic HTML elements, like
<header> for the title, and
<address> for the byline.
Next up, I created a function to render all articles of a specific category.
I add a
<section> for each category—more semantic HTML, which is good. I also add a second-level heading for the category name. I then use the
Array.prototype.slice() method to get the first three articles, and I turn them into an HTML string just like I did originally.
I moved my
fetch() invocation into a new function called
fetchArticles(). I did this so I can call it once for each category.
This is why I needed to separate the endpoint and API key into their own variables. The endpoint is now dynamic, so I need to append the
category each time this function is invoked.
Initialize the app (again)
Now all I need to do is initialize the app.
I first empty the
#app element, which is necessary because my new
render() function only appends strings to the
innerHTML property. It never resets it entirely. This is because it gets called more than once; resetting the property each time would mean only the final category would be shown.
Finally, I use the
Array.prototype.forEach() method to call my
fetchArticles() function once for each category.
And there you have it: a really clean, minimal app that gets the top stories for the day! I love it because it’s so much less intrusive than other news aggregators. No bullshit ads or tracking, just the content.
You can view the demo and check out the full source code on GitHub.
If you want to learn how to build cool apps like this, I highly recommend you join the next session of The Vanilla JS Academy. It starts on May 11th. Chris is such a good guy and he will look after you! ❤️