Prompt of the Day: Generate a Complete API Route with Validation
Part 2 of 30 in the Prompt of the Day series.
The Story
A few months ago, a student in one of my workshops showed me a FastAPI endpoint he'd generated with AI. It worked — the happy path was fine. Hit it with a valid email and a password, you got a token back.
Then I typed in an email 10,000 characters long.
The server froze for three seconds, then returned a 500. No validation. No size check. No sanitized error message. Just the raw Python traceback, including his database connection string, printed directly into the response body.
He'd asked the AI to "write a login endpoint." And the AI did exactly that — technically. But a real API route isn't just the happy path. It's the guards at every door.
This is the gap vibe coders fall into constantly. According to a Quartz analysis of AI-assisted development, one cybersecurity firm found that AI-assisted developers produced three to four times more code but generated 10 times more security issues — including exposed credentials and privilege escalation paths. Vague prompts produce vague safety.
The fix isn't to stop using AI. The fix is to ask better.
The Prompt
You are a senior backend engineer building a production API. Generate a complete, production-ready POST route for [describe your endpoint, e.g. "user registration" or "create a new order"].
The route must include ALL of the following:
1. **Input validation**: Validate every field in the request body.
- Required vs. optional fields
- Type checking (string, integer, email format, UUID, etc.)
- Length/range limits (e.g., username 3–30 chars, age 0–120)
- Format validation (email regex, phone number pattern, etc.)
- Reject unknown/extra fields
2. **Sanitization**: Trim whitespace from string inputs. Normalize email addresses to lowercase.
3. **Business logic validation**: Check rules that go beyond format (e.g., "email must not already exist in DB", "quantity must not exceed inventory").
4. **Error responses**: Return structured JSON errors with:
- HTTP 400 for validation failures
- HTTP 409 for conflicts (duplicate email, etc.)
- HTTP 422 for unprocessable content
- HTTP 500 for unexpected server errors
- Field-level error messages (e.g., {"field": "email", "message": "Invalid email format"})
- Never expose stack traces or internal details to the client
5. **Success response**: HTTP 201 with a well-shaped response body.
6. **Inline comments** explaining why each validation rule exists.
Framework: [specify your framework, e.g. FastAPI, Express/TypeScript, Django REST Framework, NestJS]
Database ORM: [specify, e.g. SQLAlchemy, Prisma, TypeORM, Django ORM]
Context: [paste in your data model or schema if you have one]
Why It Works
This prompt works because it forces the AI to treat the route as a system, not a script. Let's break down what each clause is doing:
"Production-ready" primes the model for real-world constraints, not toy examples. Studies on prompt effectiveness show that role-based and constraint-based prompts consistently outperform simple instruction prompts for complex code generation tasks.
Explicit validation categories prevent the AI from skipping inconvenient edge cases. According to Zuplo's API validation research, APIs with proper input validation show 24% lower average response times — and that's before you count the hours saved debugging injection attacks and malformed data.
Structured error responses are something AI almost always omits unless you ask for them directly. Specifying field-level error messages (not just "invalid input") is what separates a developer-friendly API from a frustrating black box.
"Never expose stack traces" is one line that can prevent a production incident. This is a direct instruction the model needs to receive — leave it out and you'll get helpful debug output that's also a security liability.
Framework and context fields are your injection points. The more specific you are here, the more idiomatic and immediately usable the output will be. A Pydantic model for FastAPI looks nothing like a Zod schema for a tRPC endpoint — the AI knows the difference when you tell it.
Example Output
Here's what a well-formed FastAPI route looks like when this prompt is used for a user registration endpoint:
from fastapi import APIRouter, HTTPException, status
from pydantic import BaseModel, EmailStr, Field, validator
from sqlalchemy.orm import Session
from typing import Optional
import re
router = APIRouter()
# Input model — Pydantic enforces types and runs validators automatically
class UserCreateRequest(BaseModel):
username: str = Field(..., min_length=3, max_length=30)
email: EmailStr # Built-in email format validation
password: str = Field(..., min_length=8, max_length=128)
display_name: Optional[str] = Field(None, max_length=50)
class Config:
# Reject any fields not defined in this model
extra = "forbid"
@validator("username")
def username_alphanumeric(cls, v):
# Usernames must be alphanumeric to prevent injection via username field
if not re.match(r"^[a-zA-Z0-9_]+$", v):
raise ValueError("Username must contain only letters, numbers, and underscores")
return v
@validator("email")
def normalize_email(cls, v):
# Normalize to lowercase to prevent duplicate accounts with same email
return v.lower().strip()
@validator("password")
def password_strength(cls, v):
# Require at least one digit and one letter — basic strength check
if not re.search(r"[A-Za-z]", v) or not re.search(r"[0-9]", v):
raise ValueError("Password must contain at least one letter and one number")
return v
class UserCreateResponse(BaseModel):
id: str
username: str
email: str
@router.post("/users", response_model=UserCreateResponse, status_code=status.HTTP_201_CREATED)
async def create_user(payload: UserCreateRequest, db: Session = Depends(get_db)):
# Business logic: check for duplicate email before writing to DB
existing = db.query(User).filter(User.email == payload.email).first()
if existing:
# 409 Conflict — not a validation error, but a state conflict
raise HTTPException(
status_code=status.HTTP_409_CONFLICT,
detail={"field": "email", "message": "An account with this email already exists"}
)
user = User(
username=payload.username,
email=payload.email,
password_hash=hash_password(payload.password),
display_name=payload.display_name
)
db.add(user)
db.commit()
db.refresh(user)
return UserCreateResponse(id=str(user.id), username=user.username, email=user.email)
Notice what's missing: the raw exception. FastAPI + Pydantic surface field-level validation errors automatically as 422 responses with a structured body. You never have to touch a try/except for format validation — the schema handles it.
The Anti-Prompt
Here's what most people actually type:
Write a user registration API endpoint in FastAPI
Why it fails: You'll get a working route, but it'll have no length limits, no duplicate-check logic, no field normalization, and error responses that likely expose your database layer. It's a skeleton, not a production route. The AI isn't being lazy — it's answering what you asked. "An endpoint" is a lower bar than "a production-ready endpoint with full validation."
According to API engineering best practices for 2026, every piece of incoming data — query params, headers, JSON bodies — should be treated as potentially malicious. A vague prompt produces a route that doesn't.
Variations
For a GET route with query parameters:
...validate query parameters including pagination (page: int ≥ 1, limit: int 1–100), optional filters, and sort direction. Return 400 with field-level errors for invalid params.
For a TypeScript/Express route:
Use Zod for schema validation. Parse and validate the request body before it reaches any business logic. Return errors in the format { errors: [{ field, message }] }.
For a NestJS endpoint:
Use class-validator DTOs with transformation pipes. Include a global exception filter that maps ValidationError arrays to structured 400 responses.
For stricter security requirements:
Add: rate-limit headers in the response, log all validation failures (with request ID, not payload), and return a consistent error envelope regardless of error type.
Today's Checklist
- Pick one existing API route in your project and paste it into your AI tool with the prompt: "Review this route. What input validation is missing?"
- Identify the top three fields that could accept malicious input — email, username, free-text fields are usual suspects.
- Re-generate (or refactor) that route using the full prompt above.
- Verify the AI included: type checks, length limits, a duplicate/conflict check, and structured error responses.
- Test with bad input: empty string, 10,000-character string, SQL injection string (
' OR 1=1 --), extra unknown fields.
Ask The Guild
This week's community question:
What's the worst validation bug you've shipped — or caught before shipping? Did AI help create it, or help you find it? Share your story (and the prompt fix) in the Guild Discord under #prompt-of-the-day. Best war story gets featured in the weekly recap.