A complete website in Python: frontend and backend with AI

Translated from the Spanish original. Read in Spanish

These days you can build modern websites using only Python for both the frontend and the backend, and integrate artificial intelligence to create advanced experiences. Here I show you how to do it securely, using a children’s story generator as the example.

Live Demo: https://bedtimestory.zerogap.uk/


Architecture

  • Frontend: NiceGUI for a modern web interface, with no JavaScript.
  • Backend: FastAPI for the logic.
  • AI: Azure OpenAI (using LangChain) to generate stories or any other natural-language task.

Example: an AI story generator

The following code shows how to connect the frontend and the backend, and how to protect your endpoints with validation.

Backend (FastAPI + Azure OpenAI via LangChain)

from fastapi import FastAPI, Request, HTTPException
from fastapi.responses import JSONResponse
from pydantic import BaseModel, validator
from azure.identity import DefaultAzureCredential, get_bearer_token_provider
from langchain_community.chat_models import AzureChatOpenAI
from langchain_core.messages import SystemMessage, HumanMessage
import os
import re
from dotenv import load_dotenv

app = FastAPI()

def whitelist(text, max_length=50, pattern=r"[^a-zA-Z0-9áéíóúÁÉÍÓÚüÜñÑ\s.,;:!?'\"()-]"):
    """Allow only letters, numbers, punctuation and spaces up to max_length."""
    if text is None:
        return None
    # Remove any disallowed character
    clean = re.sub(pattern, "", text)
    # Truncate and strip
    return clean.strip()[:max_length]

class StoryRequest(BaseModel):
    theme: str = None
    main_character: str = None

    @validator('theme', 'main_character')
    def validate_field(cls, v):
        if v is not None:
            # Only allow reasonable length and allowed characters
            v = whitelist(v)
            if len(v) == 0:
                raise ValueError("Field cannot be empty after sanitization.")
        return v

@app.post("/generate")
async def generate_story(request: StoryRequest, fastapi_request: Request):
    # Only allow local requests for security
    client_host = fastapi_request.client.host
    if client_host not in ("127.0.0.1", "::1", "localhost"):
        raise HTTPException(status_code=403, detail="Forbidden")

    load_dotenv()
    endpoint = "https://your-private-resource.openai.azure.com/"
    deployment = "my-private-deployment"
    api_version = "2024-12-01-preview"
    credential = DefaultAzureCredential()
    token_provider = get_bearer_token_provider(credential, "https://cognitiveservices.azure.com/.default")

    prompt = f"Write a children's story about {request.theme or 'friendship'}, starring a {request.main_character or 'dragon'}."
    system_prompt = "You are a creative children's storyteller. Only return the story."

    messages = [
        SystemMessage(content=system_prompt),
        HumanMessage(content=prompt)
    ]

    chat = AzureChatOpenAI(
        azure_endpoint=endpoint,
        api_version=api_version,
        deployment_name=deployment,
        azure_ad_token_provider=token_provider,
    )
    response = chat(messages)

    return JSONResponse({"text": response.content})

Frontend (NiceGUI)

from nicegui import ui

def generate_story():
    import httpx
    try:
        resp = httpx.post("http://localhost:8080/generate", json={})
        story = resp.json().get("text", "No story generated.")
        output_label.text = story
    except Exception as e:
        output_label.text = f"Error: {e}"

with ui.element('div').style('background-color: rgb(232 226 254);') \
          .classes('flex items-center justify-center absolute top-0 left-0 full-width full-height'):
    with ui.element('div').classes('full-width full-height flex flex-col items-center justify-center relative'):
        ui.image('assets/home.jpg').props('fit=contain').classes(
            'rounded-2xl w-full h-auto object-contain max-w-[95vw] max-h-[95vh]'
        )
        with ui.row().classes(
            'absolute bottom-0 left-[10vw] right-[10vw] px-8 flex flex-col md:flex-row flex-wrap items-center justify-between z-10 mb-[8vh]'
        ):
            ui.button(
                'SURPRISE ME!',
                on_click=generate_story
            ).classes(
                '!bg-yellow-400 !hover:bg-yellow-400 text-white rounded-full '
                'px-6 py-3 text-base font-bold flex items-center shadow-lg mr-4'
            ).props('icon=play_circle size=md')

output_label = ui.label("").classes("mt-6 text-lg")

ui.run()

Security and best practices for production

  • HTTPS: Never expose your site without HTTPS. Use Nginx as a reverse proxy and an SSL certificate (for example, Let’s Encrypt).
  • Sanitisation and validation: The example uses robust validation (length, allowed characters, required fields) to prevent injection attacks and abuse.
  • Reverse proxy (Nginx):
    Basic example:
  server {
      listen 443 ssl;
      server_name tu-dominio.com;

      ssl_certificate /etc/letsencrypt/live/tu-dominio.com/fullchain.pem;
      ssl_certificate_key /etc/letsencrypt/live/tu-dominio.com/privkey.pem;

      location / {
          proxy_pass http://127.0.0.1:8081;  # puerto NiceGUI
          proxy_set_header Host $host;
          proxy_set_header X-Real-IP $remote_addr;
          proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
          proxy_set_header X-Forwarded-Proto $scheme;
      }
  }

Maximiliano Díaz Doglia

AI Platform Engineer & Full-Stack Developer
Building Enterprise Integrations & Automations

Published in: AI