First of all let's see the code below
// Function to post to Dev.to
app.post("/post-to-devto", async (c) => {
try {
const { title } = await c.req.json();
if (!title) {
c.status(400);
return c.json({
error: "Title is required",
});
}
// Check if DEV_TO_API token is available
if (!process.env.DEV_TO_API_TOKEN) {
c.status(500);
return c.json({
error: "DEV_TO_API_TOKEN environment variable is not set",
});
}
// Fetch the document from Firestore publish collection using the title
const postsSnapshot = await firestore
.collection("publish")
.where("title", "==", title.replaceAll("-", " "))
.get();
if (postsSnapshot.empty) {
c.status(404);
return c.json({
error: `No post found with title: ${title}`,
});
}
const postDoc = postsSnapshot.docs[0];
const postData = postDoc.data();
// Validate required fields
if (!postData.content && !postData.htmlContent) {
return c.json(
{
success: false,
error: "Post content is required (content or htmlContent field)",
},
400
);
}
// Prepare the article data for Dev.to API
const processedTags = (() => {
// Handle tags properly for Dev.to API
if (!Array.isArray(postData.tags) || postData.tags.length === 0) {
return ["general"];
}
// Dev.to tags must be lowercase, only alphanumeric characters (no hyphens, spaces, or special chars)
const cleanTags = postData.tags
.slice(0, 4) // Dev.to allows max 4 tags
.map((tag) => {
// Convert to string and clean up
let cleanTag = String(tag)
.toLowerCase()
.trim()
// Remove ALL non-alphanumeric characters (including hyphens, spaces, underscores, etc.)
.replace(/[^a-zA-Z0-9]/g, "")
// Limit length (Dev.to has tag length limits)
.substring(0, 30);
return cleanTag;
})
.filter((tag) => tag.length > 0 && tag.length <= 30);
// Ensure we have at least one valid tag
return cleanTags.length > 0 ? cleanTags : ["general"];
})();
// Prepare content for Dev.to (they prefer markdown)
let bodyContent = "";
if (postData.content) {
// If content exists, use it (assume it's markdown)
bodyContent = postData.content;
} else if (postData.htmlContent) {
// If only HTML content exists, convert basic HTML to markdown
bodyContent = postData.htmlContent
.replace(/]*>(.*?)<\/h1>/gi, "# $1\n\n")
.replace(/]*>(.*?)<\/h2>/gi, "## $1\n\n")
.replace(/]*>(.*?)<\/h3>/gi, "### $1\n\n")
.replace(/
]*>(.*?)<\/p>/gi, "$1\n\n")
.replace(/
/gi, "\n")
.replace(/]*>(.*?)<\/strong>/gi, "**$1**")
.replace(/]*>(.*?)<\/em>/gi, "*$1*")
.replace(/]*>(.*?)<\/code>/gi, "`$1`")
.replace(/<[^>]*>/g, "") // Remove any remaining HTML tags
.replace(/\n\s*\n\s*\n/g, "\n\n") // Clean up excessive newlines
.trim();
}
// Add footer with original publication link
const footerText = `\n\n---\n\n*Originally published on [iHateReading](https://ihatereading.in/t/${encodeURIComponent(
title.replace(/\s+/g, "-")
)})*`;
bodyContent += footerText;
const articleData = {
article: {
title: "postData.title || title,"
body_markdown: bodyContent,
tags: processedTags,
published: true,
series: postData.series || null,
canonical_url: postData.canonicalUrl || null,
description: "postData.description || \"\","
cover_image: postData.coverImage || null,
main_image: postData.mainImage || null,
},
};
// Post to Dev.to API
const devtoResponse = await fetch("https://dev.to/api/articles", {
method: "POST",
headers: {
"Content-Type": "application/json",
"api-key": process.env.DEV_TO_API_TOKEN,
},
body: JSON.stringify(articleData),
});
if (!devtoResponse.ok) {
const errorData = await devtoResponse.text();
console.error("Dev.to API error:", errorData);
console.error("Response status:", devtoResponse.status);
console.error(
"Response headers:",
Object.fromEntries(devtoResponse.headers.entries())
);
// Try to parse error for better error messages
let errorMessage = "Failed to post to Dev.to";
try {
const parsedError = JSON.parse(errorData);
if (parsedError.error) {
errorMessage = `Dev.to API Error: ${parsedError.error}`;
}
} catch (e) {
errorMessage = `Dev.to API Error (${devtoResponse.status}): ${errorData}`;
}
throw new Error(errorMessage);
}
const responseData = await devtoResponse.json();
// Update Firestore document with Dev.to post information
await firestore.collection("publish").doc(postDoc.id).update({
devtoPostId: responseData.id,
devtoUrl: responseData.url,
devtoPublishedAt: new Date(),
lastUpdated: new Date(),
});
return c.json({
success: true,
message: "Post published successfully to Dev.to",
data: {
devtoPostId: responseData.id,
devtoUrl: responseData.url,
title: "responseData.title,"
publishedAt: responseData.published_at,
},
});
} catch (error) {
console.error("Error posting to Dev.to:", error);
c.status = 500;
return c.json({
error: error.message,
});
}
});
This endpoint is my simple automation API that takes a blog from our website, iHateReading, using the title of the blog and puts the content on dev.to the dev platform.
For newcomers, I write content and distribute it on multiple platforms such as Medium, Devto and Hashnode and iHatereading
But every time, I've to push content on each platform by copy-pasting, because I have made a method to convert content into HTML and markdown, so that I can go to the website and simply copy-paste on devto.
But this again takes time, and I was bored with not doing it manually and ended up making a Honojs endpoint to simply put content on automation.
For the API key to post on devto, go to devto account page and grab the integration token.
One can do the same for Medium and Hashnode, and Twitter at the same time. Make sure to parse the data correctly for each of the platforms, for example, tags in the devto platform don't allow non-alphanumeric characters and so on.
Customisation is added in the end before posting content on devto. Add originally published on {website blog link} in the end.
We can use the AI LLM after fetching content from the database, firestore in our case, to recreate the content or rewrite it and then post it on a specific platform. But I want originality to sustain so no AI layer is added.
One can use these APIs and create a SAAS tool allows other devs to post content on devto, Medium and other platforms from one single source of editor or database.
That's it for today
See you in the next one
iHateReading
Originally published on iHateReading
Top comments (0)