DEV Community

Cover image for Display Recent Posts with the DEV API and Netlify Functions
Stephanie Eckles
Stephanie Eckles

Posted on

Display Recent Posts with the DEV API and Netlify Functions

The method described in this post works for simple HTML sites with no javascript framework, CMS, or static site generator needed, but can be extended to those environments, too.

You can use these steps for any available API, not just DEV, to easily pull live data into your static site hosted on Netlify. In this pure-static HTML version, data will refresh with every page load and not require a build to be triggered when you publish a new post.

The only requirement for this tutorial is a DEV profile with at least one published post, and a Netlify account for hosting.

For reference on what you can accomplish, here's a live demo of a starter I've created with the full code from this tutorial.

preview of the final project

Use the template option on the starter repo if you'd just like to grab 'n go to get a site up that displays your DEV posts, just check out the README info.

The following describes how that was set up if you'd like to integrate into an existing project that is hosted on Netlify, and to help with extending the base project.

Step 1: Get a DEV API Key

On DEV, be sure you're logged in and go to Account Settings and generate an API key - keep this tab open for the next step

Step 2: Create Netlify Environment Variable

Login to Netlify and select your site, then within Settings > Build & Deploy > Environment create a new variable assigned to the key DEVTO with the value being your DEV API key.

Step 3: Create the Netlify Function

Netlify functions are the magic that allows safely querying an API (and many other things) on what is otherwise a truly static site.

First, create a directory where you want to store your Netlify functions. You will define this either in Netlify Settings > Functions > Deploy Settings, or in the netlify.toml file so that Netlify that they exist so they are processed.

Example netlify.toml file with functions directory defined:

[build]
  # Directory with the serverless Lambda functions to deploy to AWS.
  functions = "functions"
Enter fullscreen mode Exit fullscreen mode

For simplicity, create functions/devto.js at the root of your project.

We will write our function with javascript and use axios to fetch posts from the DEV API.

Axios does not need to be in your local package.json as Netlify will include it upon processing the function. But we do start by requiring it:

const axios = require("axios");
Enter fullscreen mode Exit fullscreen mode

Then, we create a variable with the base API URL for a user's published posts, defaulting to 9 returned posts:

const apiRoot = "https://dev.to/api/articles/me/published?per_page=9";
Enter fullscreen mode Exit fullscreen mode

Next, we create the primary function handler. This is pretty flexible, the key is that we return what we want to be displayed on our endpoint via the callback function that is passed into the handler.

Axios is used to get results from the DEV API, and then we map over them to customize what we want to appear in our customized API. We grab the title, url, description, and tags. We do a join on the tag_list to create a simple string for display purposes.

exports.handler = async (event, context, callback) => {
  try {
    const { data } = await axios.get(apiRoot, { headers: { "api-key": process.env.DEVTO } });

    let response = [];

    // Grab the items and re-format to the fields we want
    if (data.length) {
      response = data.map((item) => ({
        title: item.title,
        url: item.url,
        description: item.description,
        tags: item.tag_list.join(", "),
      }));
    }
    callback(null, {
      statusCode: 200,
      body: JSON.stringify(response),
    });
  } catch (err) {
    callback(err);
  }
};
Enter fullscreen mode Exit fullscreen mode

Credit to Raymond and Andy whose implementations helped steer me in the right direction

Step 4: Publish the Function

If you do not have branch deploys turned on, you will want to so that you can verify the function and results on a preview deploy. It's a super awesome feature of Netlify, and you can update to use it from Settings > Build & Deploy > Deploy contexts and select an option besides "None". You can certainly revert after making this update.

You can now commit your changes, and then go to the "Deploy" section of your Netlify dashboard. Once the build publishes, you can click on the bolded deploy title to launch the preview URL.

All functions once published are available off the site in the following format:

[preview-url]/.netlify/[functionsdir]/[functionname]

So for this deploy it will be the following if you used the suggested names:

[preview-url]/.netlify/functions/devto

An example payload should look like:

[
  {
    "title": "CSS-Only Accessible Dropdown Navigation Menu",
    "url": "https://dev.to/5t3ph/css-only-accessible-dropdown-navigation-menu-1f95",
    "description": "This is the seventh post in a series examining modern CSS solutions to problems I've been solving ov...",
    "tags": "css, html, webdev, a11y"
  },
  // ...and so on
]
Enter fullscreen mode Exit fullscreen mode

Local Testing Data

Due to CORS, you will not be able to fetch your remote endpoint from your local build.

You now have two options: copy the results into a local file to use for testing, or setup the Netlify CLI to build functions locally.

I'm going to proceed with the local data option as it's more beginner-friendly.

So for that, copy the contents of your endpoint into a local file called postdata.json which you will likely want to exclude from commits with .gitignore. We will reference this file to help build the next step.

Step 6: Fetch Data From the Netlify Endpoint

Back in your website project, create a new javascript file: posts.js.

First, we will setup a variable to hold the value of the Netlify endpoint URL, but if we have a window.location.port value we an assume that's a local build and change to point to our local test data file instead:

let postsApi = "/.netlify/functions/devto";

// Use local test data if not live site
if(window.location.port) {
  postsApi = "/js/postdata.json";
}
Enter fullscreen mode Exit fullscreen mode

