Building a Image Generator with Stable Diffusion, FastAPI, and Modern Web Tech

Shelwyn Corte
AI Mind
Published in
7 min readJan 31, 2025

Creating AI-powered image generation applications has never been more accessible. In this tutorial, we’ll build a stylish, cyberpunk-themed web application that generates images using Stable Diffusion through Hugging Face’s Inference API. We’ll combine FastAPI for the backend, modern web technologies for the frontend, and add some cool animations to create an engaging user experience.

Project Overview

GitHub Repository: https://github.com/shelwyn/image-generation-stable-diffusion

Application features:

  • Image generation using Stable Diffusion (runwayml/stable-diffusion-v1–5)
  • Real-time chat-like interface with animated typing indicators
  • Cyberpunk-themed UI with neon effects
  • Download capability for generated images
  • Responsive design that works on all devices
  • FastAPI backend for efficient image generation

Prerequisites

Before we begin, make sure you have:

  • Python 3.8 or higher installed
  • A Hugging Face account and API token
  • Basic knowledge of Python, JavaScript, and web development
  • pip for installing Python packages

Setting Up the Environment

First, let’s create a new project directory and set up our environment:

mkdir cyberpunk-image-generator
cd cyberpunk-image-generator
python -m venv venv
source venv/bin/activate # On Windows, use: venv\Scripts\activate
pip install fastapi uvicorn python-dotenv huggingface-hub Pillow

Create a .env file in your project root:

HF_TOKEN=your_huggingface_token_here

Building the Backend

Our FastAPI backend handles image generation requests and serves the generated images. It’s designed to be efficient and scalable. Here’s how we structure it:

from fastapi import FastAPI, HTTPException
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import FileResponse
from pydantic import BaseModel
from huggingface_hub import InferenceClient
import os
from dotenv import load_dotenv
import uuid
from pathlib import Path

# Load environment variables
load_dotenv()

app = FastAPI()

# Configure CORS
app.add_middleware(
CORSMiddleware,
allow_origins=["*"], # In production, replace with your frontend URL
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)

# Create images directory if it doesn't exist
IMAGES_DIR = Path("generated_images")
IMAGES_DIR.mkdir(exist_ok=True)

# Initialize the HuggingFace client
client = InferenceClient(
model="runwayml/stable-diffusion-v1-5",
token=os.getenv("HF_TOKEN")
)

class ImagePrompt(BaseModel):
prompt: str

@app.post("/generate-image")
async def generate_image(prompt_data: ImagePrompt):
try:
# Generate unique filename
filename = f"{uuid.uuid4()}.png"
filepath = IMAGES_DIR / filename

# Generate image
image = client.text_to_image(prompt_data.prompt)

# Save image
image.save(filepath)

return {"filename": filename}

except Exception as e:
raise HTTPException(status_code=500, detail=str(e))

@app.get("/images/{filename}")
async def get_image(filename: str):
filepath = IMAGES_DIR / filename
if not filepath.exists():
raise HTTPException(status_code=404, detail="Image not found")
return FileResponse(filepath)

Creating the Frontend

The frontend is where our application really shines. We’ve created a cyberpunk-themed interface that makes image generation feel like chatting with an AI from the future.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Stable-diffusion Image Generator</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/animejs/3.2.1/anime.min.js"></script>
<style>
:root {
--neon-text-color: #f40;
--neon-border-color: #08f;
}

