Publish Posts to Dev.to and Your Site Automatically in One Go

01 May, 20205 min read
unsplash-logoImage By NeONBRAND
If you're reading this on dev.to that means I successfully got this to work 🎉🎉

So for a while now I've wanted to start publishing posts to dev.to but haven't because I wanted to publish my posts to my personal site as well and was just too darn lazy to duplicate the content onto dev.to manually (hey I'm a dev don't hate 😅). Luckily dev.to has an API available which I used to automatically publish my posts to the platform (keep in mind this API is currently in beta v0.7.0.).

Ok so how does this work for me?

My headless CMS triggers a new Netlify build of my Gatsby site when a new post is created via webhook. While Gatsby is building and generating my pages it simultaneously gets a list of the currently published posts to dev.to and publishes any new ones via the dev.to API.

Why do I do this as part of the Gatsby build? Well, I didn't want to be specific to Netlify or my CMS as I may change those in the future plus writing this in plain JS was just nice.

Generate an API Key 🔑

You'll need to generate an API key to make calls to the dev.to API. Firstly you'll need to navigate to your dev.to profile settings and go to the Account tab.

Then you should be prompted to create an API key.

You'll need to store the dev.to API key as either an environment variable or you could hard-code it but I strongly suggest you don't. In the code below I'm assuming it's stored as an environment variable and so that is what the processs.env.DEV_TO_KEY is referencing.

On to the Code 👨‍💻 👉

Ok, so I created a util function that handles the posting of posts to dev.to. This can be called wherever, but my implementation is being called from the gatsby-node.js file. The last quick note is that I'm assuming that you are passing all of the posts as a parameter to this util but this can be easily refactored to handle one post at a time. Phew ok ok now to the code.

Firstly you'll need to fetch your existing posts on dev.to so that you don't mistakenly attempt to re-publish it. Since this script is running at build time we don't have fetch available in node, so we can use the node-fetch package here.

const existingPostCache = {};
  const postsToPublish = [];
  await fetch(`https://dev.to/api/articles/me/all`, {
    headers: {
      'api-key': process.env.DEV_TO_KEY,
    },
  })
    .then(res => res.json())
    .then(data => data.forEach((post) => existingPostCache[post.title] = post));

What's going on is this is going to the https://dev.to/api/articles/me/all endpoint, retrieving your existing blog posts and then storing them in an object which we will use in a second. Note the API key is passed as the api-key header in the request.

Next, we will iterate over the posts object array that is passed as a parameter to this util function. As you'll see in the code below I'm assuming the post object has the Html, and I'm converting it to markdown with the showdown package as the dev.to API expects the content to be passed as markdown in the request.

Since we are running this as build time in node we also need to use the jsdom package as showdown needs a document object to do this conversion.

posts.forEach((post) => {
	if (existingPostCache[post.title]) {
      return;
    }
    
    const markdown = converter.makeMarkdown(post.html, new jsdom.JSDOM().window.document);
    const devRequestBody = {
      title: post.title,
      canonical_url: `https://yourwebsite.com/blog/${post.slug}`,
      body_markdown: markdown,
      main_image: post.feature_image,
      description: post.excerpt,
      published: false,
    };

    postsToPublish.push(devRequestBody);
  });

Then we construct the request object which we will use to create the post on dev.to. This is what the various properties are for (You won't find all of these on the dev.to API docs, I had to look at their tests to figure some of this out, I'm going to hopefully make a PR soon to add this):

  • title -> post title of course
  • canonical_url -> nice for SEO, points to the post on your own site
  • body_markdown -> the markdown for the post
  • main_image -> the header/main image for the post (that's the big boy at the top)
  • description -> just a short description or excerpt
  • published -> whether or not you want it published immediately, I have it set to false just so I can proofread one last time before actually publishing

One cool thing I found is that dev.to will automagically upload the images in your post to Cloudinary so you don't have to worry about your CMS being hit with a ton of requests for those images. The dev.to team is freaking awesome for that!!

Lastly, you'll make the request to dev.to to publish your posts, again we are using the node-fetch package here.

  for (const post of postsToPublish) {
    await fetch(`https://dev.to/api/articles`, {
      headers: {
        'api-key': process.env.DEV_TO_KEY,
        'Content-Type': `application/json`,
      },
      method: `post`,
      body: JSON.stringify({
        article: post,
      }),
    }).catch((err) => console.log(err));
  }

We are making a POST call to the https://dev.to/api/articles and we are passing the request body that we defined in the previous step here.

Here is the final util code:

const showdown = require(`showdown`);
const jsdom = require(`jsdom`);
const fetch = require(`node-fetch`);
const converter = new showdown.Converter();

const DevToPublishing = async (posts) => {

  if (process.env.NODE_ENV === `development` || !process.env.DEV_TO_KEY) {
    console.log(`No dev.to key found crap...`);
    return;
  }

  const existingPostCache = {};
  const postsToPublish = [];
  await fetch(`https://dev.to/api/articles/me/all`, {
    headers: {
      'api-key': process.env.DEV_TO_KEY,
    },
  })
    .then(res => res.json())
    .then(data => data.forEach((post) => existingPostCache[post.title] = post));

  posts.forEach((post) => {
    if (existingPostCache[post.title]) {
      return;
    }

    const markdown = converter.makeMarkdown(post.html, new jsdom.JSDOM().window.document);
    const devRequestBody = {
      title: post.title,
      canonical_url: `https://ameersami.com/blog/${post.slug}`,
      body_markdown: markdown,
      main_image: post.feature_image,
      description: post.excerpt,
      published: false,
    };

    postsToPublish.push(devRequestBody);
  });

  // Don't hate for these eslint disables 🙃
  // eslint-disable-next-line no-restricted-syntax
  for (const post of postsToPublish) {
    // eslint-disable-next-line no-await-in-loop
    await fetch(`https://dev.to/api/articles`, {
      headers: {
        'api-key': process.env.DEV_TO_KEY,
        'Content-Type': `application/json`,
      },
      method: `post`,
      body: JSON.stringify({
        article: post,
      }),
    }).catch((err) => console.log(err));
  }
};

module.exports = DevToPublishing;

YAY You're all done!!!! 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉

So you now you should be able to write your posts once and have it published to your personal site and dev.to in one go!