DEV Community

Cover image for Next-Level Technical Blogging with Dev.to API
Arek Nawo for Vrite

Posted on • Updated on • Originally published at vrite.io

Next-Level Technical Blogging with Dev.to API

Having a platform like DEV at the center of your technical blog is a great option. With over 1 million users, Dev.to is one of the largest developer communities there is, welcoming technical writers and readers at all levels.

That said, things are complicated when it comes to writing and delivering content on DEV. With its minimal editor, you’ll likely have to do a lot of copy-pasting and manual adjustments to get your post from your editor to DEV and other platforms you might want to cross-post to, like Hashnode.

Thankfully, DEV’s underlying platform — Forem — has a great API you can leverage to both publish and fetch blog posts from DEV with ease. Let me show you how…

Getting Started With Forem API

Currently, there are 2 versions of the Forem API available — version 0 and version 1. They differ a bit in the request format but provide similar endpoints. You can use either one of them (both are documented in the official docs), though it’s recommended to use the newer v1.

To switch between different versions, you have to provide proper HTTP headers. For v1, these are:

API-Key: [your-api-key]
Accept: application/vnd.forem.api-v1+json
Content-Type: application/json
Enter fullscreen mode Exit fullscreen mode

While for the v0:

API-Key: [your-api-key]
Accept: application/json
Content-Type: application/json
User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; .NET CLR 1.1.4322)
Enter fullscreen mode Exit fullscreen mode

The key difference is in the Accept header — you have to specify application/vnd.forem.api-v1+json to enable the newer version. On top of that, for v0, you have to provide a valid User-Agent header, as otherwise your request will result in 403 Forbidden error. The provided value is only an example, that I verified to work.

From this point on, I’ll be referencing only v1, though all the endpoints described are the same in both versions.

With all this made clear, all you need now is an API key.

Getting Your API Key

To get your API key you have to go to the very bottom of the Extensions tab of your Dev.to account’s Settings page. There you’ll see the DEV Community API Keys section:

DEV Community API Keys section

Enter a custom description and Generate API Key. You should see a new API key appear on the list, from where you can copy it.

Fetching Articles From DEV

Now, there’s a lot you can do with this API and its endpoints, which are all pretty well-documented. So, rather than serving as the second documentation, I’d like to discuss two of probably the biggest use cases for this API.

The first one is to use Dev.to as your content hub, making DEV the center of your programming publication, while also serving the content on your own website, like a custom blog or portfolio page.

The most important endpoint for this use case would be GET /api/articles/me, which allows you to retrieve all of your published articles. Here’s how you can use it:

interface DEVArticle {
  type_of: string;
  id: number;
  title: string;
  description: string;
  published: boolean;
  published_at: string;
  slug: string;
  path: string;
  url: string;
  comments_count: number;
  public_reactions_count: number;
  page_views_count: number;
  published_timestamp: string;
  body_markdown: string;
  positive_reactions_count: number;
  cover_image: string;
  tag_list: string[] | string;
  tags?: string[];
  canonical_url: string;
  reading_time_minutes: number;
  user: {
    name: string;
    username: string;
    twitter_username: string | null;
    github_username: string | null;
    user_id: number;
    website_url: string | null;
    profile_image: string;
    profile_image_90: string;
  };
  organization?: {
    name: string;
    username: string;
    slug: string;
    profile_image: string;
    profile_image_90: string;
  };
}
interface GetArticlesOptions {
  pages?: number;
  perPage?: number;
}
const getArticles = async (
  options?: GetArticlesOptions
): Promise<DEVArticle[]> => {
  const articles: DEVArticle[] = [];
  const pages = options?.pages || 1;
  const perPage = options?.perPage || 30;

  for (let page = 1; page <= pages; page += 1) {
    const response = await fetch(
      `https://dev.to/api/articles/me?per_page=${perPage}&page=${page}`,
      {
        method: 'GET',
        headers: {
          'api-key': 'your-api-key',
          accept: 'application/vnd.forem.api-v1+json',
          'content-type': 'application/json',
        },
      }
    );
    const json = await response.json();

    articles.push(...json);
  }

  return articles;
};

getArticles({ pages: 3, perPage: 100 }).then((result) => {
  console.log(result);
});

Enter fullscreen mode Exit fullscreen mode

The API is CORS-enabled, meaning you’ll have to use the getArticles() functions from your backend. For making the actual request, you can use the fetch() function, available since Node.js v18. For older versions of Node.js, you can use a fetch()-compatible library like node-fetch.

Converting Markdown to HTML

Once you have the article data, you’ll likely have to process its body_markdown to a format required by your website, like HTML. There are many tools you can do this with — here’s an example using Marked.js:

const markdownToHTML = (markdown: string): string => {
  const regex = new RegExp(/^.*{%\s?(.+?) (.+?)\s?%}.*(?:\n|$)/);
  const getEmbedUrl = (embedType: string, input: string): string => {
    switch (embedType) {
      case 'youtube':
        return `https://www.youtube.com/embed/${input}`;
      case 'codepen':
        return input.replace(/\/pen\//, '/embed/');
      case 'codesandbox':
        return `https://codesandbox.io/embed/${input}`;
      default:
        // etc...
        return '';
    }
  };
  const embedExtension = {
    name: 'embedExtension',
    level: 'block',
    start(src) {
      return src.match(/^.*{%/)?.index;
    },
    tokenizer(src) {
      const match = regex.exec(src);

      console.log('tokenizer', src, match);
      if (match) {
        return {
          type: 'embedExtension',
          raw: match[0],
          embedType: match[1].trim(),
          input: match[2].trim(),
          tokens: [],
        };
      }
    },
    renderer(token) {
      return `<iframe src="${getEmbedUrl(
        token.embedType,
        token.input
      )}"></iframe>`;
    },
  };
  marked.use({ gfm: true, extensions: [embedExtension] });

  return marked.parse(markdown);
};

Enter fullscreen mode Exit fullscreen mode

For the most part, DEV uses a standard Markdown format, which is easily handled by Marked.js and its built-in GitHub Flavored Markdown support. For embeds (i.e. liquid tags), you can easily extend Marked.js using its Custom Extension system. Above is an example of an extension converting liquid tags to <iframe> elements, creating proper URLs for embeds like YouTube, CodePen, or CodeSandbox. You should be able to easily extend it to handle the embeds you use.

If you want to go beyond fetching articles, Forem API provides various other endpoints I encourage you to explore. Here are a few examples:

Publishing on DEV via API

An arguably more popular use case for an API like Forem’s is simplified or automatic publishing.

In general, there’s a lot of copy-pasting when writing a technical article — between various code and text editors, publications like DEV or Hashnode, and potentially even a CMS powering your custom blog. Going through this whole process for every single article can be tiring.

That said, with some automation and different APIs, you can simplify this process to a high degree. In the case of Forem API, you can use the POST /api/articles to first publish an article on DEV and then update it as needed with PUT /api/articles/{id}. Here’s an example:

interface DEVArticleInput {
  title?: string;
  body_markdown?: string;
  published?: boolean;
  series?: string | null;
  main_image?: string | null;
  canonical_url?: string | null;
  description?: string;
  tags?: string[];
  organization_id?: number | null;
}

const publishArticle = async (
  article: DEVArticleInput
): Promise<DEVArticle> => {
  const response = await fetch('https://dev.to/api/articles', {
    method: 'POST',
    headers: {
      'api-key': 'your-api-key',
      accept: 'application/vnd.forem.api-v1+json',
      'content-type': 'application/json',
    },
    body: JSON.stringify({
      article,
    }),
  });
  const json = await response.json();

  return json;
};
const updateArticle = async (
  articleId: string | number,
  articleUpdate: DEVArticleInput
): Promise<DEVArticle> => {
  const response = await fetch(`https://dev.to/api/articles/${articleId}`, {
    method: 'PUT',
    headers: {
      'api-key': 'your-api-key',
      accept: 'application/vnd.forem.api-v1+json',
      'content-type': 'application/json',
    },
    body: JSON.stringify({
      article: articleUpdate,
    }),
  });
  const json = await response.json();

  return json;
};

Enter fullscreen mode Exit fullscreen mode

With these two functions, you can easily publish and update your articles as needed, e.g.

publishArticle({
  title: 'Hello, world!',
  body_markdown: '# Hello World\nThis is my first post published through Forem API!',
  published: false,
}).then((article) => {
  console.log(article);
});

// Later on
updateArticle('1234567', {
  tags: ['javascript', 'typescript', 'react', 'node'],
  series: 'Hello World',
  canonical_url: 'https://example.com',
}).then((updatedArticle) => {
  console.log(updatedArticle);
});

Enter fullscreen mode Exit fullscreen mode

Now, you’ll need to convert your content input to a DEV-compatible Markdown format, doing a somewhat inverse operation to what was demonstrated earlier with Marked.js. This will likely require some custom processing to best fit your use case.

Now, when properly implemented, such publishing automation can greatly improve your publishing experience. Still, this likely won’t help you escape copy-pasting between your editors.

That said, if you’re looking for something that could provide a great experience across your entire technical writing process — from writing and coding to content management and publishing — then you might be interested in my latest project — Vriteopen-source headless CMS for technical content.

Easy Publishing With Vrite

Vrite combines multiple tools and concepts that are familiar to all developers, like:

in the form of headless CMS. It’s a unique combination that, in my opinion, has the potential to provide the best experience across the entire technical writing space. For more details, you can check out the official guide.

Vrite editor

Publishing via Dev.to Extension

Staying on the topic of Dev.to, Vrite’s latest feature — Extensions — make integrating Vrite with DEV as easy as clicking a few buttons, allowing you to easily publish and update content from Vrite to DEV both manually and automatically.

Right now, Vrite Extensions enable easy integration with platforms like DEV, Hashnode or Medium and extend the capabilities of Vrite editor with the power of OpenAI’s GPT-3.5, with a lot more to come.

Setting up an extension is easy and can be done from the Extensions side panel:

Configuring Dev.to extension

You can check out this blog post for a more detailed introduction to Vrite Extensions.

Bottom Line

There’s a lot you can do with Dev.to’s API. Between fetching and publishing content it’s really versatile and can be used to handle a lot of custom use cases.

That said, if you’re interested in an even better technical writing experience, definitely give Vrite a try and let me know what you think about it and how I can make it better!

Top comments (0)