Next, we will use fetch to get the results, convert the stringified data to JSON, and then pass it to a custom function that we'll write next:

fetch(postsApi, {
  method: "GET",
})
  .then((response) => response.json())
  .then((data) => {
    // Pass to post template and output function
    createPostList(data);
  })
  .catch((error) => {
    console.error("Error:", error);
  });
Enter fullscreen mode Exit fullscreen mode

Step 7: Define the Posts Placeholder

We need to define a location for the output within an HTML file.

Wherever you want the posts to display, create the following placeholder:

<div class="posts"></div>

The important part is the class which we will use to locate the placeholder. You can update it to a class of your choosing, or an id if you prefer.

Then, go ahead and add a script tag sourcing posts.js at the end of the HTML file prior to the closing </body> tag:

<script src="js/posts.js"></script>

Step 8: Create the Display Function

Back in posts.js, the first thing we'll do at the top of the file is create a variable to reference our placeholder:

const postList = document.querySelector(".posts");
Enter fullscreen mode Exit fullscreen mode

Then it's time to write the createPostList function.

Recall that it is being passed the body of the data we already customized, so we map over each post, and use destructuring to easily gain access to the value of each piece of post data.

Following that, we define the template using a template literal to place the data an li and other appropriate HTML elements.

const createPostList = (posts) => {
  const items = posts.map((post) => {

    const {
      title,
      url,
      description,
      tags
    } = post;

    return `<li class="card">
              <div class="card__content">
                <a href="${url}" class="card__title">${title}</a>
                <p>${description}</p>
                <em>${tags}</em>
              </div>
            </div>
          </li>`;
  });

  const list = `<ul class="card-wrapper" role="list">${items.join("")}</ul>`;

  postList.innerHTML = list;
  postList.classList.add("loaded");
}
Enter fullscreen mode Exit fullscreen mode

The function ends by joining the li into ul, and finally placing the completed list into our placeholder as innerHTML, and adding a loaded class for any CSS that you wish to occur once data is present.

Optional: Minimal Card Layout CSS

If you need it, here's the minimal CSS to produce responsive "cards" that use CSS grid to place in auto columns of 1-3 across depending on viewport size (does not include full visual effects from preview example for brevity):

.posts {
  // Reduce jarring effect when posts loaded and height adjusts if you have other elements on your page
  min-height: 60vh;
  transition: 320ms opacity ease-in;
  opacity: 0;
}

.posts.loaded {
  // Fade in to make appearance more pleasant
  opacity: 1;
}

.card-wrapper {
  display: grid;
  grid-gap: 2em;
  grid-template-columns: repeat(auto-fit, minmax(25ch, 1fr));
  padding-left: 0;
  list-styles: none;
}

.card {
  display: flex;
  flex-direction: column;
  border-radius: 8px;
  background-color: #fff;
  box-shadow: 0 3px 5px rgba(0, 0, 0, 0.18);
}

.card__title {
  margin-bottom: 1rem;
  color: blue;
  text-decoration: none;
}

.card__content {
  position: relative;
  display: flex;
  flex-direction: column;
  flex-grow: 1;
  padding: 24px;
}

.card__content p {
  line-height: 1.4;
  margin: 0 0 1rem;
}

.card__content *:last-child {
  margin-top: auto;
}
Enter fullscreen mode Exit fullscreen mode

Step 8: Commit and Preview Deploy

Commit your changes and review once more on the Netlify branch preview to be sure that the deployed version that queries the live Netlify function displays just like the local version without error.

If it does, then all that's left is to merge into your master branch for live site deploy when you're ready! πŸš€

API Options and Customization

Review the full DEV API docs >

Change number of returned posts

Open functions/devto.js and in the $apiRoot variable change the per_page value. DEV API allows values up to 1000. You could extend this to handle pagination and retrieve more than that in total.

Change returned values from DEV API

Open functions/devto.js and in the generated map, add or remove values as desired. Review the DEV API docs for a sample of a returned API object.

Change post template

You can change anything about the markup used in the createPostList function.

Review previous section if you want to add additional API values to display.

If you need IE11 or under support you may want to run the content of js/posts.js through the online Babel compiler to produce an alternate to the template literal used to create the post template.

Top comments (5)

Collapse
 
bethwickerson profile image
bethwickerson

Great post, Stephanie! I am doing something very similar that requires me to "refresh" the data I am pulling from my API. Basically I am submitting data with a Netlify form and I want the newly added data to show up on submit, without reloading the page. For instance, if you submitted a post to Axios, how would you refresh your feed? If you have any ideas, let me know!! Thanks β™₯️

Collapse
 
5t3ph profile image
Stephanie Eckles

I haven't yet personally worked through that scenario, but it sounds similar in function to a comment system. You may be interested in this post on how to make Jamstack comments which might get you on the right track to your solution: css-tricks.com/jamstack-comments/

Collapse
 
bethwickerson profile image
bethwickerson

Oh yes, this is perfect, thank you!

Collapse
 
focusotter profile image
Michael Liendo

This is awesome β™₯️ So nice to see and end to end project that covers so many parts of development, including styling!

Collapse
 
5t3ph profile image
Stephanie Eckles

Thanks Michael! I try to cover the linear steps plus all the questions I had myself when creating the example, and then check if beginners can follow along relatively well which helps fill in the cracks 😊 I really appreciate the feedback!