cd /news/developer-tools/how-i-built-a-free-ai-prompt-library… · home topics developer-tools article
[ARTICLE · art-72174] src=dev.to ↗ pub= topic=developer-tools verified=true sentiment=↑ positive

How I Built a Free AI Prompt Library With Node.js and PostgreSQL — From Zero to Live in 30 Days

A developer built PromptZyo, a free AI prompt library for professionals, using Node.js, Express, EJS, and PostgreSQL, deploying it live on Railway in 30 days. The site serves structured prompts organized by profession to help users get better results from AI tools like ChatGPT and Claude.

read7 min views1 publishedJul 24, 2026

If you have been following my Dev.to articles, you know I built Chatzyo — a real-time video chat platform using WebRTC. That project taught me a lot about real-time infrastructure, WebSocket connections, and scaling live traffic.

PromptZyo is completely different. No WebSockets. No real-time anything. Just a well-structured content site built for SEO and passive income.

Here is the honest technical breakdown of how I built it, the decisions I made, and what I would do differently.

Most professionals — teachers, lawyers, HR managers, nurses — know that ChatGPT and Claude can help them. But they keep getting generic, useless results.

The problem is not the AI tool.The problem is the prompt.

A vague prompt: "write a lesson plan "Result: generic, unusable output

A specific prompt: "write a 45-minute lesson plan for Grade 5 students on

multiplying fractions for the first time, for a class that excels at whole number operations but has never seen fraction multiplication"

Result: genuinely useful first draft

Nobody had built a library of professionally structured prompts organized by profession. So I built PromptZyo — a free AI prompt library for professionals.

I know Node well from Chatzyo. For a content-heavy site with PostgreSQL queries and server-side rendering, Express is fast, lightweight, and does exactly what I need.

No need for a heavy framework. Express + EJS handles the routing and

templating cleanly.

This was a deliberate decision.For Chatzyo I used React for the interactive UI. For PromptZyo — a content library where SEO matters more than interactivity — server-side rendering with EJS is the right choice.

For a prompt library where I need 150+ pages indexed quickly — EJS wins.

My initial instinct was MongoDB — I know it well and prompts seem like

document data.

But the relational structure changed my mind:

This is relational data. PostgreSQL handles it cleanly with foreign keys and JOIN queries. MongoDB would have required manual relationship management.

The query that convinced me:

SELECT p.*, 
       pr.name as profession_name,
       pr.slug as profession_slug,
       c.name as category_name
FROM prompts p
LEFT JOIN professions pr ON p.profession_id = pr.id
LEFT JOIN categories c ON p.category_id = c.id
WHERE pr.slug = $1 AND p.is_active = true
ORDER BY p.created_at DESC

Clean, fast, readable. PostgreSQL was the right call.

For Chatzyo I use a different hosting setup. For PromptZyo I chose Railway for a few reasons:

The one gotcha: Railway's internal networking only works within the same project. My PostgreSQL and Node app are in the same Railway project — so the internal connection string works. If they were in different projects, I would need the public proxy URL which has higher latency.

Cloudflare sits in front of Railway and handles:

Setup was straightforward — point Cloudflare nameservers at Namecheap, add CNAME record pointing to Railway's deployment URL, done.

The schema has 6 main tables:

-- Core content tables
professions (id, name, slug, description, icon, is_active)
categories (id, name, slug, profession_id)
prompts (id, title, slug, content, profession_id, 
         category_id, ai_tool, difficulty, 
         copy_count, is_active, created_at)

-- User features (built but not launched yet)
users (id, google_id, email, name, created_at)
saved_prompts (user_id, prompt_id, saved_at)

-- Blog
blog_posts (id, title, slug, excerpt, content, 
            author, category, is_published, 
            published_at, reading_time)

-- Sessions
session (sid, sess, expire)

The copy_count

column tracks how many times each prompt has been copied. Every time a user clicks Copy Prompt, it fires a POST to

/api/track-copy/:id

which increments the count.

router.post('/track-copy/:id', async (req, res) => {
  try {
    await pool.query(
      'UPDATE prompts SET copy_count = copy_count + 1 
       WHERE id = $1',
      [req.params.id]
    );
    res.json({ success: true });
  } catch (err) {
    console.error(err);
    res.json({ error: 'Failed to track' });
  }
});

Simple but effective engagement tracking.

This is where PromptZyo differs most from Chatzyo. Chatzyo's traffic came from localized pages. PromptZyo's traffic will come from a different content structure.

Each URL is a crawlable page with unique

title, meta description, and canonical tag.

Every page type has appropriate schema:

// Profession pages
{
  "@type": "CollectionPage",
  "numberOfItems": prompts.length,
  "breadcrumb": { ... }
}

