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.
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.
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 --versionin 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:
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
Step 2: Build the UI
Now let's build the frontend. Give AI this prompt:
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:
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
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:
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:
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 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:
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:
- Push your code to GitHub (make sure .env is in .gitignore)
- Go to vercel.com → New Project → Import your GitHub repo
- Before clicking Deploy: go to Environment Variables and add
OPENAI_API_KEY - Click Deploy — Vercel builds and deploys in about 60 seconds
- You get a URL like
your-project.vercel.app— share it
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: