Projects

Build an AI Image Generator: From Prompt to Working App in 30 Minutes

This is the "hello world" of AI-powered apps. You type a description. You click a button. An AI generates an image. It's how you go from using AI to code, to building apps that are powered by AI.

TL;DR: Build a web app where users type a prompt and get an AI-generated image back. Uses the OpenAI DALL-E API (or Replicate as a cheaper alternative). You'll need ~$5 in API credits, a text editor, and Node.js. This tutorial covers the full build: UI, API connection, error handling, image history, and deploying to Vercel.

What You'll Build

By the end of this tutorial, you'll have a working web app that looks like this:

  • A text input — type any description ("a golden retriever wearing a hard hat on a construction site")
  • A Generate button — click it, watch a loading spinner, get your image
  • An image display area — shows the generated image with a download button
  • A history sidebar — your last 10 generations, saved in the browser

It's a real, deployable app. Not a toy. You can put it on a custom domain and use it every day. Friends can use it. You can turn it into a product.

Why This Project Matters
Most vibe coding tutorials teach you to build apps that run on your computer. This project builds an app that calls an AI API — meaning your app uses AI to do work. That's the difference between "I built this with AI" and "I built an AI-powered product."

What You'll Need

  • Node.js installed — check with node --version in your terminal
  • An OpenAI API key — sign up at platform.openai.com, add $5-10 in credits
  • A code editor — Cursor, VS Code, whatever you use
  • A Vercel account — free tier is plenty for deployment
  • ~30 minutes

Cost reality check: DALL-E 3 costs $0.04 per standard image. Your $5 in credits = 125 images. More than enough to build, test, and show friends. If you want to go cheaper, we'll cover Replicate in Step 3 — their models run about $0.003 per image.

Step 1: Set Up the Project

Open Cursor (or Claude Code in your terminal) and give it this prompt:

Your prompt to AI

Create a new Node.js + Express project for an AI image generator. I need:
- A basic Express server in server.js
- An index.html file for the frontend
- A .env file template (not real keys) with OPENAI_API_KEY
- package.json with express, dotenv, and node-fetch as dependencies
- A .gitignore that excludes .env and node_modules
Set it up so I can run "node server.js" and open localhost:3000

AI will scaffold the whole structure. You'll get:

my-image-generator/
├── server.js          ← Express server (handles API calls)
├── index.html         ← The UI your users see
├── .env               ← Your secret API key (never commit this)
├── .env.example       ← Template for others
├── package.json       ← Dependencies
└── .gitignore         ← Keeps secrets out of git

After AI generates the files, run:

npm install

Then open .env and add your real OpenAI key:

OPENAI_API_KEY=sk-your-actual-key-here
Never commit your .env file. Your API key is like a credit card number. If it lands in a public GitHub repo, bots will find it within minutes and rack up charges. The .gitignore file AI created should block it — double-check before your first git push.

Step 2: Build the UI

Now let's build the frontend. Give AI this prompt:

Your prompt to AI

Build the complete index.html for my AI image generator. I want:
- Dark theme, clean modern design
- A large text area where users type their image prompt
- A "Generate Image" button
- A loading spinner that shows while generating
- An image display area (hidden until image loads)
- A "Download" button that appears with the image
- A history section showing the last 10 prompts as thumbnails
- All styles inline in the HTML (no separate CSS file needed)
Make it look professional, not like a student project

What you'll get back is a complete HTML file with embedded CSS. The key elements are:

<!-- The prompt input -->
<textarea id="promptInput" 
  placeholder="Describe the image you want..."></textarea>

<!-- The button -->
<button id="generateBtn" onclick="generateImage()">
  Generate Image
</button>

<!-- Loading state -->
<div id="loading" class="hidden">
  <div class="spinner"></div>
  <p>Generating your image... (~10 seconds)</p>
</div>

<!-- Result -->
<div id="result" class="hidden">
  <img id="generatedImage" alt="AI Generated Image">
  <button onclick="downloadImage()">Download</button>
</div>

Run node server.js and open localhost:3000. You should see the UI — nothing works yet, but it should look clean.

Step 3: Connect the AI API

This is the core of the project. Give AI this prompt:

Your prompt to AI

Add a /generate endpoint to my Express server that:
- Accepts a POST request with a JSON body containing { prompt: "..." }
- Calls the OpenAI DALL-E 3 API to generate one image
- Uses 1024x1024 size and "vivid" style
- Returns the image URL as JSON: { imageUrl: "..." }
- Handles errors gracefully (invalid key, rate limit, content policy)
Use the openai npm package. Load the API key from process.env.OPENAI_API_KEY