// Individual prompt pages  
{
  "@type": "HowTo",  // for the prompt itself
  "step": [ ... ]
}
{
  "@type": "FAQPage",  // for the FAQ section
  "mainEntity": [ ... ]
}

// Blog posts
{
  "@type": "Article",
  "author": { "@type": "Person", ... },
  "datePublished": "...",
  "dateModified": "..."
}

Schema markup helps Google understand what each page is — and can trigger

rich results in search.

The sitemap generates dynamically from the database:

router.get('/', async (req, res) => {
  const professions = await pool.query(
    'SELECT slug FROM professions WHERE is_active = true'
  );
  const prompts = await pool.query(
    'SELECT slug FROM prompts WHERE is_active = true'
  );
  const blogPosts = await pool.query(
    'SELECT slug FROM blog_posts WHERE is_published = true'
  );

  // Build XML dynamically
  let xml = `<?xml version="1.0" encoding="UTF-8"?>
  <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">`;

  // Add all URLs...

  res.setHeader('Content-Type', 'application/xml');
  res.send(xml);
});

Every new prompt or blog post automatically appears in the sitemap. No manual updates needed.

When a new blog post is published, it automatically notifies Bing via IndexNow:

async function notifyIndexNow(urls) {
  const key = process.env.INDEXNOW_KEY;

  const data = JSON.stringify({
    host: 'promptzyo.com',
    key: key,
    keyLocation: `https://promptzyo.com/${key}.txt`,
    urlList: Array.isArray(urls) ? urls : [urls]
  });

  // POST to api.indexnow.org
  // Bing indexes within hours instead of weeks
}

New content gets indexed by Bing within hours instead of waiting for the crawler to find it organically.

121 prompts across 12 professions means 121 individual pages that Google can index. Each page has:

That is a lot of on-page content for what could have been a simple prompt card. But the content depth is what makes each page rankable — not just indexable.

On top of the prompt pages, I am building topical authority through blog articles.

Articles like:

Each article targets a specific high-volume keyword and links back to relevant profession pages and individual prompts.

I built a custom admin panel rather than using a CMS. It handles:

The bulk import route accepts a JSON array:

router.post('/bulk-import', isAdmin, async (req, res) => {
  const { profession_id, ai_tool, 
          difficulty, prompts_json } = req.body;

  const prompts = JSON.parse(prompts_json);

  for (const prompt of prompts) {
    const slug = prompt.title
      .toLowerCase()
      .replace(/[^a-z0-9]+/g, '-')
      .replace(/(^-|-$)/g, '');

    await pool.query(`
      INSERT INTO prompts 
        (title, slug, content, profession_id, 
         category_id, ai_tool, difficulty)
      VALUES ($1, $2, $3, $4, $5, $6, $7)
      ON CONFLICT (slug) DO NOTHING
    `, [prompt.title, slug, prompt.content, 
        profession_id, prompt.category_id, 
        ai_tool, difficulty]);
  }

  res.render('admin-bulk', { success: `Added ${added} prompts` });
});

This let me add 10 prompts per profession in one paste rather than one by one.

Building Chatzyo taught me real-time infrastructure. Building PromptZyo

taught me something different:

Content architecture at scale.

With Chatzyo, the technical challenge was keeping WebSocket connections stable and matching users in real time. With PromptZyo, the challenge is making 150+ pages rankable individually while maintaining site-wide

consistency.

Different problems, different solutions, both valuable.

Zero organic traffic at day 30 is completely normal for a new domain. Google takes 3-6 months to trust new sites. I built this expecting

nothing until month 3.

The SEO foundation is solid. Now it is a waiting game combined with consistent content production.

1. Start the blog earlier

I launched the prompt library first and added the blog later. In hindsight, I should have had 5 blog articles ready before launch. Blog content builds topical authority faster than prompt pages alone.

2. Use a CDN for images from day one

I added Cloudflare later. Should have set it up before launch — it is free and the performance difference is immediate.

3. Build the admin panel first

I added features to the admin panel throughout the build. Building it

comprehensively at the start would have saved time later.

PromptZyo is completely free. 121 prompts across

12 professions. No account needed.

If you are building something similar — a content library, a tool directory, anything SEO-focused — happy to answer questions in the comments.

And if you read my Chatzyo articles

— thank you for following along. Building two very different products

back to back has been the best technical education I could have given myself.

Built with Node.js, Express, EJS, PostgreSQL, Railway and Cloudflare.

Previous build: How I Built an Anonymous Chat Platform Without Signup Using WebRTC

── more in #developer-tools 4 stories · sorted by recency
── more on @promptzyo 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
Live at https://your-agent.zahid.host
Get free account → Pricing
from €0/mo · no card required
LIVE [news/how-i-built-a-free-a…] indexed:0 read:7min 2026-07-24 ·