Our website is made possible by displaying online advertisements to our visitors. Please consider supporting us by disabling your ad blocker.

Download and Cache YouTube Data in an Eleventy Website with Simple JavaScript

TwitterFacebookRedditLinkedInHacker News

So you want to add your YouTube videos to your static generated website, but you don’t want to manually keep track of all your videos and playlists? I get it because I’ve been there!

Take my website, Poké Trainer Nic, for example. It’s a Pokémon website built with Eleventy that shows a list of my YouTube videos among other things. The videos on the website refresh daily, but it’s not something I do manually.

So how do I do this?

In this tutorial you’ll see how to get information about your YouTube videos and playlists using APIs for displaying within an Eleventy website. To make things better, we’re going to see how to cache this data to prevent making too many requests to an API that has a finite allocation.

To get an idea of what you can accomplish, take a look at the following image:

YouTube with Eleventy Example

Pretty slick right? The above image is just one of many possible things that you can do.

Activating and Configuring the YouTube API for an Application

When it comes to the YouTube API, there are two core steps:

  • Enabling the YouTube Data API v3 in the Google Cloud Platform
  • Finding the Playlist ID to be used

The first thing we’ll want to do is associate our Google Account to some credentials for the YouTube Data API. This can be done in the Google Cloud Platform through this link.

As part of the API setup, you’ll need to enable the API for your project and then create a credential for the project. The credential should be an API key since we won’t need authentication for anything we plan to do.

If things don’t go as planned with my instructions, go to the Google Cloud Platform, search for YouTube Data API v3, and then do some enabling.

By now you should have an API key to use. However, you can’t just say use this API key and get me every video for a user. You can only get video information on a playlist basis. The good news is there is a playlist for “Uploaded Videos” which is kind of like an “All” for a particular channel.

Here is an example of my Uploads from Poké Trainer Nic playlist on YouTube. What’s important is the list value in the URL bar. For example UUAQXlvfsuaJSKWHSaiYZlRg is the id of my particular playlist.

Your channel might have multiple playlists and each playlist would have its own id value. My use-case only pulls video data from the uploads playlist because I want all videos that I uploaded to my channel.

Listing All YouTube Videos with Meta Information for a Particular Playlist

By now you should have an API key that works with the YouTube API and a particular playlist in mind to consume from. We’re going to use these details within our Eleventy website.

Rather than going through the Eleventy fundamentals, we’re going to dive right into what’s necessary for this particular tutorial, starting with the dependencies.

Within your Eleventy project, execute the following command:

npm install @11ty/eleventy-cache-assets axios --save-dev

The above command will install axios which we’ll use for making HTTP requests and @11ty/eleventy-cache-assets which we’ll use for caching the responses.

Create a _data/youtubeapi.js file within your project with the following code:

const { AssetCache } = require("@11ty/eleventy-cache-assets");
const axios = require("axios");

module.exports = async function() {

    let API_KEY = "YOUR_API_KEY_HERE";
    let PLAYLIST_ID = "YOUR_PLAYLIST_ID_HERE";

    let baseUrl = `https://www.googleapis.com/youtube/v3/playlistItems?key=${API_KEY}&part=contentDetails,snippet&playlistId=${PLAYLIST_ID}&maxResults=50`;

    let asset = new AssetCache("youtube");

    if(asset.isCacheValid("1d")) {
        return asset.getCachedValue();
    }

    const getYouTubeVideos = async (nextPageToken) => {
        const url = nextPageToken != "" ? baseUrl + "&pageToken=" + nextPageToken : baseUrl;
        const response = await axios({
            "method": "GET",
            "url": url
        });
        const data = response.data;
        let cleanData = data.items.map(item => {
            return {
                "title": item.snippet.title,
                "url": "https://www.youtube.com/watch?v=" + item["contentDetails"]["videoId"],
                "thumbnail": item.snippet.thumbnails.medium.url
            }
        });
        if(data.nextPageToken && data.nextPageToken != "") {
            return cleanData.concat(await getYouTubeVideos(data.nextPageToken));
        } else {
            return cleanData;
        }
    }

    const videos = await getYouTubeVideos("");

    await asset.save(videos, "json");

    return videos;

};

There’s a lot happening in the above code so we’re going to break it down.

Skipping the imports of the dependencies that we downloaded, you’ll probably first notice the following:

let API_KEY = "YOUR_API_KEY_HERE";
let PLAYLIST_ID = "YOUR_PLAYLIST_ID_HERE";

