Skip to main content

Python Web Development Complete Guide - From Flask to FastAPI

· 9 min read

Python has become one of the most popular languages for web development, thanks to its simplicity, powerful frameworks, and extensive ecosystem. This guide covers everything you need to know about building web applications with Python.

Why Choose Python for Web Development?

Python offers several advantages for web development:

  • Simple and Readable: Clean syntax makes code easy to write and maintain
  • Rich Ecosystem: Extensive libraries and frameworks
  • Rapid Development: Quick prototyping and development cycles
  • Strong Community: Large, active community with excellent documentation
  • Versatile: From simple scripts to complex enterprise applications

1. Flask - Lightweight and Flexible

Flask is a micro-framework perfect for small to medium applications and learning web development concepts.

Basic Flask Application

from flask import Flask, render_template, request, jsonify

app = Flask(__name__)

@app.route('/')
def home():
return render_template('index.html')

@app.route('/api/users', methods=['GET', 'POST'])
def users():
if request.method == 'GET':
# Return list of users
return jsonify([
{'id': 1, 'name': 'John Doe', 'email': '[email protected]'},
{'id': 2, 'name': 'Jane Smith', 'email': '[email protected]'}
])

elif request.method == 'POST':
# Create new user
data = request.get_json()
new_user = {
'id': 3,
'name': data['name'],
'email': data['email']
}
return jsonify(new_user), 201

@app.route('/api/users/<int:user_id>')
def get_user(user_id):
# Get specific user
user = {'id': user_id, 'name': 'John Doe', 'email': '[email protected]'}
return jsonify(user)

if __name__ == '__main__':
app.run(debug=True)

Flask with Database Integration

from flask import Flask, request, jsonify
from flask_sqlalchemy import SQLAlchemy
from datetime import datetime

app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///users.db'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False

db = SQLAlchemy(app)

class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(80), nullable=False)
email = db.Column(db.String(120), unique=True, nullable=False)
created_at = db.Column(db.DateTime, default=datetime.utcnow)

def to_dict(self):
return {
'id': self.id,
'name': self.name,
'email': self.email,
'created_at': self.created_at.isoformat()
}

@app.route('/api/users', methods=['POST'])
def create_user():
data = request.get_json()

if not data or not data.get('name') or not data.get('email'):
return jsonify({'error': 'Name and email are required'}), 400

user = User(name=data['name'], email=data['email'])
db.session.add(user)
db.session.commit()

return jsonify(user.to_dict()), 201

@app.route('/api/users')
def get_users():
users = User.query.all()
return jsonify([user.to_dict() for user in users])

if __name__ == '__main__':
with app.app_context():
db.create_all()
app.run(debug=True)

2. FastAPI - Modern and Fast

FastAPI is a modern, fast web framework for building APIs with Python 3.7+ based on standard Python type hints.

Basic FastAPI Application

from fastapi import FastAPI, HTTPException, Depends
from pydantic import BaseModel, EmailStr
from typing import List, Optional
from datetime import datetime

app = FastAPI(
title="User Management API",
description="A simple API for managing users",
version="1.0.0"
)

# Pydantic models for request/response
class UserBase(BaseModel):
name: str
email: EmailStr

class UserCreate(UserBase):
pass

class User(UserBase):
id: int
created_at: datetime

class Config:
from_attributes = True

# In-memory storage (use database in production)
users_db = []
user_id_counter = 1

@app.get("/")
async def root():
return {"message": "Welcome to User Management API"}

@app.post("/users/", response_model=User)
async def create_user(user: UserCreate):
global user_id_counter

# Check if email already exists
if any(u["email"] == user.email for u in users_db):
raise HTTPException(status_code=400, detail="Email already registered")

new_user = {
"id": user_id_counter,
"name": user.name,
"email": user.email,
"created_at": datetime.now()
}

users_db.append(new_user)
user_id_counter += 1

return new_user

@app.get("/users/", response_model=List[User])
async def get_users(skip: int = 0, limit: int = 100):
return users_db[skip: skip + limit]

@app.get("/users/{user_id}", response_model=User)
async def get_user(user_id: int):
user = next((u for u in users_db if u["id"] == user_id), None)
if user is None:
raise HTTPException(status_code=404, detail="User not found")
return user

