Skip to content
C

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

FrameworkPhilosophy
FlaskMinimal and flexible — gives you the basics, you choose the rest
Django"Batteries-included" — comes with almost everything built in (admin panel, ORM, auth)
FastAPIModern, 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

bash
pip install flask

Basic Application

File: `app.py`

python
from flask import Flask app = Flask(__name__) @app.route("/") def home(): return "Hello, Flask!" if __name__ == "__main__": app.run(debug=True)

Run it:

bash
python 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 the home() function — this is called routing.
  • app.run(debug=True) starts a local development server; debug=True gives helpful error pages and auto-reloads on code changes (never use debug=True in 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

python
from 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`

python
from 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)

python
import 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

python
from 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=True in 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

bash
pip 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.py

Basic View and URL Routing

File: `blog/views.py`

python
from django.http import HttpResponse def home(request): return HttpResponse("Hello, Django!")

File: `blog/urls.py`

python
from 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)

python
from django.urls import include, path urlpatterns = [ path("", include("blog.urls")), ]

Models — Django's Built-in ORM

File: `blog/models.py`

python
from 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:

bash
python 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

bash
python manage.py createsuperuser

File: `blog/admin.py`

python
from 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

python
from 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):

bash
pip install djangorestframework
python
from 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 makemigrations and migrate after 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

bash
pip install fastapi uvicorn

(`uvicorn` is the server that actually runs a FastAPI application.)

Basic Application

File: `main.py`

python
from fastapi import FastAPI app = FastAPI() @app.get("/") def home(): return {"message": "Hello, FastAPI!"}

Run it:

bash
uvicorn 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.

python
from 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)

python
from 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 uvicorn to 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

FlaskDjangoFastAPI
PhilosophyMinimal, flexibleBatteries-includedModern, API-focused
Best forSmall apps, prototypes, microservicesLarge structured apps, content-heavy sitesAPIs, microservices, ML model serving
Built-in ORMNo (add one yourself)YesNo (typically paired with SQLAlchemy)
Built-in Admin PanelNoYesNo
Built-in AuthNo (via extensions)YesVia Dependency Injection, manual setup
Automatic API DocsNoVia DRF add-onYes (built-in)
Learning CurveLowHigher (more concepts)Moderate
PerformanceGoodGoodExcellent (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=True on in production.
  • Forgetting Django's makemigrations/migrate steps 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.html

Complete Code

File: `app.py`

python
from 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 to index.html, where {% for post in posts %} (Jinja2 syntax) loops through and displays each one.
  • new_post() shows a form on GET, and on POST, saves the submitted title and content, then redirects back to the homepage.

Sample Behavior

  1. Visiting / shows all existing blog posts.
  2. Clicking "Write a new post" goes to /new, showing a form.
  3. 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

  1. Create a basic Flask app with two routes: / and /about.
  2. Create a Flask route that accepts a name from the URL and displays a personalized greeting.
  3. Set up a new Django project and app, and create a simple view returning "Hello, Django!"
  4. Create a FastAPI app with a single GET route returning a JSON message.
  5. Create a FastAPI route that accepts an integer path parameter and returns it doubled.

Intermediate

  1. Build a Flask app with a form that accepts a name and email, and displays them back after submission.
  2. Create a Django model for a Product (name, price, stock) and register it in the admin panel.
  3. Build a FastAPI endpoint that accepts a Pydantic model representing a new user (name, email, age) and returns a confirmation message.
  4. Add a route in Flask that returns a list of items as JSON using jsonify().
  5. Create a Django view that requires the user to be logged in using @login_required.

Challenge

  1. Extend the Blog Application mini project to support editing and deleting posts.
  2. Build a small Django application with two models (Author and Book, linked with a foreign key) and display all books with their authors on a single page.
  3. Build a FastAPI CRUD API for a Task resource (create, read, update, delete), backed by an in-memory Python list (no database needed), and test it using the automatically generated /docs page.

Mock Test

  • Web Development (Flask, Django, FastAPI) - Quick Test

    10 questions covering Flask, Django, FastAPI, routing, templates, ORMs, admin panels, and Pydantic validation.

    10 questions · 10 min · Easy
    Start Mock Test