Imagine being able to automatically generate content using predefined patterns and prompts, and then publish it directly by simply committing a new article.
I've been eager to develop this solution for a long time, and I took advantage of the development of Apologify to implement it.
In this tutorial, I'll show you step by step how to create a Node.js script that does exactly that. Note that the code provided is for example purposes only.
We'll use:
- AstroBuild for static site generation.
- The OpenAI API to generate content.
- Unsplash to fetch images.
- GitHub Actions to schedule content generation.
- Vercel to automate deployment.
This solution is perfect for static site generators and JAMStack frameworks, making it easy to keep your content fresh without manual intervention.
Let's get started!
Step 1: Set Up Your Project Environment
First, remember to install the necessary dependencies in your project:
npm install openai node-fetch@2 fs/promises path stopword
Note: We're using
node-fetch@2
because version 3 requires ES modules and additional configurations.
Step 2: Define Patterns and Prompts
Create a folder src/lib/
and inside it, two files: patterns.js
and prompts.js
.
patterns.js
Here we'll define the patterns and variables we'll use to generate titles.
// src/lib/patterns.js
// Define the patterns with placeholders for variables
export const patterns = [
"How to {action} in {language}",
"Beginner's Guide to {topic}",
"Top {tools} for {activity}"
];
// Define possible values for each variable
export const variables = {
action: ["write an apology letter", "express regret", "seek forgiveness"],
language: ["English", "Spanish", "French"],
topic: ["Apology Letters", "Sincere Regrets", "Making Amends"],
tools: ["phrases", "templates", "tips"],
activity: ["writing apologies", "mending relationships", "expressing sincerity"]
};
prompts.js
Here we'll define the prompts we'll use with the OpenAI API to generate the content.
This part is the most important. You need to dedicate time to writing each one to create clear and as "human" as possible articles. These are just example prompts.
// src/lib/prompts.js
// Define prompts corresponding to each pattern
export const prompts = {
"How to {action} in {language}": "You are an expert in writing apology letters in {language}. Write an article on how to {action}, including practical examples.",
"Beginner's Guide to {topic}": "You are a friendly instructor. Write an introductory guide on {topic} aimed at beginners.",
"Top {tools} for {activity}": "You are a professional advisor. Write an article about the best {tools} for {activity}, including pros and cons."
};
Step 3: Implement Pattern and Variable Selection
We'll create functions to select patterns and fill in variables.
// index.js
import { patterns, variables } from './src/lib/patterns.js';
// Function to select a random element from an array
function selectRandomElement(array) {
return array[Math.floor(Math.random() * array.length)];
}
// Function to extract variable names from a pattern
function extractVariables(pattern) {
// Match all occurrences of {variable} in the pattern
return (pattern.match(/\{(\w+)\}/g) || []).map(v => v.slice(1, -1));
}
// Function to create a title by replacing variables in the pattern
function createTitle(pattern, selectedVariables) {
return pattern.replace(/\{(\w+)\}/g, (_, key) => selectedVariables[key]);
}
// Example usage
const selectedPattern = selectRandomElement(patterns); // Select a random pattern
const variableNames = extractVariables(selectedPattern); // Get variable names from the pattern
const selectedVariables = {};
// Select random values for each variable
variableNames.forEach(name => {
selectedVariables[name] = selectRandomElement(variables[name]);
});
const title = createTitle(selectedPattern, selectedVariables); // Create the title
console.log(`Generated Title: ${title}`);
Step 4: Generate Content with the OpenAI API
Now, we'll use the title and prompt to generate content.
// index.js (continued)
import OpenAI from 'openai';
const openai = new OpenAI({
apiKey: process.env.OPENAI_API_KEY, // Your OpenAI API key from environment variables
});
async function generateContent(title, pattern) {
// Get the prompt template and replace variables with selected values
const promptTemplate = prompts[pattern];
const prompt = promptTemplate.replace(/\{(\w+)\}/g, (_, key) => selectedVariables[key]);
try {
// Make a request to the OpenAI API
const response = await openai.chat.completions.create({
model: "gpt-4", // Specify the model to use
messages: [
{ role: "system", content: prompt }, // Provide the system prompt
{ role: "user", content: title } // Provide the user input
],
});
// Extract and return the generated content
const content = response.choices[0].message.content.trim();
return content;
} catch (error) {
console.error(`Error generating content: ${error}`);
return null;
}
}
// Usage
(async () => {
const content = await generateContent(title, selectedPattern);
console.log(`Generated Content:\n${content}`);
})();
Note: Make sure to set the
OPENAI_API_KEY
environment variable with your OpenAI API key.
Step 5: Fetch Images from Unsplash
To enrich our content, we'll fetch related images.
// index.js (continued)
import fetch from 'node-fetch';
async function getUnsplashImage(query) {
try {
// Make a request to the Unsplash API for a random image
const response = await fetch(`https://api.unsplash.com/photos/random?query=${encodeURIComponent(query)}&orientation=landscape`, {
headers: {
'Authorization': `Client-ID ${process.env.UNSPLASH_ACCESS_KEY}` // Your Unsplash access key
}
});
const data = await response.json();
// Extract necessary information from the response
return {
url: data.urls.regular,
author: data.user.name,
authorProfile: data.user.links.html
};
} catch (error) {
console.error(`Error fetching image: ${error}`);
return null;
}
}
// Usage
(async () => {
const image = await getUnsplashImage(title);
console.log(`Fetched Image: ${image.url}`);
})();
Note: You'll need an Unsplash access key in
UNSPLASH_ACCESS_KEY
.
Step 6: Save the Generated Content
Now, we'll save the content in a Markdown file, including the frontmatter.
// index.js (continued)
import fs from 'fs/promises';
import path from 'path';
// Function to create a URL-friendly slug from the title
function createSlug(title) {
return title.toLowerCase().split(' ').join('-').replace(/[^\w\-]/g, '');
}
async function saveContent(title, content, image) {
const slug = createSlug(title); // Generate slug from the title
const dir = path.join('src', 'content', 'articles', slug); // Define the directory path
await fs.mkdir(dir, { recursive: true }); // Create the directory if it doesn't exist
// Prepare the frontmatter with metadata
const frontmatter = `---
title: "${title}"
image: "${image.url}"
author: "${image.author}"
authorProfile: "${image.authorProfile}"
---`;
const file = path.join(dir, 'index.md'); // Define the file path
await fs.writeFile(file, `${frontmatter}\n\n${content}`); // Write the content to the file
console.log(`Content saved in ${file}`);
}
// Usage
(async () => {
const image = await getUnsplashImage(title);
await saveContent(title, content, image);
})();
Step 7: Deploy with Vercel
We use Vercel as our hosting platform, which integrates seamlessly with GitHub. Every time we push new content, Vercel automatically rebuilds and deploys the site.
Deployment Steps:
Push to GitHub: Commit your code to a GitHub repository.
Connect Vercel: Go to Vercel and import your repository.
Set Environment Variables: In Vercel, set the
OPENAI_API_KEY
andUNSPLASH_ACCESS_KEY
environment variables.Automatic Deployments: Vercel will automatically build and deploy your site every time you push changes.
Step 8: Automate with GitHub Actions
To automate the execution of the script, we'll create a GitHub Actions workflow.
Create the file .github/workflows/generate-content.yml
:
name: Generate Content
on:
schedule:
- cron: '0 0 * * *' # Every day at midnight UTC
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Node.js
uses: actions/setup-node@v3
with:
node-version: '16'
- name: Install dependencies
run: npm install
- name: Run script
env:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
UNSPLASH_ACCESS_KEY: ${{ secrets.UNSPLASH_ACCESS_KEY }}
run: node index.js
- name: Commit and push
run: |
git config --local user.email "action@github.com"
git config --local user.name "GitHub Action"
git add .
git commit -m "Automatically generated content"
git push
Important: Add your API keys as secrets in your repository (
OPENAI_API_KEY
andUNSPLASH_ACCESS_KEY
).
Step 9: Putting It All Together
With everything set up, here's how the workflow operates:
Content Generation: The GitHub Action runs
node index.js
, which generates a new article.Commit Changes: The new article is committed back to the repository.
Trigger Deployment: Vercel detects the new commit and rebuilds the site.
Automatic Publishing: The new content is live on your site without any manual intervention.
This approach is ideal for static site generators and JAMStack frameworks. By automating content creation and deployment, you can keep your site fresh and engaging without lifting a finger.
Personal Experience
When I first set up Apologify, I wanted a way to consistently provide new, valuable content to my readers without spending countless hours writing each article manually.
By combining the power of OpenAI's API, Astro's static site generation, and the automation capabilities of GitHub Actions and Vercel, I was able to create a self-sustaining content pipeline.
This setup not only saved me time but also ensured that my site remained up-to-date with relevant content. The integration with Astro and Vercel made deployment seamless, and leveraging JAMStack principles resulted in a fast, scalable website.
Conclusion
And there you have it! We've created a script that automatically generates content using patterns and prompts, fetches images from Unsplash, and saves everything as Markdown files. By integrating with AstroBuild and deploying with Vercel, we automate the entire process from content generation to publishing.
This solution is perfect for static site generators and JAMStack frameworks, making it easy to keep your content fresh without manual intervention.
I hope this tutorial was helpful! If you have any questions or comments, feel free to share them.
Top comments (0)