@app.put("/users/{user_id}", response_model=User)
async def update_user(user_id: int, user_update: UserCreate):
user_index = next((i for i, u in enumerate(users_db) if u["id"] == user_id), None)
if user_index is None:
raise HTTPException(status_code=404, detail="User not found")

# Check if email already exists (excluding current user)
if any(u["email"] == user_update.email and u["id"] != user_id for u in users_db):
raise HTTPException(status_code=400, detail="Email already registered")

users_db[user_index].update({
"name": user_update.name,
"email": user_update.email
})

return users_db[user_index]

@app.delete("/users/{user_id}")
async def delete_user(user_id: int):
user_index = next((i for i, u in enumerate(users_db) if u["id"] == user_id), None)
if user_index is None:
raise HTTPException(status_code=404, detail="User not found")

users_db.pop(user_index)
return {"message": "User deleted successfully"}

if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8000)

FastAPI with Database and Authentication

from fastapi import FastAPI, Depends, HTTPException, status
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
from sqlalchemy import create_engine, Column, Integer, String, DateTime
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker, Session
from pydantic import BaseModel
from datetime import datetime, timedelta
import jwt
import os

# Database setup
DATABASE_URL = "sqlite:///./users.db"
engine = create_engine(DATABASE_URL)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
Base = declarative_base()

# Database models
class User(Base):
__tablename__ = "users"

id = Column(Integer, primary_key=True, index=True)
name = Column(String, index=True)
email = Column(String, unique=True, index=True)
hashed_password = Column(String)
created_at = Column(DateTime, default=datetime.utcnow)

# Pydantic models
class UserCreate(BaseModel):
name: str
email: str
password: str

class UserResponse(BaseModel):
id: int
name: str
email: str
created_at: datetime

class Config:
from_attributes = True

class Token(BaseModel):
access_token: str
token_type: str

# Create tables
Base.metadata.create_all(bind=engine)

# FastAPI app
app = FastAPI(title="Secure User API", version="1.0.0")
security = HTTPBearer()

# Dependency to get database session
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()

# JWT utilities
SECRET_KEY = os.getenv("SECRET_KEY", "your-secret-key")
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 30

def create_access_token(data: dict):
to_encode = data.copy()
expire = datetime.utcnow() + timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
to_encode.update({"exp": expire})
encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
return encoded_jwt

def verify_token(credentials: HTTPAuthorizationCredentials = Depends(security)):
try:
payload = jwt.decode(credentials.credentials, SECRET_KEY, algorithms=[ALGORITHM])
user_id: int = payload.get("sub")
if user_id is None:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Invalid authentication credentials"
)
return user_id
except jwt.PyJWTError:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Invalid authentication credentials"
)

# Routes
@app.post("/register", response_model=UserResponse)
async def register(user: UserCreate, db: Session = Depends(get_db)):
# Check if user already exists
db_user = db.query(User).filter(User.email == user.email).first()
if db_user:
raise HTTPException(status_code=400, detail="Email already registered")

# Create new user (in production, hash the password)
db_user = User(
name=user.name,
email=user.email,
hashed_password=user.password # Hash this in production!
)
db.add(db_user)
db.commit()
db.refresh(db_user)

return db_user

@app.post("/login", response_model=Token)
async def login(user: UserCreate, db: Session = Depends(get_db)):
db_user = db.query(User).filter(User.email == user.email).first()
if not db_user or db_user.hashed_password != user.password:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Incorrect email or password"
)

access_token = create_access_token(data={"sub": str(db_user.id)})
return {"access_token": access_token, "token_type": "bearer"}

@app.get("/users/me", response_model=UserResponse)
async def read_users_me(current_user_id: int = Depends(verify_token), db: Session = Depends(get_db)):
user = db.query(User).filter(User.id == current_user_id).first()
if user is None:
raise HTTPException(status_code=404, detail="User not found")
return user

Django is a high-level framework that includes everything needed for rapid development.

Django Project Structure

# settings.py
from pathlib import Path

BASE_DIR = Path(__file__).resolve().parent.parent

SECRET_KEY = 'your-secret-key-here'
DEBUG = True
ALLOWED_HOSTS = []

INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'rest_framework',
'users',
]

MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

ROOT_URLCONF = 'myproject.urls'

DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': BASE_DIR / 'db.sqlite3',
}
}

