Display Recent Posts with the DEV API and Netlify Functions
Tags: meta, javascript, webdev, netlify
Published: 2020-04-26 03:06:41

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](https://www.netlify.com/) account for hosting.

For reference on what you can accomplish, here's [a live demo](https://devto-netlify-starter.netlify.app/) of a starter I've created with the full code from this tutorial.

![preview of the final project](https://dev-to-uploads.s3.amazonaws.com/i/p7ksg644zgeoegfplp3k.png)

Use the template option on the [starter repo](https://github.com/5t3ph/devto-netlify-starter) 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](https://dev.to/settings/account) and generate an API key - _keep this tab open for the next step_

## Step 2: Create Netlify Environment Variable

[Login to Netlify](https://www.netlify.com/) 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](https://www.netlify.com/products/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`](https://docs.netlify.com/configure-builds/file-based-configuration/#functions) file with functions directory defined:

```bash
[build]
  # Directory with the serverless Lambda functions to deploy to AWS.
  functions = "functions"
```

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:

```js
const axios = require("axios");
```

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

```js
const apiRoot = "https://dev.to/api/articles/me/published?per_page=9";
```

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.

```js
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);
  }
};
```

_Credit to [Raymond](https://www.raymondcamden.com/2019/01/08/adding-serverless-functions-to-your-netlify-static-site) and [Andy](https://github.com/hankchizljaw/microblog/blob/master/src/_data/likes.js) 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:

```json
[
  {
    "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
]
```

### 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](https://github.com/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:

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

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

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:

```js
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);
  });
```

## 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:

```js
const postList = document.querySelector(".posts");
```

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](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment) 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.

```js
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");
}
```

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):

```sass
.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;
}
```

## 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 >](https://docs.dev.to/api/#operation/getUserPublishedArticles)

### 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](https://docs.dev.to/api/#operation/getUserPublishedArticles) 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](https://babeljs.io/repl) to produce an alternate to the template literal used to create the post template.