body {
margin: 0;
padding: 20px;
min-height: 100vh;
background: #000;
font-family: 'Courier New', monospace;
color: #fff;
background-image:
linear-gradient(45deg, #000 25%, transparent 25%),
linear-gradient(-45deg, #000 25%, transparent 25%),
linear-gradient(45deg, transparent 75%, #000 75%),
linear-gradient(-45deg, transparent 75%, #000 75%);
background-size: 20px 20px;
background-color: #0a0a2a;
}

.container {
max-width: 800px;
margin: 0 auto;
padding: 20px;
background: rgba(0, 0, 0, 0.8);
border: 2px solid var(--neon-border-color);
box-shadow: 0 0 10px var(--neon-border-color);
}

h1 {
text-align: center;
color: var(--neon-text-color);
text-shadow: 0 0 10px var(--neon-text-color);
margin-bottom: 30px;
}

.chat-container {
height: 700px;
overflow-y: auto;
margin-bottom: 20px;
padding: 20px;
border: 1px solid var(--neon-border-color);
background: rgba(0, 0, 0, 0.5);
}

.message {
margin-bottom: 15px;
padding: 10px;
border-radius: 5px;
max-width: 80%;
opacity: 0;
transform: translateY(20px);
}

.user-message {
background: rgba(244, 0, 0, 0.2);
margin-left: auto;
border: 1px solid var(--neon-text-color);
}

.bot-message {
background: rgba(0, 136, 255, 0.2);
margin-right: auto;
border: 1px solid var(--neon-border-color);
}

.input-container {
display: flex;
gap: 10px;
}

input[type="text"] {
flex: 1;
padding: 10px;
background: rgba(0, 0, 0, 0.7);
border: 1px solid var(--neon-border-color);
color: #fff;
font-family: 'Courier New', monospace;
}

button {
padding: 10px 20px;
background: transparent;
border: 1px solid var(--neon-text-color);
color: var(--neon-text-color);
cursor: pointer;
font-family: 'Courier New', monospace;
transition: all 0.3s ease;
}

button:hover {
background: var(--neon-text-color);
color: #000;
box-shadow: 0 0 10px var(--neon-text-color);
}

.typing-message {
background: rgba(0, 136, 255, 0.2);
margin-right: auto;
border: 1px solid var(--neon-border-color);
display: flex;
align-items: center;
gap: 10px;
opacity: 0;
transform: translateY(20px);
}

.dot {
display: inline-block;
width: 8px;
height: 8px;
margin-right: 3px;
background: var(--neon-border-color);
border-radius: 50%;
animation: pulse 1.5s infinite;
}

.generated-image {
max-width: 100%;
margin-top: 10px;
border: 2px solid var(--neon-border-color);
}

.download-btn {
display: block;
margin: 10px auto;
}

@keyframes pulse {
0%, 100% { opacity: 0.4; }
50% { opacity: 1; }
}

@media (max-width: 600px) {
.container {
padding: 10px;
}
}
</style>
</head>
<body>
<div class="container">
<h1>Stable-diffusion Image Generator</h1>
<div class="chat-container" id="chatContainer">
<div class="message bot-message" style="opacity: 1; transform: none;">
Describe the image you want to generate using runwayml/stable-diffusion-v1-5.
</div>
</div>
<div class="input-container">
<input type="text" id="userInput" placeholder="Describe the image you want to generate...">
<button onclick="sendMessage()">Generate</button>
</div>
</div>

<script>
const API_URL = 'http://localhost:8000';
let isGenerating = false;

async function sendMessage() {
if (isGenerating) return;

const input = document.getElementById('userInput');
const prompt = input.value.trim();

if (!prompt) return;

isGenerating = true;

// Add user message
addMessage(prompt, 'user');
input.value = '';

// Add typing indicator with animation
const typingMessage = document.createElement('div');
typingMessage.className = 'message typing-message';
typingMessage.innerHTML = `
<div class="dot"></div>
<div class="dot"></div>
<div class="dot"></div>
<span>Generating image...</span>
`;
document.getElementById('chatContainer').appendChild(typingMessage);

// Animate typing indicator appearance
await anime({
targets: typingMessage,
opacity: [0, 1],
translateY: [20, 0],
duration: 500,
easing: 'easeOutCubic'
}).finished;

try {
// Generate image
const response = await fetch(`${API_URL}/generate-image`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ prompt }),
});

if (!response.ok) {
throw new Error('Failed to generate image');
}

const data = await response.json();

// Remove typing indicator with animation
await anime({
targets: typingMessage,
opacity: 0,
translateY: 20,
duration: 500,
easing: 'easeInCubic'
}).finished;

typingMessage.remove();

// Create image and download button elements
const imageUrl = `${API_URL}/images/${data.filename}`;
const imageHtml = `
<img src="${imageUrl}" alt="Generated image" class="generated-image">
<button onclick="downloadImage('${imageUrl}')" class="download-btn">Download Image</button>
`;

addMessage(imageHtml, 'bot');

} catch (error) {
// Remove typing indicator
typingMessage.remove();
addMessage('Sorry, there was an error generating the image. Please try again.', 'bot');
console.error('Error:', error);
}

isGenerating = false;
}

function addMessage(content, sender) {
const chatContainer = document.getElementById('chatContainer');
const messageDiv = document.createElement('div');
messageDiv.className = `message ${sender}-message`;
messageDiv.innerHTML = content;
chatContainer.appendChild(messageDiv);
chatContainer.scrollTop = chatContainer.scrollHeight;

// Animate message appearance
anime({
targets: messageDiv,
opacity: [0, 1],
translateY: [20, 0],
duration: 500,
easing: 'easeOutCubic'
});
}

async function downloadImage(url) {
try {
const response = await fetch(url);
const blob = await response.blob();
const downloadUrl = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = downloadUrl;
a.download = 'generated-image.png';
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
window.URL.revokeObjectURL(downloadUrl);
} catch (error) {
console.error('Error downloading image:', error);
}
}

// Add event listener for Enter key
document.getElementById('userInput').addEventListener('keypress', function(e) {
if (e.key === 'Enter') {
sendMessage();
}
});
</script>
</body>
</html>

Setting Up and Running the Application

After setting up your environment and installing the required packages, you’ll need to run both the backend and frontend servers.

1. Backend Setup

First, make sure you have all the required packages installed:

pip install fastapi uvicorn python-dotenv huggingface-hub Pillow python-multipart

2. Running the Backend Server

Start the FastAPI backend using uvicorn with hot reload for development:

uvicorn main:app --host 0.0.0.0 --port 8000 --reload

. Running the Frontend Server

For the frontend, we’ll use Python’s built-in HTTP server to serve our static files:

python -m http.server 3000

Now you can access the application at http://localhost:3000. The backend API will be available at http://localhost:8000.

Key Features Explained

1. Real-time Typing Indicator

One of the most engaging features is our animated typing indicator that appears while images are being generated. This creates a more interactive and dynamic user experience

2. Image Generation Flow

We’ve implemented a smooth flow for image generation:

  1. User enters prompt
  2. Animated typing indicator appears
  3. Backend processes the request
  4. Image appears with a fade-in animation
  5. Download button becomes available

3. Error Handling

Both frontend and backend implement comprehensive error handling:

  • Network errors
  • API limits
  • Invalid prompts
  • File system issues

Styling with a Cyberpunk Theme

The cyberpunk aesthetic is achieved through:

  • Neon color palette with CSS variables
  • Animated elements using anime.js
  • Grid background patterns
  • Glowing effects on interactive elements

Deployment Considerations

When deploying this application, consider:

  • Securing your Hugging Face API token
  • Implementing rate limiting
  • Setting up proper CORS configuration
  • Managing generated images storage
  • Scaling considerations

Conclusion

This project demonstrates how to combine modern web technologies with AI capabilities to create an engaging user experience. The cyberpunk theme adds a unique flavor to what could otherwise be a standard image generation interface.

Feel free to fork the repository and add your own features or modifications. If you enjoy this tutorial, don’t forget to star the repository and follow for more updates!

Resources

*If you found this article helpful, feel free to follow me for more content on AI development, web technologies, and practical coding tutorials.*

A Message from AI Mind

Thanks for being a part of our community! Before you go:

Free

Distraction-free reading. No ads.

Organize your knowledge with lists and highlights.

Tell your story. Find your audience.

Membership

Read member-only stories

Support writers you read most

Earn money for your writing

Listen to audio narrations

Read offline with the Medium app

--

--

Published in AI Mind

Learn, explore, or build the future of AI with top stories on the latest trends, tools, and technology. Share your crazy success stories or AI-fueled fails in a supportive community.

No responses yet

Write a response