(Partially working)🙃 Automate Blog Post Publishing from Dev.to to Hashnode using Express Server(Importer) - #APIHackathon

Shibu JosephShibu Joseph
4 min read

Introduction

Embarking on the challenging terrain of the Hashnode Hackathon, this project unfolds as a testament to the relentless pursuit of web development excellence. While the completion of the project remains an aspiration at this juncture, the journey has been a profound exploration of technologies, problem-solving, and skill

Framework Architecture with Express

The project's architecture relies on Express, a robust Node.js web application framework. Configured meticulously, Express seamlessly handles HTTP requests, incorporates body parsing, and efficiently serves static files. The portability and efficiency of Express lay a solid foundation for the entire project.

import express from "express";
import bodyParser from "body-parser";
import { getDevToArticleData } from './fetchApiData.js';
import { publishArticleToGraphQL } from './postApiData.js';

const app = express();
app.use(bodyParser.urlencoded({ extended: true }));
app.use(express.static("public"));
const port = 3000;

// Root endpoint, Once this endpoint is hit, The index.ejs file will be renderded in the client side
app.get("/", async (req, res) => {
    res.render("index.ejs")
});

app.post('/submit', async (req, res) => {
    try {
        const url = req.body.data; // Extractiong URL from the client side request

        // Use a regular expression to extract username and slug
        const match = url.match(/https:\/\/dev\.to\/([^\/]+)\/([^\/]+)/);

        if (match && match.length === 3) {
            const username = match[1];
            const slug = match[2];

            console.log('Username:', username);
            console.log('Slug:', slug);

            const articleData = await getDevToArticleData(username, slug);
            console.log(articleData);
            const publishedArticle = await publishArticleToGraphQL(articleData);

            // Assuming you want to send some response back
            res.send(`Article published successfully for ${username}/${slug}`);
        } else {
            console.log('Invalid URL format');
            res.status(400).send('Invalid URL format');
        }
    } catch (error) {
        console.error(error);
        res.status(500).send('Internal Server Error');
    }
});

app.listen(port, () => {
    console.log(`Server running at http://localhost:${port}`);;
});

Unveiling DevTo Article Data

The getDevToArticleData function serves as our passport to the treasure trove of DevTo articles. By providing a username and a unique article slug, this function seamlessly interacts with the DevTo API, fetching comprehensive data about a specific article. The response from the DevTo API is dissected to create a structured data object. Each property within this object encapsulates essential information about the article, including its ID, title, author details, URLs, cover images, and the article's content in both markdown and HTML formats.

async function getDevToArticleData(username, slug) {
    try {
        const response = await axios.get(`https://dev.to/api/articles/${username}/${slug}`, {
            headers: {
                "Accept": acceptHeader,
                "api-key": DevToApiKey,
            },
        });

        // Processing the response
        if (response.status === 200) {
            const responseData = response.data;

            // Extracting relevant data
            const dataObjectPropertyFromDevTo = {
                id: responseData.id,
                slug: responseData.slug,
                title: responseData.title,
                author: {
                    id: responseData.user.user_id,
                    username: responseData.user.username,
                    name: responseData.user.name,
                    profilePicture: responseData.user.profile_image,
                },
                url: responseData.url,
                canonicalUrl: responseData.canonical_url,
                coverImage: {
                    url: responseData.cover_image,
                },
                content: {
                    markdown: responseData.body_markdown,
                    html: responseData.body_html,
                },    
            };

            // Returning the curated data
            return dataObjectPropertyFromDevTo;
        } else {
            // Throwing an error if the response status is not 200
            throw new Error(`Error fetching data. Status code: ${response.status}`);
        }
    } catch (error) {
        // Handling errors during the API request
        throw new Error(`Error making request: ${error.message}`);
    }
}

// Exporting the function for use in our project
export { getDevToArticleData };

Crafting the GraphQL Client for Hashnode

The publishArticleToGraphQL function leverages the Axios library to create a GraphQL client tailored for Hashnode's API. By setting the base URL to 'https://gql.hashnode.com/' and configuring the necessary headers, we establish a secure and efficient channel for interacting with Hashnode's GraphQL endpoint.

import axios from 'axios';

const hashNodeApiKey = ""; // Replace with your actual API key

const graphqlClient = axios.create({
  baseURL: 'https://gql.hashnode.com/',
  headers: {
    'Content-Type': 'application/graphql',
    'Authorization': hashNodeApiKey,
  },
});

const publishArticleMutation = `
  mutation PublishPost($input: PublishPostInput!) {
    publishPost(input: $input) {
      post {
        id
        slug
        title
        author {
          id
          username
          name
          profilePicture
        }
        url
        canonicalUrl
        coverImage {
          url
        }
        content {
          markdown
          html
        }
      }
    }
  }
`;

async function publishArticleToGraphQL(dataHeadersFromDevTo) {
  try {
    console.log("Posting data to GraphQL...");
    const response = await graphqlClient.post('', {
      query: publishArticleMutation,
      variables: {
        input: dataHeadersFromDevTo,
      },
    });

    if (response.status === 200) {
      const publishedArticle = response.data?.data?.publishPost?.post;

      if (publishedArticle) {
        console.log("Article published successfully!");
        return publishedArticle;
      } else {
        throw new Error("Error: Invalid response structure. Post data not found.");
      }
    } else {
      throw new Error(`Error publishing article. Status code: ${response.status}`);
    }
  } catch (error) {
    console.error(`Error making GraphQL request: ${error}`);
    throw error;
  }
}

export { publishArticleToGraphQL };

Check out the video

Conclusion

Although, I was not able to finish the project , I really feel good for my dedication towards a purpose and it was my first try to build in hackathons. I'm very open to fixes and if time permits help me completing the project. Special thanks for the stage and opportunity.

🔗GitHub Repo

3
Subscribe to my newsletter

Read articles from Shibu Joseph directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Shibu Joseph
Shibu Joseph