The server code AI generates will look like this:

import OpenAI from 'openai';
import dotenv from 'dotenv';
dotenv.config();

const openai = new OpenAI({
  apiKey: process.env.OPENAI_API_KEY
});

app.post('/generate', async (req, res) => {
  const { prompt } = req.body;
  
  try {
    const response = await openai.images.generate({
      model: "dall-e-3",
      prompt: prompt,
      n: 1,
      size: "1024x1024",
      style: "vivid"
    });
    
    const imageUrl = response.data[0].url;
    res.json({ imageUrl });
    
  } catch (error) {
    if (error.code === 'content_policy_violation') {
      res.status(400).json({ error: "Prompt rejected by safety filter. Try rephrasing." });
    } else if (error.status === 429) {
      res.status(429).json({ error: "Rate limited. Wait a moment and try again." });
    } else {
      res.status(500).json({ error: "Generation failed: " + error.message });
    }
  }
});

What this code does in plain English:

  • Your browser sends a prompt to your server at /generate
  • Your server passes that prompt to OpenAI's API
  • OpenAI generates the image and returns a temporary URL
  • Your server sends that URL back to your browser
  • Your browser displays the image
Want cheaper images? Use Replicate instead.
Replicate hosts hundreds of open-source image models. Stable Diffusion XL costs about $0.003 per image — 13x cheaper than DALL-E. The tradeoff: slightly lower quality on photorealistic images, but often better for artistic styles. Ask AI: "Change my /generate endpoint to use Replicate's stable-diffusion-xl model instead of DALL-E."

Step 4: Handle Loading and Errors in the Frontend

The server works. Now wire up the UI so it actually calls it:

Your prompt to AI

Add JavaScript to my index.html that:
- On "Generate" click: validates the prompt isn't empty, shows loading spinner, hides previous result
- POSTs { prompt } to /generate
- On success: hides spinner, shows the image, saves prompt+url to localStorage history
- On error: hides spinner, shows an error message in red
- Disables the button while generating (prevents double-clicks)
- Updates the history thumbnails after each generation

The key pattern is the async fetch call:

async function generateImage() {
  const prompt = document.getElementById('promptInput').value.trim();
  if (!prompt) return alert('Enter a prompt first');
  
  // Show loading, hide old result
  showLoading(true);
  document.getElementById('result').classList.add('hidden');
  document.getElementById('generateBtn').disabled = true;
  
  try {
    const response = await fetch('/generate', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ prompt })
    });
    
    const data = await response.json();
    
    if (!response.ok) {
      throw new Error(data.error || 'Generation failed');
    }
    
    // Show the image
    document.getElementById('generatedImage').src = data.imageUrl;
    document.getElementById('result').classList.remove('hidden');
    
    // Save to history
    saveToHistory(prompt, data.imageUrl);
    
  } catch (error) {
    showError(error.message);
  } finally {
    showLoading(false);
    document.getElementById('generateBtn').disabled = false;
  }
}

Test it now: restart your server, open localhost:3000, type a prompt, click Generate. You should see a spinner for about 10 seconds, then an image appears. If it works — you've just built an AI-powered app.

Step 5: Save Your Generation History

Right now, every time you refresh the page your history disappears. Let's fix that:

Your prompt to AI

Add localStorage history to my image generator:
- Save each generation as { prompt, imageUrl, timestamp } to localStorage
- Keep only the last 20 entries (remove oldest when full)
- On page load, render the history thumbnails from localStorage
- Add a "Clear History" button
- Note: DALL-E URLs expire after 1 hour, so add a visual indicator when an image URL is likely expired

AI will add a saveToHistory() and loadHistory() function using localStorage.getItem and localStorage.setItem. The expiry indicator is a nice touch — it tells users why old thumbnails show broken images.

localStorage vs a real database.
localStorage stores data in the user's browser — it's private to them and disappears if they clear their browser data. For a personal tool, that's fine. If you want shared history across devices, or if you're building for multiple users, you need a real database like Supabase or PocketBase. Ask AI: "Move my history storage from localStorage to a Supabase table."

Step 6: Deploy It to Vercel

Right now your app only works on your computer. Let's put it on the internet:

Your prompt to AI

Help me deploy this Express app to Vercel. I need:
- A vercel.json config file that routes everything to my Express server
- Instructions for setting my OPENAI_API_KEY as a Vercel environment variable
- Any changes needed to package.json for Vercel deployment
The app uses Express with a single server.js entry point