let baseUrl = `https://www.googleapis.com/youtube/v3/playlistItems?key=${API_KEY}&part=contentDetails,snippet&playlistId=${PLAYLIST_ID}&maxResults=50`;

If you spend some time in the YouTube API documentation, if you’re eyeballs haven’t popped out of your head, you probably stumbled upon the API URL that we’re using as the baseUrl variable. This particular API endpoint will return details for each item returned from the playlist with a maximum of fifty (50) results. The particular playlist and API key were obtained in the previous step when we configured the YouTube API.

Next we want to setup our cache and use it if present and not expired:

let asset = new AssetCache("youtube");

if(asset.isCacheValid("1d")) {
    return asset.getCachedValue();
}

We’re calling our cache, which could be one of many, “YouTube” and we’re giving it an expiration time of one (1) day. If the cache is not valid, we proceed to the next step without returning.

Now you might think a YouTube API request is a one and done type of thing, but if you have a lot of videos it becomes a little more complicated. The YouTube API we are using maxes out at fifty (50) results, so if we have more than fifty (50) videos, we need to use some pagination.

The first part of our API request looks like this:

const url = nextPageToken != "" ? baseUrl + "&pageToken=" + nextPageToken : baseUrl;
const response = await axios({
    "method": "GET",
    "url": url
});
const data = response.data;

If we have a paging token, then use it, otherwise just use the baseUrl variable as is. The axios package will handle the HTTP request for us.

The data we get back from the API is not pleasant. Rather than caching everything in the exact format we are responded with, it’s best to parse it and only store what we need:

let cleanData = data.items.map(item => {
    return {
        "title": item.snippet.title,
        "url": "https://www.youtube.com/watch?v=" + item["contentDetails"]["videoId"],
        "thumbnail": item.snippet.thumbnails.medium.url
    }
});

In my particular use-case, I only need the title of the video, the URL of the video, and the medium sized thumbnail. Your needs may vary, so it is worth looking at the full response before you come to a conclusion. We’re using the map function in JavaScript to format the data we want in a way that we want it.

The response may or may not include a nextPageToken field. If no such field was returned then there are no more results. If you do end up with a nextPageToken field, it means you can get more results from the API. If you have more than fifty (50) videos in the selected playlist, the nextPageToken will exist.

To get every video from every page, we can recursively make our requests until the token no longer exists:

if(data.nextPageToken && data.nextPageToken != "") {
    return cleanData.concat(await getYouTubeVideos(data.nextPageToken));
} else {
    return cleanData;
}

If the token exists, concatenate the recursive results, otherwise just return whatever our parsed data is.

To bring the youtubeapi.js file to a close, we have the following lines:

const videos = await getYouTubeVideos("");

await asset.save(videos, "json");

return videos;

The first kicks off the initial HTTP request and any further recursive request. When we have clean data for our videos returned, we can save it to the cache. After we save it to the cache we can return the data to Eleventy to be rendered to the website.

So what does using this data look like? Let’s take a simple index.njk file for example:

<html>
    <body>
        <div>
            {% for video in youtubeapi %}
                <p>
                    <img src="{{ video.thumbnail }}" alt="{{ video.title }}" loading="lazy" />
                    {{ video.title }} - {{ video.url }}
                </p>
            {% endfor %}
        </div>
    </body>
</html>

Notice that we’re using youtubeapi which is the name of our _data/youtubeapi.js file. We’re looping through each of the videos in the data, whether that is cached or not, and we are presenting it on the screen with HTML tags.

Not bad right?

You might want to check out some of the YouTube API documentation to see what else you can do with the API. There might be some feature you want to include in your Eleventy website.

Conclusion

You just saw how to use the YouTube API to list YouTube videos and their meta information on your Eleventy static generated website. This can be incredibly convenient if you have a continuous deployment pipeline and don’t want to burden yourself with manually entering YouTube video information every time a video publishes.

Like I mentioned, I’m currently using the code we saw on my other website, Poké Trainer Nic. The website automatically re-deploys to Netlify every day and during that process all of the YouTube videos are picked up and automatically added because of the API.

A video version of this tutorial can be found below.

Nic Raboy

Nic Raboy

Nic Raboy is an advocate of modern web and mobile development technologies. He has experience in C#, JavaScript, Golang and a variety of frameworks such as Angular, NativeScript, and Unity. Nic writes about his development experiences related to making web and mobile development easier to understand.