Alright, here's a disclaimer... By the end of this article, you'll probably be in love with FastAPI. Now, this isn't sponsored (actually, I wish it were), but I wrote this because I recently used FastAPI for a project and loved every bit of it. I also found out that there weren't many engaging resources available for FastAPI.
They say project-based learning is the best. So for this article, we'll do something fun while still learning about APIs.
Introduction: Boring LinkedIn Bios
I'm a LinkedIn fan, but most bios sound corporate and sometimes like lies.
"Passionate and results-oriented professional leveraging synergies in cross-functional teams…"
So, what if we built an API that turns your name, role, and tech stack into something funny, and maybe a little savage?
Welcome to RoastBio, a FastAPI-powered project that helps us learn FastAPI while roasting boring bios.
By the end of this article, you'll know:
- Why FastAPI feels different from Flask and Django
- How type hints, Pydantic, and async make your life easier
- How to build a real API that serves spicy LinkedIn roasts — and a Gemini AI Integration
FastAPI vs Flask vs Django
Flask:
"I'm simple. You bring the tools; I'll bring the routes."
Django:
"I built a city for you: ORM, templates, and 100 rules."
FastAPI:
"I validate your data, document your API, and go async — all in plain Python."
Here's the difference:
- Flask: It's minimal and great if you just want to set things up quickly, but you hand-roll validation, serialization, and docs
- Django: It's great for full-stack apps and gives many features, but heavy if you just want an API. No beef with Django
- FastAPI: API-first, powered by ASGI (Asynchronous Server Gateway Interface). Type hints aren't optional here because they're the backbone of data validation, auto-docs, and clean code. I'm getting the hang of it.
Step 1: Project Setup
Create your folder:
mkdir linkedin-roastbio
cd linkedin-roastbio
Set up a virtual environment:
python -m venv venv
source venv/bin/activate # Windows: venv\Scripts\activate.bat
Install dependencies:
pip install fastapi uvicorn httpx python-dotenv
Note:
"Yes, you actually need a virtual environment. You'll have issues deploying the project because package conflicts are much worse than you think."
Step 2: Your First FastAPI App
Create main.py
:
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
async def read_root():
return {"message": "Welcome to RoastBio — where LinkedIn bios get spicy!"}
Run:
uvicorn main:app --reload
Open http://127.0.0.1:8000/docs — and boom: interactive Swagger docs for your API!
Behind the scenes:
- ASGI (via Uvicorn) handles concurrent requests
-
async def
: Non-blocking I/O. While your API waits (for a DB or AI response), it can serve other requests - Docs auto-generated using type hints and Pydantic models
But wait... Why do Type Hints Matter?
FastAPI leans on Python's type hints — those str
, int
, List[str]
annotations you might have ignored.
Example:
async def greet_user(name: str) -> str:
return f"Hello, {name}!"
-
At runtime: FastAPI checks that
name
is astr
- Docs: Swagger knows what to expect and builds clear API docs
- Editor: Autocomplete and type checking get smarter
Think of type hints as contracts:
"Dear API, I promise to send you a str. If I lie, you throw a 422 and roast me."
Step 3: Validating Data with Pydantic
Let's validate incoming bio data with Pydantic. Create schemas.py
:
from pydantic import BaseModel, Field, validator
from typing import List, Optional
class BioRequest(BaseModel):
name: str = Field(..., min_length=2, description="Your full name")
role: str = Field(..., min_length=2, description="Your professional title")
tech_stack: Optional[List[str]] = Field(None, description="Technologies you work with")
@validator("name")
def validate_name(cls, value):
if value.lower() in ["user", "admin", "anonymous"]:
raise ValueError("Pick a name that's not as boring as 'Admin'.")
return value.title()
class BioResponse(BaseModel):
bio: str
roast_level: str = "standard"
What's happening in the code?
-
...
means that the field is required and is not optional -
BaseModel
: Automatically validates and serializes (converting JSON to Python and vice-versa) your data, preparing it for the API and documentation. The validation prevents errors and handles error management -
Field
: Adds rules (min_length
) and docs. If this were Django, what would it be called? - Validators: Custom rules that run before your endpoint logic
Bad data? FastAPI replies with:
{
"detail": [
{"loc": ["body", "name"], "msg": "Pick a name that's not as boring as 'Admin'."}
]
}
"Yes, even your name can get rejected. Deal with it."
Step 4: The Roast Engine
Create generator_logic.py
:
import random
from typing import Optional, List
def generate_bio(name: str, role: str, tech_stack: Optional[List[str]] = None) -> str:
easter_eggs = {
"Null": "Name is 'Null'? Did your parents forget to call the constructor?",
"Test User": "Living proof that QA never ends.",
"root": "All hail the sysadmin overlord. May sudo be with you."
}
if name in easter_eggs:
return f"{name} — {role}. {easter_eggs[name]}"
roasts = [
"ships features faster than you can say 'merge conflict'",
"believes Docker errors are modern riddles",
"debugs Kubernetes clusters like a digital exorcist"
]
tech_roasts = {
"Python": "Indentation errors build character.",
"JavaScript": "May your console.logs be ever in your favor.",
"FastAPI": "Built APIs so fast even requests can't keep up."
}
tech_comment = " ".join(
tech_roasts.get(tech, f"Probably good at {tech}… or at least Googling it.")
for tech in tech_stack or []
)
return f"{name} — {role}. {random.choice(roasts)}. {tech_comment}".strip()
Step 5: Building the Endpoint
Update main.py
:
from fastapi import FastAPI, Query
from schemas import BioRequest, BioResponse
from generator_logic import generate_bio
app = FastAPI(title="RoastBio API", description="Roast your LinkedIn bio with FastAPI")
@app.post("/generate-bio", response_model=BioResponse)
async def create_bio(request: BioRequest, premium: bool = Query(False)):
bio = generate_bio(request.name, request.role, request.tech_stack)
return BioResponse(bio=bio)
Test it using Swagger Docs:
Request:
{
"name": "Ada Lovelace",
"role": "Backend Developer",
"tech_stack": ["Python", "FastAPI"]
}
Response:
{
"bio": "Ada Lovelace — Backend Developer. ships features faster than you can say 'merge conflict'. Indentation errors build character. Built APIs so fast even requests can't keep up.",
"roast_level": "standard"
}
Step 6: How about we add AI (just for AI-trend's sake)
For AI-powered roasts, create ai_roaster.py
. First, visit the Gemini AI Studio and obtain your API key. Create a .env
file and store your secret key:
.env file:
GEMINI_API_KEY=your_actual_api_key_here
ai_roaster.py:
import os
import httpx
from typing import Optional, List
from dotenv import load_dotenv
load_dotenv()
GEMINI_API_KEY = os.getenv("GEMINI_API_KEY")
async def generate_ai_roast(name: str, role: str, tech_stack: Optional[List[str]] = None) -> str:
if not GEMINI_API_KEY:
return "AI roast unavailable — you're safe for now."
tech_list = ', '.join(tech_stack) if tech_stack else 'None'
prompt = (
f"Write a witty, roasty LinkedIn bio for {name}, a {role}. "
f"Tech stack: {tech_list}. "
"Make it clever, professional, and full of inside tech jokes. Keep it under 2 sentences."
)
try:
async with httpx.AsyncClient(timeout=30.0) as client:
response = await client.post(
"https://generativelanguage.googleapis.com/v1beta/models/gemini-pro:generateContent",
headers={
"Content-Type": "application/json",
"x-goog-api-key": GEMINI_API_KEY
},
json={
"contents": [{"parts": [{"text": prompt}]}],
"generationConfig": {
"temperature": 0.9,
"maxOutputTokens": 150
}
}
)
if response.status_code == 200:
result = response.json()
return result["candidates"][0]["content"]["parts"][0]["text"].strip()
else:
return "AI roast generator is having a coffee break ☕"
except Exception:
return "AI roast temporarily unavailable — even robots need debugging sometimes! 🤖"
Update main.py:
from ai_roaster import generate_ai_roast
@app.post("/generate-bio", response_model=BioResponse)
async def create_bio(request: BioRequest, premium: bool = Query(False)):
if premium:
roast = await generate_ai_roast(request.name, request.role, request.tech_stack)
return BioResponse(bio=roast, roast_level="premium")
bio = generate_bio(request.name, request.role, request.tech_stack)
return BioResponse(bio=bio)
Why async?
- API calls to Gemini are I/O-bound
-
async
lets FastAPI handle other requests while waiting for Gemini's reply
Example request:
POST /generate-bio?premium=true
AI response:
{
"bio": "Grace Hopper — Software Engineer. Debugging Kubernetes is like pulling endless rabbits out of YAML hats. Python whisperer and breaker of production at 3 AM.",
"roast_level": "premium"
}
FastAPI Request Lifecycle: What Happens Behind the Scenes
Here's what happens when someone hits your /generate-bio
endpoint:
- Uvicorn (ASGI server) receives the request
-
Routing matches
/generate-bio
→create_bio
-
Dependency injection handles query params (
premium
) -
Pydantic validation parses JSON →
BioRequest
. Invalid? Auto 422 -
Business logic runs:
generate_bio()
orgenerate_ai_roast()
-
Serialization:
BioResponse
ensures clean JSON output
What We Built
Hmm, I have more ideas... Maybe deploy and connect with a frontend. But let's wrap up here, save some brain power.
In this tutorial we built:
- A FastAPI app that roasts LinkedIn bios
- Pydantic validation that refuses boring inputs
- AI-powered roast mode for premium sass
- Swagger docs that make testing fun
Conclusion
Thanks for staying to the end. Please don't end your journey here; go crazy about APIs and be creative with them. I'm Wisdom, and I intend to become an expert in FastAPI.
FastAPI isn't just another web framework — it's a paradigm shift toward modern, type-safe, self-documenting APIs. The skills you've learned here apply to any API project, from simple microservices to complex enterprise applications.
Ready to dive deeper? You can read the FastAPI documentation here — it's some of the best technical documentation you'll ever read.
If this article helped you fall in love with FastAPI, give it a ❤️ and share it with fellow developers who are tired of boring API tutorials!
Top comments (2)
I'm not a developer but this was an interesting read. Thank you for making it fun and relatable to read
Thank you very much Princess. Promise to write more