# Django REST Framework settings
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework.authentication.TokenAuthentication',
],
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.IsAuthenticated',
],
}

Django Models and Views

# models.py
from django.db import models
from django.contrib.auth.models import AbstractUser

class User(AbstractUser):
email = models.EmailField(unique=True)
created_at = models.DateTimeField(auto_now_add=True)

USERNAME_FIELD = 'email'
REQUIRED_FIELDS = ['username']

class Post(models.Model):
title = models.CharField(max_length=200)
content = models.TextField()
author = models.ForeignKey(User, on_delete=models.CASCADE)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)

def __str__(self):
return self.title

# serializers.py
from rest_framework import serializers
from .models import User, Post

class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ['id', 'username', 'email', 'created_at']

class PostSerializer(serializers.ModelSerializer):
author = UserSerializer(read_only=True)

class Meta:
model = Post
fields = ['id', 'title', 'content', 'author', 'created_at', 'updated_at']

# views.py
from rest_framework import generics, permissions
from rest_framework.authtoken.views import ObtainAuthToken
from rest_framework.authtoken.models import Token
from rest_framework.response import Response
from .models import Post
from .serializers import PostSerializer, UserSerializer

class PostListCreateView(generics.ListCreateAPIView):
queryset = Post.objects.all()
serializer_class = PostSerializer
permission_classes = [permissions.IsAuthenticated]

def perform_create(self, serializer):
serializer.save(author=self.request.user)

class PostDetailView(generics.RetrieveUpdateDestroyAPIView):
queryset = Post.objects.all()
serializer_class = PostSerializer
permission_classes = [permissions.IsAuthenticated]

class CustomAuthToken(ObtainAuthToken):
def post(self, request, *args, **kwargs):
serializer = self.serializer_class(data=request.data,
context={'request': request})
serializer.is_valid(raise_exception=True)
user = serializer.validated_data['user']
token, created = Token.objects.get_or_create(user=user)
return Response({
'token': token.key,
'user_id': user.pk,
'email': user.email
})

Best Practices for Python Web Development

1. Project Structure

myproject/
├── app/
│ ├── __init__.py
│ ├── main.py
│ ├── models/
│ ├── routes/
│ ├── services/
│ └── utils/
├── tests/
├── requirements.txt
├── .env
└── README.md

2. Environment Management

# Use python-dotenv for environment variables
from dotenv import load_dotenv
import os

load_dotenv()

DATABASE_URL = os.getenv("DATABASE_URL")
SECRET_KEY = os.getenv("SECRET_KEY")

3. Error Handling

from fastapi import HTTPException, status

@app.exception_handler(ValueError)
async def value_error_handler(request, exc):
return JSONResponse(
status_code=status.HTTP_400_BAD_REQUEST,
content={"detail": str(exc)}
)

4. Testing

import pytest
from fastapi.testclient import TestClient
from app.main import app

client = TestClient(app)

def test_create_user():
response = client.post(
"/users/",
json={"name": "John Doe", "email": "[email protected]"}
)
assert response.status_code == 201
assert response.json()["name"] == "John Doe"

Deployment Considerations

1. Production Settings

# Use environment variables for sensitive data
SECRET_KEY = os.getenv("SECRET_KEY")
DEBUG = os.getenv("DEBUG", "False").lower() == "true"
ALLOWED_HOSTS = os.getenv("ALLOWED_HOSTS", "").split(",")

2. Database Configuration

# Use PostgreSQL for production
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': os.getenv('DB_NAME'),
'USER': os.getenv('DB_USER'),
'PASSWORD': os.getenv('DB_PASSWORD'),
'HOST': os.getenv('DB_HOST'),
'PORT': os.getenv('DB_PORT'),
}
}

3. Static Files and Media

# Django settings for static files
STATIC_URL = '/static/'
STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles')

MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')

Conclusion

Python web development offers powerful frameworks for every use case:

  • Flask: Perfect for learning and small applications
  • FastAPI: Ideal for modern APIs with automatic documentation
  • Django: Best for full-featured web applications

Choose the framework that best fits your project requirements and team expertise.

Next Steps

Ready to dive deeper into Python web development? Check out our comprehensive tutorials:


Which Python web framework interests you most? Share your thoughts and experiences in the comments below!