After AI generates the config, the deployment steps are:

  1. Push your code to GitHub (make sure .env is in .gitignore)
  2. Go to vercel.com → New Project → Import your GitHub repo
  3. Before clicking Deploy: go to Environment Variables and add OPENAI_API_KEY
  4. Click Deploy — Vercel builds and deploys in about 60 seconds
  5. You get a URL like your-project.vercel.app — share it
Set environment variables BEFORE deploying. If you deploy first without the API key, your app will 500 on every request. Set them in Vercel's dashboard under Project Settings → Environment Variables, then redeploy.

What AI Gets Wrong

Common issues you'll hit and how to fix them:

1. The image URL is broken after an hour

DALL-E image URLs are temporary and expire after ~1 hour. If you screenshot the URL and share it later, the image will be gone. To permanently save images, ask AI: "After generating, download the image to a /public/images/ folder and serve it from there instead of using the OpenAI URL directly."

2. "Content policy violation" errors

DALL-E has strict content filters. It rejects prompts with violence, nudity, real person's names, and sometimes surprisingly innocent things. When it rejects a prompt, it doesn't always tell you why. Strategy: rephrase the prompt more neutrally, avoid proper nouns, describe composition rather than subjects. Replicate's models have less restrictive filters if you need more creative freedom.

3. The server works locally but fails on Vercel

Almost always: missing environment variable. Check Vercel's dashboard → Functions tab → look for the error logs. If you see "invalid API key" or "process.env.OPENAI_API_KEY is undefined", go to Project Settings → Environment Variables and add it. Then redeploy (Vercel doesn't auto-redeploy when you add env vars).

4. CORS errors in the browser

If you're calling the API directly from the browser (not through your server), you'll get CORS errors because OpenAI's API doesn't allow browser-side calls. The fix: always route API calls through your server. Your browser calls /generate on your server, and your server calls OpenAI. Never put your API key in frontend JavaScript.

5. Slow generation with no feedback

DALL-E 3 takes 10-15 seconds. Without a loading indicator, users will click the button repeatedly thinking it's broken. Make sure your loading state actually shows before the async call, not after. The showLoading(true) call must come before the await fetch().

Make It Your Own

Three directions to take this project further:

1. Add style presets

Add buttons for "Photorealistic", "Cartoon", "Oil Painting", "Pixel Art" that append style instructions to the prompt automatically. Ask AI: "Add a style selector to my image generator that appends style keywords to the user's prompt."

2. Multi-image grid

Generate 4 variations at once and let users pick their favorite. Ask AI: "Change my generator to produce 4 images simultaneously using Promise.all and display them in a 2x2 grid."

3. Turn it into a product

Add user accounts (so people don't use your API key) and a credit system (each user gets 10 free generations, then pays for more). Ask AI: "Add Clerk authentication and a Stripe payment system to my image generator so users pay for credits."

FAQ

How much does it cost to run an AI image generator?

DALL-E 3 costs about $0.04 per image at standard quality (1024x1024). For a personal project generating 10-20 images a day, you're looking at $1-2/month. Replicate's Stable Diffusion models run around $0.002-0.005 per image — much cheaper if you need volume. Either way, $5 in API credits will cover weeks of experimenting.

Do I need to know React to build this?

No. This tutorial uses plain HTML, CSS, and JavaScript — no frameworks. That's intentional: you'll understand exactly what every line does. If you already know React, the same API calls work in React with minor adjustments. Start simple, then port it if you want.

Can I use this commercially?

DALL-E 3 images can be used commercially under OpenAI's terms of service — you own the images you generate. Stable Diffusion (via Replicate) is open source with commercial use allowed for most models. Always check the specific model's license on Replicate's model page before building a commercial product.

What happens if the API call fails?

The most common failures are: invalid API key (check your .env file), rate limiting (you're generating too fast — add a delay), content policy violation (DALL-E rejected your prompt — rephrase it), and network timeout (try again). This tutorial includes error handling that catches all of these and shows a plain-English message.

How do I save generated images permanently?

DALL-E image URLs expire after 1 hour. To save permanently: download the image to your server using a fetch + file write, upload it to cloud storage like Cloudflare R2 or AWS S3, and store the permanent URL in your database. This tutorial covers a simple localStorage approach for history — enough for a personal tool.

What to Learn Next

You've built your first AI-powered app. Here's where to go from here: