Exploring the DEV.to API to Build a Blog
Tags: api, nuxt, i18n, programming
Published: 2024-03-12 19:15:00

Hello, folks! Today I'm going to share with you how I set up a blog page using the Dev.to API. This is a great way to expand the reach of your content. Additionally, I write the same post in both Portuguese and English to reach more people, and with that, there's a challenge of implementing localization on the blog so that the same post works in both languages.

Before discussing the API usage, I want to mention three references that helped me set up my blog page. Keeping in mind the concept that nothing is created, only combined, I drew inspiration from two blog themes for Nuxt 3 and a post on creating a skeleton loader with Tailwind CSS.

Alpine has a pleasant post layout, always highlighting the most recent one, while Nuxt Starter Blog provides a simple and effective way to handle pagination for numerous posts.

{% github https://github.com/nuxt-themes/alpine %}{% github https://github.com/narasimhajupally/tailwind-nuxtjs-starter-blog %}

I liked the idea of combining the two. Thus, I could create a unique experience that combines the elegant look of Alpine with the navigational ease of Nuxt Starter Blog.

{% link https://dev.to/kouts/skeleton-loader-using-vue-tailwind-422a %} Thanks [@kouts](https://dev.to/kouts)

## What you'll need:

- The Dev.to API, which facilitates fetching posts and tags for your blog.
- Tools like Nuxt.js and Vue-i18n to handle site localization and switch between posts in different languages.
- Basic knowledge of HTML, CSS, and JavaScript to manipulate the data returned by the API and create the structure of your blog page.

## Step by step:

###  **Fetch posts and tags from the Dev.to API:**

I used the API to retrieve posts written by you and the tags associated with each post. I used my username as a query parameter for this call, along with the state "all", ensuring that the response returns a maximum of 1000 posts with associated tags, instead of the default 30. (I hope to write at least 500 posts 😅)

```js 
     const { data: posts, pending } = await useLazyFetch(
    	"https://dev.to/api/articles?username=lfxa&state=all");
```

### **Work with site localization and manipulate the data returned by the API:**

Now comes the interesting part: to make this functional, the post needs to be available in both English and Portuguese on dev.to. To determine if the post is in one of the languages, I use the "canonical_url" field with the location in the URL (e.g., https://lfxa.vercel.app/en-US/blog?to=um-portfolio-de-um-engenheiro-de-software-162o,a-portfolio-of-a-software-engineer-3i47). This field indicates the URL where the post was originally published and is redirected to my site. I apply a filter to display only posts in the desired location.

```js
   const route = useRoute();
    const { locale } = useI18n();
    
    const filteredBlogPosts = computed(() => {
      return props.posts
        ?.filter((post) => {
    	    // location in url
          const postLang = post.canonical_url.split("/")[3];      
          const isSameLanguage = postLang === locale.value;
    
          //filter tag and location
          const hasTagQuery = route.query.tag !== undefined;
          return isSameLanguage &&
               (!hasTagQuery || post.tag_list.includes(route.query.tag));
        })
        // pagination
        .slice(
          props.postPerPage * (currentPage.value - 1),
          currentPage.value * props.postPerPage,
        );
    });
```


As the post was originally published on dev.to, I redirect if someone clicks the link that leads directly to my site. (needs improvement if more site locations are added 😉).

```js
    // redirect to post in certain location
    if (route.query.to !== undefined) {
      const toPost = route.query.to.split(",");
      const index = locale.value === "pt-BR" ? 0 : 1;
      await navigateTo(`/blog/${toPost[index]}`);
    }
 ```


I filtered the tags to show them in their respective location:

```js
    const tags = computed(() => {
      const tagCounts = {};
      props.posts?.forEach((post) => {
        const lang = post.canonical_url.split("/")[3];
        post.tag_list.forEach((tag) => {
          const key = tag + ":" + lang;
          if (tagCounts[key]) {
            tagCounts[key]++;
          } else {
            tagCounts[key] = 1;
          }
        });
      });
    
      const output = [];
      for (const tagLang in tagCounts) {
        const key = tagLang.split(":");
        output.push({ name: key[0], quantity: tagCounts[tagLang], lang: key[1] });
      }
      output.sort((a, b) => a.name.localeCompare(b.name));
      return output;
    });
```
    

and I also added a filter for tags that are selected and become query parameters:

```js
    // v-if
    tag.lang === $i18n.locale;
    
    // nuxtLink to
    route.fullPath.includes(tag.name) ? 'blog' : 'blog?tag=' + tag.name
```

### **Create the structure of the blog post page:**

To get the post content, it's necessary to query the API specifically for that particular post. I used the post's slug along with my username as query parameters in the API. The post content is provided in the "body_html" field, maintaining the CSS classes from the dev.to site. To ensure proper post viewing, I copied part of the CSS from the site that makes sense. I recommend avoiding the use of the v-html directive as it is sensitive to [XSS attacks](https://dev-academy.com/vue-xss/#if-it-is-not-vue-is-it-you) (Cross-Site Scripting). (I hope dev.to doesn't transmit malicious scripts that can be executed on users' sites).

```js
       const { data: post, pending } = await useFetch(
      "https://dev.to/api/articles/lfxa/" + slug,
    );
```


Additionally, if there's a language change on the page, I make a new call for the same post in the other language:

```js
    onBeforeRouteLeave(async (to, from, next) => {
      if (
        post.value &&
        to.fullPath.split("/").pop() === from.fullPath.split("/").pop()
      ) {
        const last = post.value.canonical_url.split("/").pop();
        const toPost = last.replace("blog?to=", "").split(",");
        const index = locale.value === "pt-BR" ? 0 : 1;
        await next(`/${locale.value}/blog/${toPost[index]}`);
      }
      next();
    });
 ```

If you're interested, you can see the source code on GitHub [here](https://github.com/LFXA/lfxa-nuxt-personal).


---
## Conclusion:

Sharing what we know is essential to make learning more accessible and promote the growth of other developers. Additionally, by sharing knowledge, we strengthen our own skills, see what can be improved, and contribute to the advancement of the software development community as a whole.

> In the same way, let your light shine before others, that they may see your good deeds and glorify your Father in heaven. Matthew 5:16