HomeBlogTutorials
📖 12 min read
A laptop screen displaying raw JSON code with base64 strings streaming out of it, transforming mid-air into a highly detailed, glowing 3D cube illustration representing a finished DALL-E image
Advertisement

Generating text from an API is easy. Generating image assets is significantly harder simply because of file size and latency. When you make a request to DALL-E 3, the payload getting returned isn't a 2kb string of text. It is a massive binary blob.

Most developers read the OpenAI documentation, copy the basic snippet, and accidentally build a devastating architectural flaw: they ask the API for a temp URL, pass that URL to the frontend, and realize 2 hours later that the URL expired and their web app is displaying broken images.

In this tutorial, we will build the correct, production-ready backend pipeline using Python. We will request a b64_json image, decode it locally, and prepare it for permanent storage (like AWS S3) before we ever send it to the frontend.

The Production Pattern

The flow must look like this to prevent data loss:

  1. User clicks "Generate" on your frontend UI.
  2. Frontend sends the text prompt to your Python backend.
  3. Your Backend modifies the prompt (using hidden system constraints so the user can't generate inappropriate content) and sends it to OpenAI.
  4. OpenAI returns a raw base64 string.
  5. Your Backend decodes the string, saves the PNG file to your Cloud Storage (S3, Cloudflare R2), and gets a permanent, public URL.
  6. Your Backend returns that permanent URL to the User frontend.
Advertisement

Step 1: The Basic DALL-E 3 Call

Assume you have your environment set up with openai and python-dotenv installed (as covered in our ChatGPT API Guide). DALL-E 3 costs $0.04 per HD image. Do not put this function on an open internet port without user authentication, or a bot will bankrupt you.

import os
from dotenv import load_dotenv
from openai import OpenAI

load_dotenv()
client = OpenAI()

def generate_image_basic(prompt):
    print("Requesting DALL-E 3...")
    response = client.images.generate(
        model="dall-e-3",
        prompt=prompt,
        size="1024x1024",
        quality="standard",
        n=1,
    )
    
    # By default, OpenAI returns a URL that expires in roughly 1 hour.
    image_url = response.data[0].url
    return image_url

if __name__ == "__main__":
    url = generate_image_basic("A futuristic neon coffee mug, unreal engine 5 render.")
    print("EXPIRING URL:", url)

If you use that URL in an <img src=""> tag, your app will look great today, and broken tomorrow. We need to fix this.

Advertisement

Step 2: Requesting Base64 Data

We need to explicitly tell the API to return the raw binary data of the image instead of a link. We change the response_format parameter.

import base64

def generate_image_base64(prompt):
    print("Requesting Base64 from DALL-E 3...")
    
    response = client.images.generate(
        model="dall-e-3",
        prompt=prompt,
        size="1024x1024",
        quality="standard",
        response_format="b64_json", # Crucial change
        n=1,
    )
    
    # We now have a massive string of encoded binary data
    b64_data = response.data[0].b64_json
    return b64_data

Step 3: Decoding and Saving Locally

Once your backend receives the b64_data, it needs to be decoded into bytes, and then written into a file stream. Here is the utility function to do that and save it to your server's local disk.

import base64
import uuid

def save_b64_to_disk(b64_data, output_folder="generated_images"):
    # Ensure folder exists
    os.makedirs(output_folder, exist_ok=True)
    
    # Generate a random filename so we don't overwrite previous images
    filename = f"dalle_{uuid.uuid4().hex[:8]}.png"
    filepath = os.path.join(output_folder, filename)
    
    # Decode the base64 string
    image_bytes = base64.b64decode(b64_data)
    
    # Write the bytes to a PNG file
    with open(filepath, "wb") as f:
        f.write(image_bytes)
        
    print(f"Image saved permanently to: {filepath}")
    return filepath

Step 4: The Production Architecture (Putting it together)

Now, let's wire it up as if we were fulfilling an API request from an Express or React app. We intercept the prompt, modify it for safety, get the Base64, save it, and simulate returning the path to the user.

def handle_frontend_request(user_prompt):
    # 1. Enforce business logic (Prompt Injection Defense)
    safe_prompt = f"A cute 3D Pixar style illustration of: {user_prompt}. No text. Safe for work."
    
    # 2. Call OpenAI
    try:
        b64_raw = generate_image_base64(safe_prompt)
    except Exception as e:
        return {"status": "error", "message": f"API Failed: {e}"}
        
    # 3. Save the image to permanent storage
    # In a real app, you would replace this with a boto3 call to AWS S3.
    local_path = save_b64_to_disk(b64_raw)
    
    # 4. Return the permanent path to the frontend
    return {
        "status": "success",
        "url": f"https://my_app.com/{local_path}" # Example domain
    }

if __name__ == "__main__":
    result = handle_frontend_request("A golden retriever holding a coffee cup")
    print(result)

Conclusion

Treat LLM image generation APIs exactly how you treat user uploads. The image does not exist until it is safely written to a hard drive you control. By handling the Base64 decoding directly in your backend, you eliminate broken image links, prevent users from seeing the raw OpenAI URLs, and secure the intellectual property of your generations.

Advertisement