Web Development (Flask, Django, FastAPI)
The three major Python web frameworks — Flask (minimal), Django (batteries-included), and FastAPI (modern, API-focused) — routing, templates, ORMs, admin panels, Pydantic validation, and when to choose which.
Until now, your programs have run entirely on your own computer. Web development is about building applications that run on a server and are accessed by many users through a browser or another program — websites, admin dashboards, and APIs that power mobile apps.
Python has three major web frameworks, each with a different philosophy. This file covers all three, and — importantly — explains when to choose which.
1. What is a Web Framework?
What is it?
A web framework is a toolkit that handles the repetitive, common parts of building a web application — routing URLs to code, handling requests, talking to databases — so you focus on your application's actual logic instead of rebuilding the basics every time.
Why do we use it?
Building a web server completely from scratch (handling raw HTTP, parsing URLs, managing sessions) would take enormous effort. A framework handles all of that, letting you write a working web application in a fraction of the time.
The Three Major Python Web Frameworks
| Framework | Philosophy |
|---|---|
| Flask | Minimal and flexible — gives you the basics, you choose the rest |
| Django | "Batteries-included" — comes with almost everything built in (admin panel, ORM, auth) |
| FastAPI | Modern, fast, built specifically for APIs, with automatic documentation |
2. Flask
What is it?
Flask is a lightweight, minimalist Python web framework — easy to learn, giving you just the essentials, while letting you add exactly what you need.
Installation
bashpip install flask
Basic Application
File: `app.py`
pythonfrom flask import Flask app = Flask(__name__) @app.route("/") def home(): return "Hello, Flask!" if __name__ == "__main__": app.run(debug=True)
Run it:
bashpython app.py
Visiting http://127.0.0.1:5000/ in a browser shows "Hello, Flask!"
Explanation of the Code
Flask(__name__)creates the application object.@app.route("/")is a decorator (from the Intermediate Python file) that connects the URL/to thehome()function — this is called routing.app.run(debug=True)starts a local development server;debug=Truegives helpful error pages and auto-reloads on code changes (never usedebug=Truein a real production deployment).
Routing with Parameters
python@app.route("/student/<name>") def student_profile(name): return f"Profile page for {name}"
Visiting /student/Aditi displays "Profile page for Aditi" — <name> captures whatever appears in that part of the URL and passes it to the function.
Handling GET and POST Requests
pythonfrom flask import request @app.route("/greet", methods=["GET", "POST"]) def greet(): if request.method == "POST": name = request.form.get("name") return f"Hello, {name}! (submitted via POST)" return "Send a POST request with a 'name' field"
Explanation: request.form accesses data submitted through an HTML form. request.args would be used instead for data sent as URL query parameters (e.g., /greet?name=Aditi).
Templates (Jinja2)
Flask uses the Jinja2 templating engine to generate HTML dynamically, keeping HTML separate from Python logic.
File: `templates/profile.html`
html<!DOCTYPE html> <html> <body> <h1>Hello, {{ name }}!</h1> <p>You are {{ age }} years old.</p> </body> </html>
File: `app.py`
pythonfrom flask import render_template @app.route("/profile/<name>/<int:age>") def profile(name, age): return render_template("profile.html", name=name, age=age)
Explanation: {{ name }} and {{ age }} inside the HTML are placeholders, automatically filled in with the actual values passed from render_template().
Database Integration (SQLite Example)
pythonimport sqlite3 from flask import g def get_db(): if "db" not in g: g.db = sqlite3.connect("blog.db") return g.db @app.route("/posts") def list_posts(): db = get_db() posts = db.execute("SELECT title FROM posts").fetchall() return str(posts)
A Simple REST API Endpoint
pythonfrom flask import jsonify @app.route("/api/students") def api_students(): students = [ {"name": "Aditi", "course": "CS"}, {"name": "Rohan", "course": "AI"} ] return jsonify(students)
Explanation: jsonify() converts a Python list/dictionary into a proper JSON HTTP response, exactly what a REST API consumer (like a mobile app) would expect.
Real-World Example
Flask is popular for small-to-medium web apps, internal tools, prototypes, and microservices — where flexibility matters more than built-in structure.
Common Mistakes
- Leaving
debug=Truein a production deployment — this exposes sensitive internal details if something crashes. - Forgetting that Flask alone doesn't include a built-in ORM, admin panel, or authentication system — you add these yourself (often via extensions like Flask-SQLAlchemy or Flask-Login).
3. Django
What is it?
Django is a full-featured, "batteries-included" web framework — it comes with a built-in ORM, admin panel, authentication system, and much more, following a strong "convention over configuration" philosophy.
Installation and Project Setup
bashpip install django django-admin startproject myproject cd myproject python manage.py startapp blog
This creates a full project structure automatically, including settings, URL routing, and a database configuration (SQLite by default).
Project Structure Overview
myproject/
manage.py
myproject/
settings.py
urls.py
blog/
models.py
views.py
urls.py
admin.pyBasic View and URL Routing
File: `blog/views.py`
pythonfrom django.http import HttpResponse def home(request): return HttpResponse("Hello, Django!")
File: `blog/urls.py`
pythonfrom django.urls import path from . import views urlpatterns = [ path("", views.home, name="home"), ]
File: `myproject/urls.py` (connects the app's URLs to the project)
pythonfrom django.urls import include, path urlpatterns = [ path("", include("blog.urls")), ]
Models — Django's Built-in ORM
File: `blog/models.py`
pythonfrom django.db import models class Post(models.Model): title = models.CharField(max_length=200) content = models.TextField() created_at = models.DateTimeField(auto_now_add=True) def __str__(self): return self.title
Apply the model to the database:
bashpython manage.py makemigrations python manage.py migrate
Explanation: makemigrations generates the instructions for the database changes needed; migrate actually applies them. This "migrations" system tracks database schema changes over time — a core Django concept.
The Django Admin Panel
bashpython manage.py createsuperuser
File: `blog/admin.py`
pythonfrom django.contrib import admin from .models import Post admin.site.register(Post)
Explanation: With just these few lines, Django automatically generates a full, working admin web interface to create, view, edit, and delete Post records — one of Django's most famous, time-saving features.
Templates
Django also uses its own templating language, syntactically very similar to Jinja2:
html<h1>{{ post.title }}</h1> <p>{{ post.content }}</p>
Built-in Authentication
pythonfrom django.contrib.auth.decorators import login_required @login_required def dashboard(request): return HttpResponse(f"Welcome, {request.user.username}!")
Explanation: Django ships with a complete user authentication system (login, logout, password management) — @login_required automatically redirects unauthenticated users to a login page.
REST APIs with Django
Django itself is focused on full websites; for REST APIs, the standard companion library is Django REST Framework (DRF):
bashpip install djangorestframework
pythonfrom rest_framework.decorators import api_view from rest_framework.response import Response @api_view(["GET"]) def api_posts(request): posts = Post.objects.all().values("title", "content") return Response(list(posts))
Real-World Example
Django is a strong choice for large, structured applications with complex data models — content management systems, e-commerce platforms, and admin-heavy internal tools, thanks to its built-in admin panel and ORM.
Common Mistakes
- Forgetting to run
makemigrationsandmigrateafter changing a model — the database won't reflect the new structure otherwise. - Underestimating Django's learning curve — its "batteries-included" nature means more concepts (settings, apps, migrations, the ORM) to learn upfront compared to Flask.
4. FastAPI
What is it?
FastAPI is a modern, high-performance Python web framework, purpose-built for APIs — known for being extremely fast, having built-in data validation, and automatically generating interactive API documentation.
Installation
bashpip install fastapi uvicorn
(`uvicorn` is the server that actually runs a FastAPI application.)
Basic Application
File: `main.py`
pythonfrom fastapi import FastAPI app = FastAPI() @app.get("/") def home(): return {"message": "Hello, FastAPI!"}
Run it:
bashuvicorn main:app --reload
Visiting http://127.0.0.1:8000/docs automatically shows a full, interactive API documentation page — generated entirely on its own, with zero extra effort. This is one of FastAPI's standout features.
Routing with Path and Query Parameters
python@app.get("/students/{student_id}") def get_student(student_id: int, detailed: bool = False): if detailed: return {"id": student_id, "name": "Aditi", "course": "CS", "age": 21} return {"id": student_id, "name": "Aditi"}
Explanation: student_id: int is a type hint — FastAPI uses this to automatically validate and convert the URL value, and to reject invalid input (like text where a number was expected) with a clear error, automatically.
Request Bodies with Pydantic
FastAPI uses Pydantic models to define and automatically validate the structure of incoming JSON data.
pythonfrom pydantic import BaseModel class Student(BaseModel): name: str age: int course: str @app.post("/students") def create_student(student: Student): return {"message": f"Student {student.name} created successfully"}
Explanation: If a client sends JSON missing a required field, or with the wrong data type (like a string where age should be a number), FastAPI automatically rejects the request with a clear validation error — you don't need to write any manual validation code yourself.
Database Integration
FastAPI is commonly paired with SQLAlchemy (covered in the Database Programming file) for database access, following the same ORM patterns already covered there.
Authentication (Brief Overview)
pythonfrom fastapi import Depends, HTTPException from fastapi.security import OAuth2PasswordBearer oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token") @app.get("/protected") def protected_route(token: str = Depends(oauth2_scheme)): if token != "valid-token": raise HTTPException(status_code=401, detail="Invalid token") return {"message": "You accessed a protected route"}
Explanation: FastAPI's Depends() system (called "Dependency Injection") is commonly used to handle authentication checks cleanly and reusably across many routes — full authentication systems (like JWT-based login) build on this same pattern.
Real-World Example
FastAPI is increasingly the top choice for building APIs that power mobile apps, microservices, and machine learning model deployments — thanks to its speed and automatic validation/documentation.
Common Mistakes
- Forgetting to install and use
uvicornto actually run the application (FastAPI itself doesn't include a built-in dev server the way Flask does). - Not using Pydantic models for request bodies, missing out on FastAPI's automatic validation — one of its biggest advantages.
Comparison Table — Flask vs Django vs FastAPI
| Flask | Django | FastAPI | |
|---|---|---|---|
| Philosophy | Minimal, flexible | Batteries-included | Modern, API-focused |
| Best for | Small apps, prototypes, microservices | Large structured apps, content-heavy sites | APIs, microservices, ML model serving |
| Built-in ORM | No (add one yourself) | Yes | No (typically paired with SQLAlchemy) |
| Built-in Admin Panel | No | Yes | No |
| Built-in Auth | No (via extensions) | Yes | Via Dependency Injection, manual setup |
| Automatic API Docs | No | Via DRF add-on | Yes (built-in) |
| Learning Curve | Low | Higher (more concepts) | Moderate |
| Performance | Good | Good | Excellent (built on async foundations) |
When to Choose Which
- Choose Flask when you want full control, a simple app, or you're just learning web development fundamentals.
- Choose Django when building a large, structured application — especially one needing an admin panel, robust user management, or a content-heavy site.
- Choose FastAPI when building a modern API — especially one needing high performance, automatic validation, or serving machine learning models.
Common Beginner Mistakes — Summary for This Section
- Leaving Flask's
debug=Trueon in production. - Forgetting Django's
makemigrations/migratesteps after changing models. - Not running FastAPI with
uvicorn. - Mixing up which framework has which built-in features (all three are commonly confused by beginners).
Cheat Sheet — Web Development
python# Flask from flask import Flask, request, jsonify, render_template app = Flask(__name__) @app.route("/path", methods=["GET", "POST"]) def view(): return "response" app.run(debug=True) # Django django-admin startproject myproject python manage.py startapp blog python manage.py makemigrations python manage.py migrate python manage.py runserver # FastAPI from fastapi import FastAPI app = FastAPI() @app.get("/path") def view(): return {"key": "value"} # run with: uvicorn main:app --reload
Mini Project: Blog Application (Flask)
Objective
Build a simple web-based blog where users can view all posts and create new ones, using Flask, templates, and SQLite.
Requirements
- A homepage listing all blog posts.
- A form to create a new post.
- Data stored persistently in SQLite.
Concepts Used
Flask routing, templates (Jinja2), forms (request.form), SQLite integration.
Project Structure
blog_app/
app.py
templates/
index.html
new_post.htmlComplete Code
File: `app.py`
pythonfrom flask import Flask, render_template, request, redirect, url_for import sqlite3 app = Flask(__name__) def get_db(): conn = sqlite3.connect("blog.db") conn.execute(""" CREATE TABLE IF NOT EXISTS posts ( id INTEGER PRIMARY KEY AUTOINCREMENT, title TEXT NOT NULL, content TEXT NOT NULL ) """) return conn @app.route("/") def index(): conn = get_db() posts = conn.execute("SELECT * FROM posts ORDER BY id DESC").fetchall() conn.close() return render_template("index.html", posts=posts) @app.route("/new", methods=["GET", "POST"]) def new_post(): if request.method == "POST": title = request.form["title"] content = request.form["content"] conn = get_db() conn.execute("INSERT INTO posts (title, content) VALUES (?, ?)", (title, content)) conn.commit() conn.close() return redirect(url_for("index")) return render_template("new_post.html") if __name__ == "__main__": app.run(debug=True)
File: `templates/index.html`
html<!DOCTYPE html> <html> <body> <h1>My Blog</h1> <a href="/new">Write a new post</a> {% for post in posts %} <h2>{{ post[1] }}</h2> <p>{{ post[2] }}</p> <hr> {% endfor %} </body> </html>
File: `templates/new_post.html`
html<!DOCTYPE html> <html> <body> <h1>New Post</h1> <form method="POST"> <input type="text" name="title" placeholder="Title"><br> <textarea name="content" placeholder="Write your post..."></textarea><br> <button type="submit">Publish</button> </form> </body> </html>
Code Explanation
get_db()opens (and initializes, if needed) the SQLite database used to store posts.index()fetches all posts and passes them toindex.html, where{% for post in posts %}(Jinja2 syntax) loops through and displays each one.new_post()shows a form onGET, and onPOST, saves the submitted title and content, then redirects back to the homepage.
Sample Behavior
- Visiting
/shows all existing blog posts. - Clicking "Write a new post" goes to
/new, showing a form. - Submitting the form saves the post and redirects back to the homepage, where the new post now appears.
Possible Improvements
- Add the ability to edit and delete existing posts.
- Add user authentication so only logged-in users can create posts.
- Add categories/tags for posts.
Challenge Task
Convert this application to use Flask-SQLAlchemy (an ORM) instead of raw SQL, following the SQLAlchemy patterns from the Database Programming file.
Interview Questions
Q1. What is the main philosophical difference between Flask and Django? Answer: Flask is minimal and flexible, giving developers only the basics and letting them choose additional tools. Django is "batteries-included," providing a built-in ORM, admin panel, and authentication system out of the box.
Q2. What makes FastAPI different from Flask and Django? Answer: FastAPI is purpose-built for modern APIs, offering automatic request validation (via Pydantic), automatic interactive documentation, and high performance built on asynchronous foundations.
Q3. What does `@app.route()` do in Flask? Answer: It's a decorator that connects (routes) a specific URL to the function that should handle requests to that URL.
Q4. What are Django migrations? Answer: A system for tracking and applying changes to the database schema over time, generated with makemigrations and applied with migrate.
Q5. What is Pydantic used for in FastAPI? Answer: Defining the expected structure of request/response data as Python classes, which FastAPI then uses to automatically validate incoming data and generate documentation.
Q6. When would you choose Django over Flask? Answer: For larger, more structured applications that benefit from Django's built-in admin panel, ORM, and authentication system — especially content-heavy sites needing rapid, consistent development.
Practice Questions
Beginner
- Create a basic Flask app with two routes:
/and/about. - Create a Flask route that accepts a name from the URL and displays a personalized greeting.
- Set up a new Django project and app, and create a simple view returning "Hello, Django!"
- Create a FastAPI app with a single
GETroute returning a JSON message. - Create a FastAPI route that accepts an integer path parameter and returns it doubled.
Intermediate
- Build a Flask app with a form that accepts a name and email, and displays them back after submission.
- Create a Django model for a
Product(name, price, stock) and register it in the admin panel. - Build a FastAPI endpoint that accepts a Pydantic model representing a new user (
name,email,age) and returns a confirmation message. - Add a route in Flask that returns a list of items as JSON using
jsonify(). - Create a Django view that requires the user to be logged in using
@login_required.
Challenge
- Extend the Blog Application mini project to support editing and deleting posts.
- Build a small Django application with two models (
AuthorandBook, linked with a foreign key) and display all books with their authors on a single page. - Build a FastAPI CRUD API for a
Taskresource (create, read, update, delete), backed by an in-memory Python list (no database needed), and test it using the automatically generated/docspage.