Category: Uncategorized

  • Python Fastapi Jwt Oauth2 Authentication

    When it comes to building modern, high‑performance APIs with Python, FastAPI has quickly become the go‑to framework for developers who crave speed, type safety, and developer friendliness. Yet, a powerful API is only as good as its security model, and that’s where JWT (JSON Web Tokens) combined with OAuth2 shines. In this guide we’ll walk through everything you need to know to implement robust JWT‑based OAuth2 authentication in a FastAPI project—from the underlying concepts to a complete, production‑ready code example. By the end, you’ll be equipped to protect your endpoints, manage token lifecycles, and scale authentication securely.

    Why Choose JWT and OAuth2 with FastAPI?

    • Stateless authentication: JWTs carry all necessary claims, eliminating the need for server‑side session storage.
    • Scalability: Because tokens are self‑contained, horizontal scaling and micro‑service architectures become straightforward.
    • Standard compliance: OAuth2 is an industry‑standard protocol, and FastAPI provides first‑class support via fastapi.security.
    • Developer experience: FastAPI’s automatic OpenAPI documentation displays security schemes out of the box, making client integration painless.

    Core Concepts You Should Know

    1. OAuth2 Grant Types

    OAuth2 defines several grant types for different use‑cases. For most API‑first projects, the Resource Owner Password Credentials (ROPC) or Authorization Code flow with PKCE is used. In this tutorial we’ll focus on the password grant, which is ideal for internal tools and mobile apps where the client can safely handle user credentials.

    2. JSON Web Tokens (JWT)

    A JWT consists of three Base64‑URL‑encoded parts: header.payload.signature. The header declares the signing algorithm (e.g., HS256), the payload carries claims like sub (subject), exp (expiration), and custom fields, and the signature ensures integrity.

    3. Access vs. Refresh Tokens

    Access tokens are short‑lived (typically 5‑15 minutes) to limit exposure if stolen. Refresh tokens are longer‑lived and can be exchanged for new access tokens without re‑authenticating the user. Implementing both improves security and user experience.

    Setting Up the Project

    # Install dependencies
    pip install fastapi uvicorn python-multipart python-jose[cryptography] passlib[bcrypt] sqlalchemy
    

    We’ll use python-jose for JWT handling, passlib for password hashing, and SQLAlchemy as a lightweight ORM.

    Step‑by‑Step Implementation

    1. Define Settings and Security Utilities

    # app/config.py
    import os
    from datetime import timedelta
    
    class Settings:
        SECRET_KEY: str = os.getenv("SECRET_KEY", "supersecretkey")
        ALGORITHM: str = "HS256"
        ACCESS_TOKEN_EXPIRE_MINUTES: int = 15
        REFRESH_TOKEN_EXPIRE_DAYS: int = 30
    
    settings = Settings()
    
    # app/security.py
    from datetime import datetime, timedelta
    from typing import Optional
    from jose import JWTError, jwt
    from passlib.context import CryptContext
    from .config import settings
    
    pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
    
    def verify_password(plain_password: str, hashed_password: str) -> bool:
        return pwd_context.verify(plain_password, hashed_password)
    
    def get_password_hash(password: str) -> str:
        return pwd_context.hash(password)
    
    def create_access_token(data: dict, expires_delta: Optional[timedelta] = None) -> str:
        to_encode = data.copy()
        expire = datetime.utcnow() + (expires_delta or timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES))
        to_encode.update({"exp": expire})
        return jwt.encode(to_encode, settings.SECRET_KEY, algorithm=settings.ALGORITHM)
    
    def create_refresh_token(data: dict, expires_delta: Optional[timedelta] = None) -> str:
        to_encode = data.copy()
        expire = datetime.utcnow() + (expires_delta or timedelta(days=settings.REFRESH_TOKEN_EXPIRE_DAYS))
        to_encode.update({"exp": expire, "type": "refresh"})
        return jwt.encode(to_encode, settings.SECRET_KEY, algorithm=settings.ALGORITHM)
    
    def decode_token(token: str) -> dict:
        try:
            payload = jwt.decode(token, settings.SECRET_KEY, algorithms=[settings.ALGORITHM])
            return payload
        except JWTError:
            raise
    

    2. Create the User Model

    # app/models.py
    from sqlalchemy import Column, Integer, String, Boolean
    from sqlalchemy.ext.declarative import declarative_base
    
    Base = declarative_base()
    
    class User(Base):
        __tablename__ = "users"
        id = Column(Integer, primary_key=True, index=True)
        username = Column(String, unique=True, index=True, nullable=False)
        hashed_password = Column(String, nullable=False)
        is_active = Column(Boolean, default=True)
    

    3. Dependency for Getting the Current User

    # app/dependencies.py
    from fastapi import Depends, HTTPException, status
    from fastapi.security import OAuth2PasswordBearer
    from .security import decode_token
    from .database import SessionLocal
    from .models import User
    
    oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
    
    def get_db():
        db = SessionLocal()
        try:
            yield db
        finally:
            db.close()
    
    def get_current_user(token: str = Depends(oauth2_scheme), db: SessionLocal = Depends(get_db)) -> User:
        credentials_exception = HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Could not validate credentials",
            headers={"WWW-Authenticate": "Bearer"},
        )
        try:
            payload = decode_token(token)
            username: str = payload.get("sub")
            if username is None:
                raise credentials_exception
        except Exception:
            raise credentials_exception
        user = db.query(User).filter(User.username == username).first()
        if user is None:
            raise credentials_exception
        return user
    

    4. Authentication Endpoints

    # app/main.py
    from fastapi import FastAPI, Depends, HTTPException, status
    from fastapi.security import OAuth2PasswordRequestForm
    from sqlalchemy.orm import Session
    from .models import Base, User
    from .database import engine, SessionLocal
    from .security import (
        verify_password,
        get_password_hash,
        create_access_token,
        create_refresh_token,
    )
    from .dependencies import get_current_user, get_db
    
    app = FastAPI(title="FastAPI JWT OAuth2 Demo")
    
    Base.metadata.create_all(bind=engine)
    
    @app.post("/token", summary="Obtain access and refresh tokens")
    def login(
        form_data: OAuth2PasswordRequestForm = Depends(),
        db: Session = Depends(get_db)
    ):
        user = db.query(User).filter(User.username == form_data.username).first()
        if not user or not verify_password(form_data.password, user.hashed_password):
            raise HTTPException(
                status_code=status.HTTP_401_UNAUTHORIZED,
                detail="Incorrect username or password",
                headers={"WWW-Authenticate": "Bearer"},
            )
        access_token = create_access_token({"sub": user.username})
        refresh_token = create_refresh_token({"sub": user.username})
        return {"access_token": access_token, "refresh_token": refresh_token, "token_type": "bearer"}
    
    @app.post("/refresh", summary="Refresh access token using a valid refresh token")
    def refresh_token(refresh_token: str, db: Session = Depends(get_db)):
        try:
            payload = decode_token(refresh_token)
            if payload.get("type") != "refresh":
                raise HTTPException(status_code=400, detail="Invalid token type")
            username = payload.get("sub")
        except Exception:
            raise HTTPException(status_code=401, detail="Invalid refresh token")
        user = db.query(User).filter(User.username == username).first()
        if not user:
            raise HTTPException(status_code=404, detail="User not found")
        new_access = create_access_token({"sub": user.username})
        return {"access_token": new_access, "token_type": "bearer"}
    
    @app.get("/users/me", summary="Get current authenticated user")
    def read_current_user(current_user: User = Depends(get_current_user)):
        return {"username": current_user.username, "is_active": current_user.is_active}
    

    Testing the Flow with cURL

    • Step 1 – Register a user (once):
      curl -X POST "http://localhost:8000/users/" -H "Content-Type: application/json" -d '{"username":"alice","password":"strongpwd"}'
    • Step 2 – Obtain tokens:
      curl -X POST "http://localhost:8000/token" -d "grant_type=password&username=alice&password=strongpwd"
    • Step 3 – Access a protected route:
      curl -H "Authorization: Bearer <ACCESS_TOKEN>" http://localhost:8000/users/me
    • Step 4 – Refresh the access token:
      curl -X POST "http://localhost:8000/refresh" -d "refresh_token=<REFRESH_TOKEN>"

    Best Practices for Production‑Ready JWT Auth

    1. Use HTTPS everywhere. Tokens travel in clear text over HTTP; TLS eliminates man‑in‑the‑middle attacks.
    2. Rotate secret keys regularly. Store the secret in a vault (e.g., AWS Secrets Manager) and implement key‑id (kid) rotation if you need multiple keys.
    3. Set appropriate token lifetimes. Short‑lived access tokens (5
  • Python Fastapi Api Rate Limiting Tutorial

    If you’ve ever built a public API with FastAPI, you know how easy it is to expose powerful endpoints in minutes. But speed can be a double‑edged sword—without proper safeguards, a single client can overwhelm your service, leading to degraded performance or even downtime. That’s where rate limiting comes in. In this tutorial we’ll walk through everything you need to know to implement robust API rate limiting in a FastAPI application, from the theory behind throttling to practical, production‑ready code examples.

    Why Rate Limiting Matters for FastAPI Services

    Rate limiting protects your API in several critical ways:

    • Prevents abuse: Stops malicious users from spamming endpoints.
    • Ensures fairness: Guarantees that all clients get an equal share of resources.
    • Reduces costs: Limits unnecessary compute and bandwidth usage, especially important on cloud platforms.
    • Improves reliability: Keeps response times low by avoiding request spikes.

    Search engines also love well‑structured, secure APIs, which can indirectly boost your SEO. By adding rate limiting, you signal that your service is trustworthy and resilient—key factors for developers searching for “FastAPI rate limiting tutorial”.

    Core Concepts Behind API Rate Limiting

    1. Fixed Window vs. Sliding Window

    A fixed window counts requests in discrete intervals (e.g., 100 requests per minute). It’s simple but can cause “burst” problems at the edge of each window. A sliding window smooths traffic by counting requests over a rolling period, offering more even throttling.

    2. Token Bucket Algorithm

    The token bucket method provides a flexible approach: a bucket holds a set number of tokens that refill at a constant rate. Each request consumes a token; if the bucket is empty, the request is rejected. This algorithm balances burst capacity with a steady request rate.

    3. Leaky Bucket Algorithm

    Similar to token bucket, the leaky bucket enforces a constant outflow of requests, “leaking” excess traffic over time. It’s ideal when you want to guarantee a uniform request rate.

    Choosing the Right Rate Limiting Strategy for FastAPI

    For most FastAPI projects, the token bucket implementation strikes the best balance between simplicity and flexibility. It allows short bursts (helpful for mobile apps) while enforcing a long‑term limit.

    Step‑by‑Step Implementation Using slowapi

    The slowapi library provides a clean integration of the limits package with FastAPI, handling token bucket logic out of the box.

    Prerequisites

    • Python 3.9+ installed
    • FastAPI and Uvicorn (or another ASGI server)
    • Redis (optional but recommended for distributed rate limiting)

    1. Install Dependencies

    pip install fastapi uvicorn slowapi[redis] redis

    2. Create a Basic FastAPI App

    from fastapi import FastAPI
    
    app = FastAPI()
    
    @app.get("/ping")
    async def ping():
        return {"message": "pong"}
    

    3. Add SlowAPI Middleware

    Wrap the FastAPI instance with Limter and configure a default limit (e.g., 10 requests per minute).

    from slowapi import Limiter, _rate_limit_exceeded_handler
    from slowapi.util import get_remote_address
    from fastapi import Request, HTTPException
    from fastapi.responses import JSONResponse
    
    # Create a Limiter instance – using in‑memory storage for demo
    limiter = Limiter(key_func=get_remote_address)
    
    # Register the exception handler
    app.state.limiter = limiter
    app.add_exception_handler(429, _rate_limit_exceeded_handler)
    
    @app.get("/limited")
    @limiter.limit("10/minute")
    async def limited_endpoint(request: Request):
        return {"detail": "You are within the rate limit!"}
    

    4. Switch to Redis for Distributed Limits

    When you run multiple worker processes (e.g., with gunicorn -k uvicorn.workers.UvicornWorker), an in‑memory store won’t share counters across processes. Configure Redis as the storage backend:

    from slowapi import Limiter
    from slowapi.storage import RedisStorage
    import redis
    
    redis_client = redis.from_url("redis://localhost:6379/0")
    limiter = Limiter(key_func=get_remote_address, storage=RedisStorage(redis_client))
    

    5. Fine‑Tune Limits per Endpoint

    You can apply different limits to each route or even group routes using include_in_schema=False for internal endpoints.

    @app.get("/search")
    @limiter.limit("5/second")
    async def search(q: str):
        # Simulated search logic
        return {"results": f"Results for {q}"}
    
    @app.post("/upload")
    @limiter.limit("2/minute")
    async def upload(file: bytes):
        # Handle file upload
        return {"status": "uploaded"}
    

    6. Customize the 429 Response

    By default SlowAPI returns a generic JSON error. You can tailor the message to improve developer experience.

    from fastapi.responses import JSONResponse
    
    @app.exception_handler(429)
    async def ratelimit_handler(request: Request, exc):
        return JSONResponse(
            status_code=429,
            content={"detail": "Rate limit exceeded. Please try again later."},
        )
    

    Advanced Techniques

    Dynamic Limits Based on API Keys

    If you issue API keys to clients, you can vary the limit per key. Store each key’s quota in a database and retrieve it in the key_func:

    def key_func(request: Request):
        api_key = request.headers.get("X-API-Key")
        if not api_key:
            return get_remote_address(request)  # fallback to IP
        return f"api_key:{api_key}"
    

    Then apply a dynamic rule:

    @app.get("/premium")
    @limiter.limit(lambda request: f"{get_quota(request.headers['X-API-Key'])}/minute")
    async def premium_endpoint():
        return {"detail": "Premium access granted"}
    

    Rate Limit Headers for Client Transparency

    Adding RateLimit-Limit, RateLimit-Remaining, and RateLimit-Reset headers helps clients self‑regulate.

    from fastapi import Response
    
    @app.get("/with-headers")
    @limiter.limit("20/minute")
    async def with_headers(request: Request, response: Response):
        # SlowAPI automatically sets headers on the response object
        return {"detail": "Headers added"}
    

    Testing Rate Limiting Locally

    Use curl or a simple Python script to verify your limits:

    import requests, time
    
    url = "http://localhost:8000/limited"
    for i in range(12):
        r = requests.get(url)
        print(i, r.status_code, r.json())
        time.sleep(5)  # adjust to trigger limit
    

    Deploying a Rate‑Limited FastAPI Service

    When moving to production, consider the following checklist:

    1. Use Redis or Memcached: Guarantees consistent counters across containers.
    2. Secure API Keys: Store them in environment variables or a secret manager.
    3. Monitor Metrics: Export slowapi counters to Prometheus or Grafana.
    4. Graceful Degradation: Return helpful error messages and possibly a Retry-After header.
    5. Automated Tests: Include rate‑limit tests in your CI pipeline to prevent regressions.

    Common Pitfalls and How to Avoid Them

    • Using IP address alone: Users behind NAT share the same IP, causing unintended blocks. Combine IP with API key when possible.
    • Hard‑coding limits: Keep limits configurable via environment variables (e.g., RATE_LIMIT=10/minute).
    • Neglecting async safety: Ensure your storage backend (Redis) supports async operations if you use asyncio in other parts of the app.
    • Missing exception handling: Without a custom 429 handler, clients receive generic HTML errors that break API contracts.

    Full Example: A Ready‑to‑Run FastAPI Project

    Below is a minimal yet complete project structure you can clone and run.

    my_fastapi_app/
    ├── app/
    │   ├── __init__.py
    │   ├── main.py
    │   └── config.py
    ├── requirements.txt
    └── Dockerfile
    

    app/config.py

    import os
    
    REDIS_URL = os.getenv("REDIS_URL", "redis://localhost:6379/0")
    DEFAULT_LIMIT = os.getenv("DEFAULT_LIMIT", "10/minute")
    

    app/main.py

    from fastapi import FastAPI, Request, HTTPException
    from fastapi.responses import JSONResponse
    from slowapi import Limiter, _rate_limit_exceeded_handler
    from slowapi.util import get_remote_address
    from slow

  • Python Fastapi Websockets Real-Time App

    Building a real‑time application with Python has never been easier thanks to FastAPI and its native support for WebSockets. In this guide you’ll learn how to set up a scalable, low‑latency chat or notification system from scratch, understand the core concepts behind WebSocket communication, and see production‑ready code that you can adapt to any use‑case—from live dashboards to multiplayer games. Whether you’re a seasoned backend developer or just starting with FastAPI, the step‑by‑step examples below will give you the confidence to ship a real‑time app that feels instant to end users.

    Why Choose FastAPI for Real‑Time WebSocket Apps?

    FastAPI is celebrated for its high performance, automatic OpenAPI documentation, and async‑first design. When it comes to WebSockets, these strengths translate into:

    • Asynchronous I/O: Native async/await support lets you handle thousands of concurrent connections without blocking the event loop.
    • Type safety: Pydantic models validate incoming data, reducing runtime errors in real‑time streams.
    • Auto‑generated docs: The same Swagger UI that documents your REST endpoints also shows WebSocket routes, making debugging a breeze.
    • Easy integration: Works seamlessly with popular tools like uvicorn, Redis, and SQLModel for persistence and scaling.

    Core Concepts: WebSocket vs. HTTP

    Before diving into code, it’s useful to contrast the two communication models:

    1. HTTP request/response: A client initiates a request, the server processes it, and the connection closes. Ideal for CRUD operations.
    2. WebSocket: After an initial HTTP handshake, a persistent, full‑duplex channel remains open, allowing both client and server to push data at any time. Perfect for chat, live updates, and collaborative tools.

    Because the connection stays alive, you must manage resources carefully—handle disconnects, broadcast efficiently, and avoid memory leaks.

    Setting Up the Project

    1. Install Dependencies

    python -m venv venv
    source venv/bin/activate  # On Windows: venv\Scripts\activate
    pip install fastapi[all] uvicorn python-multipart
    # Optional for scaling
    pip install redis aioredis
    

    2. Project Structure

    • app/main.py – FastAPI entry point.
    • app/ws.py – WebSocket manager and endpoint definitions.
    • app/models.py – Pydantic schemas for messages.
    • templates/ – Simple HTML client for testing.

    Creating a WebSocket Manager

    A manager centralizes connection handling, broadcasting, and private messaging. Below is a minimal yet production‑ready implementation.

    # app/ws.py
    import json
    from typing import List, Dict
    from fastapi import WebSocket, WebSocketDisconnect
    
    class ConnectionManager:
        def __init__(self):
            self.active_connections: List[WebSocket] = []
            self.user_map: Dict[str, WebSocket] = {}
    
        async def connect(self, websocket: WebSocket, username: str):
            await websocket.accept()
            self.active_connections.append(websocket)
            self.user_map[username] = websocket
            await self.broadcast_json({
                "type": "join",
                "user": username,
                "message": f"{username} has entered the chat."
            })
    
        def disconnect(self, websocket: WebSocket):
            self.active_connections.remove(websocket)
            # Remove from user_map if present
            for user, ws in list(self.user_map.items()):
                if ws == websocket:
                    del self.user_map[user]
                    break
    
        async def send_personal_message(self, message: str, websocket: WebSocket):
            await websocket.send_text(message)
    
        async def broadcast(self, message: str):
            for connection in self.active_connections:
                await connection.send_text(message)
    
        async def broadcast_json(self, data: dict):
            payload = json.dumps(data)
            await self.broadcast(payload)
    
        async def send_to_user(self, username: str, data: dict):
            ws = self.user_map.get(username)
            if ws:
                await ws.send_text(json.dumps(data))
    

    Defining the WebSocket Endpoint

    FastAPI treats a WebSocket route like any other path operation. The key is to keep the handler async and loop forever until a disconnect occurs.

    # app/main.py
    from fastapi import FastAPI, WebSocket, Depends, Query
    from .ws import ConnectionManager
    from .models import Message
    
    app = FastAPI()
    manager = ConnectionManager()
    
    @app.websocket("/ws/chat")
    async def chat_endpoint(
        websocket: WebSocket,
        username: str = Query(..., description="Unique nickname for the session")
    ):
        await manager.connect(websocket, username)
        try:
            while True:
                data = await websocket.receive_text()
                msg = Message.parse_raw(data)  # Pydantic validation
                # Broadcast to everyone
                await manager.broadcast_json({
                    "type": "message",
                    "user": username,
                    "content": msg.content,
                    "timestamp": msg.timestamp.isoformat()
                })
        except WebSocketDisconnect:
            manager.disconnect(websocket)
            await manager.broadcast_json({
                "type": "leave",
                "user": username,
                "message": f"{username} has left the chat."
            })
    

    Message Model

    # app/models.py
    from pydantic import BaseModel, Field
    from datetime import datetime
    
    class Message(BaseModel):
        content: str = Field(..., min_length=1, max_length=500)
        timestamp: datetime = Field(default_factory=datetime.utcnow)
    

    Testing the Real‑Time App Locally

    Run the server with uvicorn and open two browser tabs pointing to a simple HTML client (provided in templates/index.html). When you type a message in one tab, it instantly appears in the other, proving the full‑duplex nature of WebSockets.

    uvicorn app.main:app --reload --host 0.0.0.0 --port 8000
    

    Scaling Beyond a Single Process

    For production you’ll likely need more than one worker. Because each FastAPI instance has its own in‑memory ConnectionManager, you must externalize state. Two common patterns are:

    • Redis Pub/Sub: Publish messages to a Redis channel; every worker subscribes and forwards to its local connections.
    • Message Queues (e.g., RabbitMQ, NATS): Use a broker to route events, especially when you need guaranteed delivery or complex routing.

    Below is a concise example using aioredis for broadcast:

    # app/ws_redis.py
    import aioredis
    import json
    from fastapi import WebSocket
    
    redis = aioredis.from_url("redis://localhost", decode_responses=True)
    
    class RedisManager(ConnectionManager):
        async def broadcast(self, message: str):
            await redis.publish("chat_channel", message)
    
        async def listen(self):
            pubsub = redis.pubsub()
            await pubsub.subscribe("chat_channel")
            async for msg in pubsub.listen():
                if msg["type"] == "message":
                    await super().broadcast(msg["data"])
    

    Start a background task at app startup to run RedisManager.listen(), and you’ll have a horizontally scalable chat service.

    Security Considerations

    Real‑time endpoints are attractive targets, so follow these best practices:

    1. Authentication: Use JWT tokens passed as query parameters or sub‑protocol headers, then validate before accepting the connection.
    2. Rate limiting: Apply per‑IP or per‑user limits to prevent flooding. Libraries like slowapi work with FastAPI WebSockets.
    3. Input sanitization: Even though messages travel as JSON, validate length and content to avoid injection attacks.
    4. TLS/SSL: Deploy behind HTTPS (wss://) to encrypt traffic, especially for sensitive data.

    Deploying to Production

    When you’re ready to go live, consider the following stack:

    • Server: uvicorn behind Gunicorn with uvicorn.workers.UvicornWorker for multiple workers.
    • Containerization: Dockerize the app for consistent environments. A typical Dockerfile runs uvicorn app.main:app --host 0.0.0.0 --port 80.
    • Orchestration: Kubernetes with a Service of type LoadBalancer and Ingress handling TLS termination.
    • Observability: Export metrics with prometheus-fastapi-instrumentator and log WebSocket events for debugging.

    Common Pitfalls and How to Avoid Them

    • Blocking calls inside the loop: Never use synchronous database queries or heavy CPU work directly. Offload to a thread pool or async driver.
    • Forgot to handle disconnects: If you don’t remove dead sockets, memory usage will grow until the process crashes.
    • Large payloads:
  • Python Fastapi Dependency Injection Guide

    FastAPI has taken the Python web development world by storm thanks to its speed, intuitive design, and built‑in support for modern features like async programming and automatic OpenAPI documentation. One of the most powerful, yet often misunderstood, features is its dependency injection (DI) system. In this guide we’ll demystify FastAPI DI, show you how to write clean, reusable components, and give you practical code snippets you can copy straight into your projects. Whether you’re a beginner looking for a clear explanation or an experienced developer seeking best‑practice patterns, this Python FastAPI dependency injection guide has you covered.

    What Is Dependency Injection and Why Does FastAPI Need It?

    Dependency injection is a design pattern that separates the creation of an object (or resource) from its usage. Instead of hard‑coding a database connection, authentication service, or configuration value inside a route function, you declare those dependencies and let the framework provide them when needed. This brings several benefits:

    • Testability: You can swap real services for mocks in unit tests without touching the route logic.
    • Reusability: The same dependency can be shared across multiple endpoints.
    • Maintainability: Changes to a dependency (e.g., switching from SQLite to PostgreSQL) are made in one place.
    • Cleaner code: Route functions stay focused on business logic, not on wiring resources.

    FastAPI’s DI system is built on Python’s type hints and the Depends class. It works seamlessly with async functions, background tasks, and even third‑party libraries, making it one of the most flexible DI implementations in the Python ecosystem.

    Basic Dependency Injection with Depends

    Simple Example: Providing a Query Parameter

    from fastapi import FastAPI, Depends
    
    app = FastAPI()
    
    def common_query(q: str = None):
        return q
    
    @app.get("/items/")
    def read_items(query: str = Depends(common_query)):
        return {"query": query}
    

    In this example, common_query is a dependency that extracts the optional query parameter q. FastAPI calls the function, injects its return value into read_items, and you get a clean endpoint signature.

    Dependency with a Return Type

    Adding explicit return types improves IDE support and documentation:

    from typing import Optional
    
    def get_user_id(user_id: Optional[int] = None) -> Optional[int]:
        return user_id
    

    FastAPI will still treat it as a dependency as long as you wrap it with Depends in the endpoint.

    Advanced Dependency Patterns

    1. Using Classes as Dependencies

    When a dependency needs internal state (e.g., a database session), a class can be more appropriate than a plain function.

    from sqlalchemy.orm import Session
    from fastapi import Depends
    
    class DBSession:
        def __init__(self):
            self.session = SessionLocal()
    
        def __call__(self) -> Session:
            try:
                yield self.session
            finally:
                self.session.close()
    
    def get_db(db: Session = Depends(DBSession())):
        return db
    

    The class implements __call__, turning it into a callable that FastAPI can treat as a dependency. The yield syntax makes it a generator‑based dependency, allowing FastAPI to execute cleanup code after the request finishes.

    2. Dependency Scopes: request vs singleton

    FastAPI supports three scopes:

    • request – a new instance for every request (default).
    • session – reused within a single client session (useful with websockets).
    • singleton – one instance for the entire application lifetime.

    Define the scope with the Depends parameter:

    def get_cache() -> dict:
        return {}
    
    cache_dependency = Depends(get_cache, scope="singleton")
    

    Now cache_dependency will be instantiated only once, making it ideal for in‑memory caches or configuration objects.

    3. Nested Dependencies

    Dependencies can depend on other dependencies, creating a powerful chain of reusable components.

    def get_current_user(token: str = Depends(oauth2_scheme)):
        # Decode token, fetch user, raise HTTPException if invalid
        ...
    
    def get_active_user(current_user: User = Depends(get_current_user)):
        if not current_user.is_active:
            raise HTTPException(status_code=400, detail="Inactive user")
        return current_user
    
    @app.get("/profile")
    def read_profile(user: User = Depends(get_active_user)):
        return {"username": user.username}
    

    Here, get_active_user builds on get_current_user, and the endpoint receives the final, validated user object.

    Practical Use Cases

    Database Sessions

    Most production APIs need a database connection. Using a generator‑based dependency guarantees that the session is closed even if an exception occurs.

    from fastapi import Depends, FastAPI, HTTPException
    from sqlalchemy import create_engine
    from sqlalchemy.orm import sessionmaker, Session
    
    SQLALCHEMY_DATABASE_URL = "sqlite:///./test.db"
    engine = create_engine(SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False})
    SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
    
    def get_db() -> Session:
        db = SessionLocal()
        try:
            yield db
        finally:
            db.close()
    
    @app.post("/users/")
    def create_user(name: str, db: Session = Depends(get_db)):
        db_user = User(name=name)
        db.add(db_user)
        db.commit()
        db.refresh(db_user)
        return db_user
    

    Authentication and Authorization

    FastAPI’s DI makes token validation concise and reusable.

    from fastapi.security import OAuth2PasswordBearer
    from jose import JWTError, jwt
    
    oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
    
    def verify_token(token: str = Depends(oauth2_scheme)):
        try:
            payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
            user_id: str = payload.get("sub")
            if user_id is None:
                raise credentials_exception
            return get_user(user_id)
        except JWTError:
            raise credentials_exception
    

    Any endpoint that adds user: User = Depends(verify_token) automatically enforces authentication without extra boilerplate.

    Rate Limiting as a Dependency

    Because dependencies run before the endpoint logic, they’re perfect for cross‑cutting concerns like rate limiting.

    from fastapi import Request, HTTPException, status
    
    def rate_limiter(request: Request):
        client_ip = request.client.host
        if not limiter.allow(client_ip):
            raise HTTPException(
                status_code=status.HTTP_429_TOO_MANY_REQUESTS,
                detail="Rate limit exceeded"
            )
        return True
    
    @app.get("/search")
    def search(q: str, allowed: bool = Depends(rate_limiter)):
        # Business logic runs only if the request passed the limiter
        return {"results": perform_search(q)}
    

    Testing FastAPI Dependencies

    One of the biggest selling points of DI is testability. You can override dependencies in the TestClient or in the app.dependency_overrides dictionary.

    from fastapi.testclient import TestClient
    
    def override_get_db():
        # Return a session bound to a test database
        ...
    
    app.dependency_overrides[get_db] = override_get_db
    
    client = TestClient(app)
    
    def test_create_user():
        response = client.post("/users/", json={"name": "Alice"})
        assert response.status_code == 200
        assert response.json()["name"] == "Alice"
    

    By swapping the real database for an in‑memory SQLite instance, you keep tests fast and isolated.

    Best Practices for FastAPI Dependency Injection

    • Keep dependencies small. A single responsibility (e.g., “fetch current user”) makes them easier to reuse.
    • Prefer generator‑based dependencies for resources that need cleanup. The yield pattern ensures finally blocks run.
    • Document return types. Adding explicit -> Type hints improves auto‑generated OpenAPI docs and IDE autocomplete.
    • Use scopes wisely. Singleton scope is great for read‑only configuration, but avoid it for mutable state.
    • Leverage nested dependencies. Build a dependency tree that mirrors your application architecture (auth → permissions → business logic).
    • Override dependencies in tests. This isolates external services and speeds up CI pipelines.

    Common Pitfalls and How to Avoid Them

    1. Forgetting to Return a Value

    If a dependency function ends without a return (or yield), FastAPI injects None. This can cause obscure AttributeError exceptions later. Always end with an explicit return.

    2. Using Global State in Request‑Scoped Dependencies

    Mixing mutable globals with request‑scoped dependencies leads to race conditions under high load. Stick to class instances with proper scopes or thread‑local storage.

    3. Over‑Complicating the Dependency Graph

    While nesting is powerful, a deeply chained graph can become hard to follow. Aim for a maximum depth of two or three levels, and keep each dependency focused on a single concern.

    Conclusion

    FastAPI’s dependency injection system is more than a convenience—it’s a cornerstone of building scalable, maintainable, and test

  • Python Fastapi Async Crud Operations Tutorial

    Welcome to the ultimate Python FastAPI async CRUD operations tutorial! Whether you’re a seasoned developer looking to modernize your API stack or a newcomer eager to build high‑performance web services, FastAPI’s asynchronous capabilities make it the perfect choice. In this guide, we’ll walk through everything you need to know—from setting up a FastAPI project to implementing fully asynchronous create, read, update, and delete (CRUD) endpoints backed by an async‑compatible database. By the end, you’ll have a production‑ready template that you can extend for any real‑world application.

    Why Choose FastAPI for Async CRUD?

    FastAPI has quickly become the go‑to framework for building RESTful APIs in Python, and for good reasons:

    • Native async support: Built on top of Starlette, FastAPI lets you write async def endpoints that run concurrently, dramatically improving throughput.
    • Automatic OpenAPI documentation: Swagger UI and ReDoc are generated out of the box, making your API self‑documenting.
    • Type‑safety and validation: Pydantic models enforce data schemas, catching errors before they hit your database.
    • Performance: Benchmarks show FastAPI rivals Node.js and Go for request latency.

    Prerequisites

    Before diving into code, make sure you have the following installed:

    1. Python 3.9 or newer
    2. Git (optional, for version control)
    3. A terminal or command prompt with pip access

    We’ll also use uvicorn as the ASGI server and SQLModel (a SQLAlchemy‑based ORM) for async database interactions.

    Project Setup

    1. Create a virtual environment

    python -m venv venv
    source venv/bin/activate   # On Windows use: venv\Scripts\activate
    

    2. Install dependencies

    pip install fastapi uvicorn[standard] sqlmodel aiosqlite
    

    3. Project structure

    Organize your files for clarity and scalability:

    .
    ├── app
    │   ├── __init__.py
    │   ├── main.py
    │   ├── models.py
    │   ├── schemas.py
    │   └── crud.py
    └── requirements.txt
    

    Defining the Data Model

    We’ll build a simple Item resource with id, name, description, and price. Using SQLModel gives us both Pydantic validation and SQLAlchemy ORM features.

    app/models.py

    from sqlmodel import SQLModel, Field
    from typing import Optional
    
    class Item(SQLModel, table=True):
        id: Optional[int] = Field(default=None, primary_key=True)
        name: str = Field(index=True, max_length=100)
        description: Optional[str] = Field(default=None, max_length=255)
        price: float
    

    Creating Pydantic Schemas

    Separate schemas for request payloads and response models keep your API clean.

    app/schemas.py

    from pydantic import BaseModel, Field
    from typing import Optional
    
    class ItemCreate(BaseModel):
        name: str = Field(..., max_length=100)
        description: Optional[str] = Field(None, max_length=255)
        price: float = Field(..., gt=0)
    
    class ItemRead(BaseModel):
        id: int
        name: str
        description: Optional[str]
        price: float
    
    class ItemUpdate(BaseModel):
        name: Optional[str] = Field(None, max_length=100)
        description: Optional[str] = Field(None, max_length=255)
        price: Optional[float] = Field(None, gt=0)
    

    Async CRUD Functions

    All database interactions are async, leveraging SQLModel’s async engine.

    app/crud.py

    from sqlmodel import select
    from sqlmodel.ext.asyncio.session import AsyncSession
    from .models import Item
    from typing import List, Optional
    
    async def get_item(session: AsyncSession, item_id: int) -> Optional[Item]:
        result = await session.exec(select(Item).where(Item.id == item_id))
        return result.first()
    
    async def get_items(session: AsyncSession, skip: int = 0, limit: int = 100) -> List[Item]:
        result = await session.exec(select(Item).offset(skip).limit(limit))
        return result.all()
    
    async def create_item(session: AsyncSession, item_data) -> Item:
        db_item = Item.from_orm(item_data)
        session.add(db_item)
        await session.commit()
        await session.refresh(db_item)
        return db_item
    
    async def update_item(session: AsyncSession, db_item: Item, updates) -> Item:
        item_data = updates.dict(exclude_unset=True)
        for key, value in item_data.items():
            setattr(db_item, key, value)
        session.add(db_item)
        await session.commit()
        await session.refresh(db_item)
        return db_item
    
    async def delete_item(session: AsyncSession, db_item: Item) -> None:
        await session.delete(db_item)
        await session.commit()
    

    FastAPI Application Entry Point

    app/main.py

    import uvicorn
    from fastapi import FastAPI, HTTPException, Depends, status
    from sqlmodel import SQLModel
    from sqlmodel.ext.asyncio.session import AsyncSession
    from sqlmodel.ext.asyncio.engine import create_async_engine
    from .models import Item
    from .schemas import ItemCreate, ItemRead, ItemUpdate
    from . import crud
    
    DATABASE_URL = "sqlite+aiosqlite:///./test.db"
    engine = create_async_engine(DATABASE_URL, echo=True)
    
    app = FastAPI(
        title="FastAPI Async CRUD Tutorial",
        description="A step‑by‑step guide to building async CRUD endpoints with FastAPI and SQLModel.",
        version="1.0.0"
    )
    
    # Dependency to get async DB session
    async def get_session() -> AsyncSession:
        async with AsyncSession(engine) as session:
            yield session
    
    @app.on_event("startup")
    async def on_startup():
        async with engine.begin() as conn:
            await conn.run_sync(SQLModel.metadata.create_all)
    
    @app.post("/items/", response_model=ItemRead, status_code=status.HTTP_201_CREATED)
    async def create_item_endpoint(item: ItemCreate, session: AsyncSession = Depends(get_session)):
        return await crud.create_item(session, item)
    
    @app.get("/items/", response_model=List[ItemRead])
    async def read_items(skip: int = 0, limit: int = 100, session: AsyncSession = Depends(get_session)):
        return await crud.get_items(session, skip=skip, limit=limit)
    
    @app.get("/items/{item_id}", response_model=ItemRead)
    async def read_item(item_id: int, session: AsyncSession = Depends(get_session)):
        db_item = await crud.get_item(session, item_id)
        if not db_item:
            raise HTTPException(status_code=404, detail="Item not found")
        return db_item
    
    @app.patch("/items/{item_id}", response_model=ItemRead)
    async def update_item_endpoint(item_id: int, item: ItemUpdate, session: AsyncSession = Depends(get_session)):
        db_item = await crud.get_item(session, item_id)
        if not db_item:
            raise HTTPException(status_code=404, detail="Item not found")
        return await crud.update_item(session, db_item, item)
    
    @app.delete("/items/{item_id}", status_code=status.HTTP_204_NO_CONTENT)
    async def delete_item_endpoint(item_id: int, session: AsyncSession = Depends(get_session)):
        db_item = await crud.get_item(session, item_id)
        if not db_item:
            raise HTTPException(status_code=404, detail="Item not found")
        await crud.delete_item(session, db_item)
        return None
    
    if __name__ == "__main__":
        uvicorn.run("app.main:app", host="0.0.0.0", port=8000, reload=True)
    

    Testing the API with HTTPie or cURL

    Once the server is running (uvicorn app.main:app --reload), you can interact with the endpoints:

    • Create an item:
      http POST http://localhost:8000/items/ name="FastAPI Book" price:=29.99 description="Learn async FastAPI"
    • Read all items:
      curl -X GET "http://localhost:8000/items/"
    • Update an item:
      http PATCH http://localhost:8000/items/1 price:=24.99
    • Delete an item:
      curl -X DELETE "http://localhost:8000/items/1"

    Best Practices for Production‑Ready Async CRUD APIs

    Building a functional prototype is just the first step. To ensure reliability and scalability, consider the following recommendations:

    1. Use a robust database engine

    SQLite is great for demos, but for production use PostgreSQL or MySQL with async drivers (asyncpg, aiomysql).

    2. Implement pagination and filtering

    Instead of returning all rows, add query parameters for limit, offset, and field‑based filters. This reduces memory usage and improves response times.

  • Python Django Signals And Middleware Guide

    When you start building a Django application, you quickly discover that the framework offers more than just models, views, and templates. Two powerful, yet often misunderstood, components are signals and middleware. Mastering these tools can help you keep your code clean, enforce business rules, and react to events across the entire project without tightly coupling your logic. In this guide we’ll dive deep into Python Django signals and middleware, explain when to use each, show practical code snippets, and share best‑practice tips that will make your Django projects more maintainable and SEO‑friendly.

    What Are Django Signals?

    Signals are a publish‑subscribe mechanism built into Django. They allow one part of an application to send a notification (the signal) that something has happened, while one or more listeners (receivers) react to that event. This decouples the sender from the receiver, making it easier to add or modify functionality without touching the original code.

    Core Concepts

    • Signal object – defined in django.dispatch, e.g., post_save or a custom signal.
    • Sender – the model or component that dispatches the signal.
    • Receiver – a callable (function or method) that processes the signal.
    • dispatch_uid – an optional unique identifier to prevent duplicate connections.

    Built‑in Signals You Should Know

    • pre_save / post_save
    • pre_delete / post_delete
    • m2m_changed
    • request_started / request_finished
    • got_request_exception

    How to Use Django Signals

    Connecting a Receiver

    from django.db.models.signals import post_save
    from django.dispatch import receiver
    from myapp.models import Order
    
    @receiver(post_save, sender=Order)
    def send_order_confirmation(sender, instance, created, **kwargs):
        if created:
            # Your email logic here
            print(f"Order {instance.id} was created.")

    Notice the @receiver decorator – it registers send_order_confirmation as a listener for the post_save signal emitted by the Order model.

    Creating a Custom Signal

    from django.dispatch import Signal
    
    # Define a signal that provides the user and the action performed
    user_action = Signal(providing_args=["user", "action"])
    
    # Somewhere in your view or service layer
    def perform_action(request):
        # ... business logic ...
        user_action.send(sender=perform_action, user=request.user, action="login")

    Best Practices for Signals

    • Keep receivers lightweight – avoid heavy database queries or long‑running tasks.
    • Use dispatch_uid to avoid duplicate connections, especially in reusable apps.
    • Document each signal in your project’s README or a dedicated SIGNALS.md file.
    • Prefer explicit function calls for critical logic; signals are best for side‑effects like logging, caching, or notifications.

    Signals vs. Middleware: When to Choose Which

    Both signals and middleware can intercept events, but they operate at different layers:

    • Signals react to model or application‑level events (e.g., a user is saved, an order is placed).
    • Middleware processes HTTP request/response cycles before the view is called or after the response is returned.

    If you need to modify the request object, enforce authentication, or add security headers, middleware is the right tool. If you want to trigger an email after a model is saved, signals are more appropriate.

    Understanding Django Middleware

    Middleware is a stack of classes that wrap the request/response processing. Each middleware component gets a chance to:

    • Inspect or modify the HttpRequest before it reaches the view.
    • Process the HttpResponse before it is sent to the client.
    • Handle exceptions raised by downstream components.

    Typical Use Cases

    • Performance monitoring (e.g., timing requests).
    • Custom authentication or permission checks.
    • Response compression, content security policies, or CORS handling.
    • Logging request data for audit trails.

    Built‑in Middleware Examples

    • django.middleware.security.SecurityMiddleware
    • django.contrib.sessions.middleware.SessionMiddleware
    • django.middleware.common.CommonMiddleware
    • django.middleware.csrf.CsrfViewMiddleware

    Creating Custom Middleware

    To add your own logic, create a class with at least one of the following methods: __init__, process_request, process_view, process_template_response, process_response, or process_exception. In Django 2.0+ the preferred style is a single __call__ method.

    Simple Request‑Timing Middleware

    import time
    from django.utils.deprecation import MiddlewareMixin
    
    class RequestTimingMiddleware(MiddlewareMixin):
        def process_request(self, request):
            request.start_time = time.monotonic()
    
        def process_response(self, request, response):
            duration = time.monotonic() - getattr(request, "start_time", time.monotonic())
            response["X-Request-Duration-ms"] = int(duration * 1000)
            return response

    Register the middleware in settings.py:

    MIDDLEWARE = [
        # Default middleware ...
        "myproject.middleware.RequestTimingMiddleware",
    ]

    Exception‑Handling Middleware

    class JsonExceptionMiddleware:
        def __init__(self, get_response):
            self.get_response = get_response
    
        def __call__(self, request):
            try:
                return self.get_response(request)
            except Exception as exc:
                return JsonResponse(
                    {"error": str(exc)},
                    status=500,
                    content_type="application/json"
                )

    Key Middleware Tips

    • Order matters – place authentication middleware before your custom logic that depends on request.user.
    • Avoid heavy processing in process_request; defer to the view when possible.
    • Always return an HttpResponse object; never return None unless you intend to let the next middleware handle the request.
    • Use django.utils.deprecation.MiddlewareMixin only when you need backward compatibility with older Django versions.

    Combining Signals and Middleware for Powerful Workflows

    In many real‑world projects you’ll find that signals and middleware complement each other. For example, you might use middleware to capture the authenticated user and store it in threadlocals, then let a signal receiver access that user when a model is saved.

    # middleware.py
    import threading
    
    _user = threading.local()
    
    class CurrentUserMiddleware:
        def __init__(self, get_response):
            self.get_response = get_response
    
        def __call__(self, request):
            _user.value = request.user if request.user.is_authenticated else None
            response = self.get_response(request)
            _user.value = None
            return response
    
    def get_current_user():
        return getattr(_user, "value", None)
    
    # signals.py
    from django.db.models.signals import post_save
    from django.dispatch import receiver
    from .models import Article
    from .middleware import get_current_user
    
    @receiver(post_save, sender=Article)
    def set_author(sender, instance, created, **kwargs):
        if created and not instance.author:
            instance.author = get_current_user()
            instance.save(update_fields=["author"])

    This pattern keeps the author‑assignment logic out of the view while still having access to request‑specific data.

    Performance and Security Considerations

    • Signal overhead: Signals add a tiny amount of overhead because Django must iterate through all connected receivers. Keep the number of receivers reasonable and avoid long‑running tasks inside them.
    • Middleware latency: Each middleware layer adds processing time. Profile your stack with tools like django-silk or django-debug-toolbar to identify bottlenecks.
    • Security: Never expose sensitive data in custom headers added by middleware. Validate and sanitize any data passed through signals, especially if it originates from user input.
    • Testing: Use django.test.override_settings to temporarily disable middleware or mock signals during unit tests, ensuring isolated and fast test suites.

    Frequently Asked Questions

    Can I disable a built‑in signal?

    Yes. Use signal.disconnect() with the appropriate receiver and sender. For example, to stop Django’s pre_save on a specific model, call pre_save.disconnect(receiver=my_receiver, sender=MyModel).

    Do signals work with async views?

    Starting with Django 3.1, signals can be emitted from async code, but

  • Python Django Redis Caching Performance

    Python Django Redis caching performance is a hot topic for developers who want to supercharge their web applications. In this article we’ll explore why Redis is the go‑to cache for Django projects, how to set it up correctly, and the performance gains you can expect. Whether you’re building a small blog or a high‑traffic e‑commerce platform, mastering Django‑Redis caching can reduce response times, lower database load, and improve overall user experience.

    Why Use Redis for Caching in Django?

    Redis (Remote Dictionary Server) is an in‑memory data store that excels at fast key‑value operations. When paired with Django’s caching framework, it offers several distinct advantages:

    • Speed: Data lives in RAM, delivering sub‑millisecond read/write latency.
    • Scalability: Supports clustering and replication, allowing you to grow horizontally as traffic spikes.
    • Rich data structures: Strings, hashes, lists, sets, and sorted sets enable advanced caching patterns beyond simple key‑value pairs.
    • Persistence options: Snapshotting (RDB) and append‑only file (AOF) give you durability without sacrificing performance.
    • Built‑in eviction policies: LRU, LFU, and TTL mechanisms keep the cache size under control automatically.

    Setting Up Redis with Django

    1. Install Required Packages

    pip install django-redis redis

    The django-redis package provides a seamless backend that integrates Redis with Django’s cache API.

    2. Configure Django Settings

    # settings.py
    CACHES = {
        "default": {
            "BACKEND": "django_redis.cache.RedisCache",
            "LOCATION": "redis://127.0.0.1:6379/1",
            "OPTIONS": {
                "CLIENT_CLASS": "django_redis.client.DefaultClient",
                # Optional: enable connection pooling for better performance
                "CONNECTION_POOL_KWARGS": {"max_connections": 100},
            },
            "TIMEOUT": 300,  # default TTL in seconds
        }
    }
    

    3. Verify the Connection

    from django.core.cache import cache
    cache.set('django_redis_test', 'It works!', timeout=10)
    print(cache.get('django_redis_test'))  # Should output: It works!

    Core Caching Strategies for Django

    Choosing the right strategy determines how much performance you actually gain. Below are the most common patterns.

    Per‑View Caching

    Ideal for pages that rarely change (e.g., static landing pages). Use Django’s cache_page decorator:

    from django.views.decorators.cache import cache_page
    
    @cache_page(60 * 15)  # cache for 15 minutes
    def home(request):
        # Expensive DB queries here
        return render(request, "home.html")

    Template Fragment Caching

    When only a part of a page is expensive, wrap the fragment with the cache template tag.

    {% load cache %}
    {% cache 300 sidebar %}
        {% include "sidebar.html" %}
    {% endcache %}

    Low‑Level API Caching

    For custom logic, use cache.set() and cache.get() directly. This gives you full control over keys and TTLs.

    # Example: Caching a queryset result
    def get_recent_posts():
        cache_key = "recent_posts"
        posts = cache.get(cache_key)
        if posts is None:
            posts = Post.objects.filter(published=True).order_by("-created")[:10]
            cache.set(cache_key, list(posts), 300)  # store for 5 minutes
        return posts

    Measuring Performance Improvements

    Before you claim a speed boost, quantify the impact. Here’s a simple benchmark workflow:

    1. Record baseline response time with django-debug-toolbar or ab (ApacheBench).
    2. Enable Redis caching for the target view or fragment.
    3. Run the same load test and compare average latency and DB query count.

    A typical Django‑Redis setup can reduce database queries by 70‑90% and cut page load times from 800 ms to under 150 ms under moderate traffic.

    Advanced Performance Tweaks

    Use Connection Pooling

    Creating a new socket for every request adds overhead. Enable pooling in the OPTIONS section (as shown in the settings example) to reuse connections.

    Leverage Redis Pipelines

    When you need to read or write many keys in a single request, pipelines batch commands to reduce round‑trip latency.

    with redis_client.pipeline() as pipe:
        pipe.get('key1')
        pipe.get('key2')
        pipe.set('key3', 'value')
        results = pipe.execute()

    Set Appropriate TTLs

    Never let stale data linger. Use short TTLs for rapidly changing content and longer TTLs for static assets. You can also implement cache busting by including a version number in the key:

    cache_key = f"product_{product.id}_v{product.version}"
    cache.set(cache_key, product_data, 86400)  # 1‑day TTL

    Enable Redis LRU Eviction

    If your memory is limited, configure Redis to evict the least‑recently‑used keys automatically:

    # redis.conf
    maxmemory 2gb
    maxmemory-policy allkeys-lru

    Common Pitfalls and How to Avoid Them

    • Cache stampede: When a popular key expires, many requests may hit the database simultaneously. Mitigate with locking or stale‑while‑revalidate patterns.
    • Serializing complex objects: Django’s default cache backend uses pickle, which can be slower and insecure. Consider JSON or MessagePack for predictable data structures.
    • Missing cache invalidation: Forgetting to clear or update cached data leads to inconsistent UI. Use Django signals (e.g., post_save) to invalidate related keys automatically.
    • Over‑caching: Caching tiny fragments can waste memory without measurable benefit. Profile first, then cache the heaviest parts.

    Real‑World Example: Scaling an E‑Commerce Site

    Imagine an online store handling 10,000 concurrent users during a flash sale. The product listing page performs three heavy database joins and renders a personalized recommendation carousel. By applying the following Redis‑based optimizations, the site achieved dramatic performance gains:

    • Per‑view cache for the product list (TTL = 30 seconds).
    • Fragment cache for the recommendation carousel, refreshed only when a user’s purchase history changes.
    • Low‑level cache for price calculations, using Redis hashes to store {product_id: price} pairs.
    • Connection pooling with max_connections=200 to handle burst traffic.

    Post‑deployment metrics:

    • Average page load time dropped from 1.2 seconds to 0.28 seconds.
    • Database query count per request fell from 12 to 2.
    • CPU usage on the app server decreased by 35%, allowing the same hardware to serve 2× more users.

    Monitoring and Maintaining Redis Health

    Performance is only sustainable with proper monitoring. Integrate these tools into your Django ops stack:

    • Redis INFO command: Exposes memory usage, hit/miss ratios, and evicted keys.
    • Prometheus + Grafana: Collects metrics like redis_keyspace_hits_total and visualizes trends.
    • django-redis‑monitor: Provides a Django admin view for real‑time cache statistics.

    Set alerts for hit‑rate drops below 90% or memory usage approaching the maxmemory limit, as these indicate potential bottlenecks.

    Conclusion

    Implementing Python Django Redis caching is one of the most effective ways to boost web‑application performance. By configuring Redis as the default cache backend, selecting the right caching strategy, and fine‑tuning connection pooling, TTLs, and eviction policies, you can achieve sub‑second response times even under heavy load. Remember to monitor hit ratios, avoid common pitfalls like cache stampedes, and continuously profile your code to ensure that every cached piece delivers real value. With these best practices in place, your Django project will be faster, more scalable, and ready to handle the traffic spikes of tomorrow.

  • Python Django Celery Background Tasks

    When you build a Django web application, you quickly discover that not every operation belongs in the request‑response cycle. Sending emails, generating PDFs, processing images, or syncing data with external APIs are tasks that can and should run in the background. Celery—the powerful, open‑source asynchronous task queue—pairs perfectly with Django to offload these workloads, improve user experience, and keep your site responsive. In this guide we’ll explore everything you need to know about Python Django Celery background tasks, from installation and configuration to best practices for scaling and monitoring.

    Why Use Celery with Django?

    • Asynchronous execution: Run long‑running jobs outside the HTTP request, preventing timeouts.
    • Scalable architecture: Distribute work across multiple worker processes or machines.
    • Reliable retries: Automatic retry logic for transient failures.
    • Scheduled tasks: Use celery beat to run periodic jobs, replacing cron for Python‑centric workflows.
    • Rich ecosystem: Supports many brokers (Redis, RabbitMQ, Amazon SQS) and result backends (Django ORM, Redis, Memcached).

    Core Concepts You Should Know

    Broker

    The broker is the message transport that Celery uses to send tasks from Django to workers. The most common choices are Redis and RabbitMQ. The broker stores the task request until a worker picks it up.

    Worker

    A worker is a long‑running process that consumes tasks from the broker, executes the Python function, and optionally stores the result. Workers can be scaled horizontally by launching more instances or by adding concurrency threads/greenlets.

    Task

    In Celery terminology a task is a regular Python callable decorated with @shared_task (or @app.task). Celery serializes the function name, arguments, and execution options into a message that the broker transports.

    Result Backend

    If you need to retrieve the outcome of a task later, you configure a result backend. Django’s ORM backend stores results in the database, while Redis or Memcached provide fast, in‑memory storage.

    Step‑by‑Step Setup for Django + Celery

    1. Install Packages

    pip install celery[redis] django-celery-beat

    The celery[redis] extra includes the Redis broker client, and django-celery-beat adds a Django admin UI for periodic tasks.

    2. Create a Celery Application

    In your Django project root (next to settings.py) create a celery.py file:

    import os
    from celery import Celery
    
    # Set default Django settings module for 'celery' program.
    os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'myproject.settings')
    
    app = Celery('myproject')
    
    # Using a string here means the worker doesn’t have to serialize
    # the configuration object to the child processes.
    app.config_from_object('django.conf:settings', namespace='CELERY')
    
    # Autodiscover tasks from all installed apps.
    app.autodiscover_tasks()
    

    3. Update __init__.py

    Add the following line so that Django loads Celery when it starts:

    from .celery import app as celery_app
    
    __all__ = ('celery_app',)

    4. Configure Settings

    In settings.py add the Celery configuration block:

    # Broker URL – replace with your own Redis or RabbitMQ address.
    CELERY_BROKER_URL = 'redis://localhost:6379/0'
    
    # Result backend (optional, but useful for tracking task status).
    CELERY_RESULT_BACKEND = 'django-db'
    
    # Optional: serialize using JSON (default) for better interoperability.
    CELERY_ACCEPT_CONTENT = ['json']
    CELERY_TASK_SERIALIZER = 'json'
    CELERY_RESULT_SERIALIZER = 'json'
    
    # Enable UTC and timezone support.
    CELERY_TIMEZONE = 'UTC'
    CELERY_ENABLE_UTC = True
    
    # Load Django-Celery-Beat schedule from the database.
    INSTALLED_APPS += [
        'django_celery_beat',
        'django_celery_results',
    ]
    

    5. Define a Sample Task

    Create a tasks.py file inside any Django app (e.g., myapp/tasks.py) and add:

    from celery import shared_task
    import time
    
    @shared_task
    def send_welcome_email(user_id):
        # Simulate a time‑consuming operation.
        time.sleep(5)
        # Here you would fetch the user and send an email.
        return f'Welcome email sent to user {user_id}'
    

    6. Call the Task Asynchronously

    From a view, signal, or management command you can trigger the background job:

    from myapp.tasks import send_welcome_email
    
    def register_user(request):
        # ... user creation logic ...
        user_id = new_user.id
        # Queue the email without blocking the request.
        send_welcome_email.delay(user_id)
        return HttpResponse('User created, welcome email queued.')
    

    7. Run the Worker and Beat Scheduler

    # Start a Celery worker (adjust concurrency as needed).
    celery -A myproject worker -l info
    
    # In another terminal, start the beat scheduler for periodic tasks.
    celery -A myproject beat -l info
    

    Both commands can be combined with --beat or managed via a process supervisor like systemd**, **supervisord**, or **Docker Compose**.

    Scheduling Periodic Tasks with Django‑Celery‑Beat

    Celery Beat replaces traditional cron by storing schedules in the database, making it easy to edit via the Django admin.

    1. Run migrations for django_celery_beat:
      python manage.py migrate django_celery_beat
    2. In the admin, navigate to “Periodic tasks” → “Add periodic task”.
    3. Select the task (e.g., myapp.tasks.send_welcome_email), set the schedule (crontab, interval, or solar), and save.

    Behind the scenes, Beat reads the schedule, creates task messages, and pushes them to the broker exactly like any other task.

    Best Practices for Production‑Ready Celery

    1. Choose the Right Broker

    • Redis: Easy to set up, ideal for small‑to‑medium workloads.
    • RabbitMQ: Handles high‑throughput, supports advanced routing, and provides stronger delivery guarantees.

    2. Separate Queues for Different Workloads

    Use named queues to isolate CPU‑intensive image processing from quick email notifications:

    # tasks.py
    @shared_task(queue='emails')
    def send_email(...):
        ...
    
    @shared_task(queue='images')
    def generate_thumbnail(...):
        ...

    Then start workers with specific queues:

    celery -A myproject worker -Q emails -l info
    celery -A myproject worker -Q images -l info

    3. Set Time Limits and Soft Time Limits

    Prevent runaway tasks from hogging resources:

    CELERY_TASK_TIME_LIMIT = 300        # Hard limit (seconds)
    CELERY_TASK_SOFT_TIME_LIMIT = 250  # Soft limit (raises SoftTimeLimitExceeded)

    4. Enable Automatic Retries

    Network‑related tasks often fail transiently. Use autoretry_for and exponential backoff:

    @shared_task(
        autoretry_for=(ConnectionError,),
        retry_backoff=True,
        retry_kwargs={'max_retries': 5}
    )
    def fetch_remote_data(url):
        response = requests.get(url, timeout=10)
        response.raise_for_status()
        return response.json()
    

    5. Monitor Workers with Flower

    Flower provides a real‑time web UI for Celery:

    pip install flower
    celery -A myproject flower

    Visit http://localhost:5555 to view task history, worker status, and queue lengths.

    6. Use Docker for Consistent Environments

    A typical Docker Compose setup includes services for web, worker, beat, and the broker:

    version: '3.8'
    services:
      redis:
        image: redis:7-alpine
        ports: ['6379:6379']
    
      web:
        build: .
        command: gunicorn myproject.wsgi:application --bind 0.0.0.0:8000
        env_file: .env
        depends_on: [redis]
    
      worker:
        build: .
        command: celery -A myproject worker -l info
        env_file: .env
        depends_on: [redis]
    
      beat:
        build: .
        command: celery -A myproject beat -l info
        env_file: .env
        depends_on: [redis]
    

    Common Pitfalls and How to Avoid Them

    • Missing __init__.py import: Forgetting to import
  • Python Django Stripe Payment Integration

    Integrating Stripe payments into a Python Django application is one of the most effective ways to monetize your web services while providing a smooth, secure checkout experience. In this guide we’ll walk through every step of the Python Django Stripe payment integration process—from installing the required packages to handling webhooks and testing in live mode. By the end, you’ll have a production‑ready payment flow that scales with your business and boosts conversion rates.

    Why Choose Stripe for Django Projects?

    • Developer‑friendly API: Stripe’s well‑documented REST endpoints and official Python SDK make it easy to implement complex payment flows.
    • PCI‑DSS compliance: By using Stripe Elements or Checkout, sensitive card data never touches your server, reducing compliance burden.
    • Global support: Accept credit cards, debit cards, Apple Pay, Google Pay, and local payment methods in over 135 currencies.
    • Extensible features: Subscriptions, one‑time payments, coupons, and tax calculations are built‑in.

    Prerequisites

    Before diving into code, ensure you have the following ready:

    1. A Python 3.9+ environment.
    2. Django 3.2 or newer installed.
    3. A Stripe account (sign up at dashboard.stripe.com).
    4. Basic knowledge of Django models, views, and templates.

    Step 1: Install Stripe and Django Packages

    First, add the official Stripe Python library to your project and make sure Django is up to date.

    pip install stripe django-environ

    We’ll use django-environ to keep API keys out of the source code.

    Step 2: Configure Environment Variables

    Create a .env file at the root of your project and add your Stripe keys:

    # .env
    STRIPE_PUBLIC_KEY=pk_test_XXXXXXXXXXXXXXXXXXXXXXXX
    STRIPE_SECRET_KEY=sk_test_XXXXXXXXXXXXXXXXXXXXXXXX
    STRIPE_WEBHOOK_SECRET=whsec_XXXXXXXXXXXXXXXXXXXXXXXX

    Load these variables in settings.py:

    import environ
    env = environ.Env()
    environ.Env.read_env()
    
    STRIPE_PUBLIC_KEY = env('STRIPE_PUBLIC_KEY')
    STRIPE_SECRET_KEY = env('STRIPE_SECRET_KEY')
    STRIPE_WEBHOOK_SECRET = env('STRIPE_WEBHOOK_SECRET')

    Step 3: Set Up a Simple Product Model

    Even if you’re selling a digital download or a subscription, a model helps keep the code clean.

    from django.db import models
    
    class Product(models.Model):
        name = models.CharField(max_length=255)
        description = models.TextField(blank=True)
        price_cents = models.PositiveIntegerField(help_text="Price in cents")
        stripe_price_id = models.CharField(max_length=255, blank=True)
    
        def __str__(self):
            return self.name

    After creating migrations, run python manage.py migrate. Populate a few products via the admin or Django shell and note their price_cents values.

    Step 4: Create Stripe Prices for Your Products

    Stripe recommends creating a Price object for each product. You can do this manually in the dashboard or programmatically. Below is a one‑off script you can run once:

    import stripe
    from django.conf import settings
    from myapp.models import Product
    
    stripe.api_key = settings.STRIPE_SECRET_KEY
    
    for product in Product.objects.all():
        price = stripe.Price.create(
            unit_amount=product.price_cents,
            currency='usd',
            product_data={'name': product.name},
        )
        product.stripe_price_id = price.id
        product.save()
        print(f'Created Stripe price {price.id} for {product.name}')

    Step 5: Build the Checkout View

    We’ll use Stripe Checkout for a frictionless UI. The view creates a Checkout Session and redirects the user.

    from django.shortcuts import get_object_or_404, redirect
    from django.views import View
    import stripe
    from django.conf import settings
    
    stripe.api_key = settings.STRIPE_SECRET_KEY
    
    class CreateCheckoutSessionView(View):
        def post(self, request, *args, **kwargs):
            product_id = request.POST.get('product_id')
            product = get_object_or_404(Product, pk=product_id)
    
            session = stripe.checkout.Session.create(
                payment_method_types=['card'],
                line_items=[{
                    'price': product.stripe_price_id,
                    'quantity': 1,
                }],
                mode='payment',
                success_url=request.build_absolute_uri('/success/') + '?session_id={CHECKOUT_SESSION_ID}',
                cancel_url=request.build_absolute_uri('/cancel/'),
            )
            return redirect(session.url, code=303)

    Step 6: Add URLs and Templates

    Map the view and create simple templates for product listing, success, and cancel pages.

    # urls.py
    from django.urls import path
    from .views import CreateCheckoutSessionView, success_view, cancel_view
    
    urlpatterns = [
        path('checkout/', CreateCheckoutSessionView.as_view(), name='checkout'),
        path('success/', success_view, name='success'),
        path('cancel/', cancel_view, name='cancel'),
    ]
    

    Example product list template ( product_list.html ):

    <h2>Available Products</h2>
    <ul>
    {% for product in products %}
        <li>
            <strong>{{ product.name }}</strong> – ${{ product.price_cents|floatformat:2|divisibleby:100 }}
    <form action="{% url 'checkout' %}" method="post"> {% csrf_token %} <input type="hidden" name="product_id" value="{{ product.id }}"> <button type="submit">Buy Now</button> </form> </li> {% endfor %} </ul>

    Step 7: Handling Stripe Webhooks

    Webhooks let your app react to asynchronous events such as successful payments, refunds, or disputes. Create a dedicated endpoint that validates the signature and updates order status.

    import json
    from django.http import HttpResponse, HttpResponseBadRequest
    from django.views.decorators.csrf import csrf_exempt
    import stripe
    from django.conf import settings
    
    stripe.api_key = settings.STRIPE_SECRET_KEY
    
    @csrf_exempt
    def stripe_webhook(request):
        payload = request.body
        sig_header = request.META.get('HTTP_STRIPE_SIGNATURE')
        try:
            event = stripe.Webhook.construct_event(
                payload, sig_header, settings.STRIPE_WEBHOOK_SECRET
            )
        except (ValueError, stripe.error.SignatureVerificationError):
            return HttpResponseBadRequest('Invalid payload or signature')
    
        # Handle the event
        if event['type'] == 'checkout.session.completed':
            session = event['data']['object']
            # Example: mark order as paid
            handle_successful_payment(session)
        # Add more event types as needed
    
        return HttpResponse(status=200)
    
    def handle_successful_payment(session):
        # Retrieve the line items to know which product was bought
        line_items = stripe.checkout.Session.list_line_items(session['id'])
        for item in line_items['data']:
            # Here you could create an Order model instance, send email, etc.
            print(f"Payment for Stripe price {item['price']['id']} succeeded.")

    Don’t forget to register the webhook URL in your Stripe dashboard and enable the events you plan to handle (e.g., checkout.session.completed, invoice.payment_failed).

    Step 8: Testing the Integration

    Stripe provides a set of test cards that simulate various scenarios. Follow these steps to ensure everything works before going live:

    • Set STRIPE_PUBLIC_KEY and STRIPE_SECRET_KEY to the test keys from your dashboard.
    • Use the test card 4242 4242 4242 4242 with any future expiration date and any CVC.
    • Trigger edge cases: 4000 0000 0000 0341 for a declined card, 4000 0000 0000 9995 for a charge that requires authentication.
    • Check the webhook endpoint locally with Stripe CLI:
      stripe listen --forward-to localhost:8000/webhook/

    Step 9: Going Live

    When you’re ready to accept real payments, swap the test keys for the live ones in your .env file. Also, verify the following:

    • All URLs (success, cancel, webhook) are served over HTTPS.
    • Your webhook endpoint is reachable from the public internet.
    • Compliance: Ensure you have a clear refund policy and display it to customers.

    After the switch, monitor the Stripe Dashboard for any unexpected errors and adjust your webhook handling as needed.

    Common Pitfalls & How to Avoid Them

    1. Storing Card Data Locally

    Never store raw card numbers or CVC codes. Use Stripe Elements or Checkout, which tokenizes the data for you.

    2. Forgetting to Verify Webhook Signatures

    Skipping signature verification opens the door to forged events. Always use stripe.Webhook.construct_event

  • Python Django Social Media App From Scratch

    Building a social media app with Python Django from scratch might sound daunting, but with a clear roadmap and the right tools, you can launch a fully functional platform that rivals the big players. In this step‑by‑step guide we’ll cover everything you need—from setting up your development environment to deploying a production‑ready application. Whether you’re a seasoned Django developer or a curious beginner, you’ll walk away with a solid foundation, reusable code snippets, and SEO‑friendly practices that help your app rank higher in search results.

    Why Choose Django for a Social Media App?

    Django’s “batteries‑included” philosophy makes it an ideal choice for complex, data‑driven projects. Here are a few reasons why developers love Django for social networking sites:

    • Rapid development: Built‑in admin, ORM, and authentication reduce boilerplate.
    • Scalability: Handles high traffic with proper caching and async support.
    • Security: Protection against XSS, CSRF, and SQL injection out of the box.
    • Community & Packages: Rich ecosystem (e.g., django‑rest‑framework, django‑channels) accelerates feature implementation.

    1. Setting Up the Development Environment

    Prerequisites

    1. Python 3.10 or later
    2. Virtualenv or pipenv
    3. Git for version control
    4. Node.js (optional, for front‑end tooling)

    Installation Steps

    # Create and activate a virtual environment
    python -m venv venv
    source venv/bin/activate   # On Windows use `venv\Scripts\activate`
    
    # Install Django and supporting packages
    pip install django djangorestframework django-channels psycopg2-binary
    
    # Start a new project
    django-admin startproject socialapp
    cd socialapp
    
    # Create the core app
    python manage.py startapp core
    

    2. Designing the Project Structure

    A clean layout makes maintenance easier. Below is a recommended file hierarchy:

    socialapp/
    │
    ├─ core/                 # Main app (models, views, serializers)
    │   ├─ migrations/
    │   ├─ templates/
    │   │   └─ core/
    │   ├─ static/
    │   │   └─ core/
    │   ├─ admin.py
    │   ├─ models.py
    │   ├─ views.py
    │   ├─ urls.py
    │   └─ consumers.py      # For WebSocket handling
    │
    ├─ socialapp/            # Project settings
    │   ├─ settings.py
    │   ├─ urls.py
    │   └─ asgi.py
    │
    ├─ requirements.txt
    └─ manage.py
    

    3. Implementing User Authentication

    Django’s built‑in User model covers most needs, but extending it gives you flexibility for profile pictures, bio, and follower relationships.

    Custom User Model

    # core/models.py
    from django.contrib.auth.models import AbstractUser
    from django.db import models
    
    class CustomUser(AbstractUser):
        bio = models.CharField(max_length=255, blank=True)
        avatar = models.ImageField(upload_to='avatars/', null=True, blank=True)
    
        def __str__(self):
            return self.username
    

    Remember to update settings.py:

    # socialapp/settings.py
    AUTH_USER_MODEL = 'core.CustomUser'
    

    Registration & Login Views

    # core/views.py
    from django.contrib.auth import login, authenticate
    from django.shortcuts import render, redirect
    from .forms import SignUpForm
    
    def signup_view(request):
        if request.method == 'POST':
            form = SignUpForm(request.POST, request.FILES)
            if form.is_valid():
                user = form.save()
                login(request, user)
                return redirect('feed')
        else:
            form = SignUpForm()
        return render(request, 'core/signup.html', {'form': form})
    

    4. Core Models: Posts, Comments, Likes, and Followers

    Post Model

    # core/models.py (continued)
    class Post(models.Model):
        author = models.ForeignKey('core.CustomUser', on_delete=models.CASCADE, related_name='posts')
        content = models.TextField()
        image = models.ImageField(upload_to='posts/', blank=True, null=True)
        created_at = models.DateTimeField(auto_now_add=True)
    
        class Meta:
            ordering = ['-created_at']
    
        def __str__(self):
            return f'{self.author.username}: {self.content[:30]}'
    

    Comment and Like Models

    class Comment(models.Model):
        post = models.ForeignKey(Post, on_delete=models.CASCADE, related_name='comments')
        author = models.ForeignKey('core.CustomUser', on_delete=models.CASCADE)
        body = models.CharField(max_length=300)
        created_at = models.DateTimeField(auto_now_add=True)
    
    class Like(models.Model):
        post = models.ForeignKey(Post, on_delete=models.CASCADE, related_name='likes')
        user = models.ForeignKey('core.CustomUser', on_delete=models.CASCADE)
        created_at = models.DateTimeField(auto_now_add=True)
    
        class Meta:
            unique_together = ('post', 'user')
    

    Followers (Self‑Referencing Many‑To‑Many)

    class Follow(models.Model):
        follower = models.ForeignKey('core.CustomUser', related_name='following', on_delete=models.CASCADE)
        following = models.ForeignKey('core.CustomUser', related_name='followers', on_delete=models.CASCADE)
        created_at = models.DateTimeField(auto_now_add=True)
    
        class Meta:
            unique_together = ('follower', 'following')
    

    5. Building Views, Serializers, and URLs

    RESTful API with Django REST Framework

    # core/serializers.py
    from rest_framework import serializers
    from .models import Post, Comment, Like
    
    class PostSerializer(serializers.ModelSerializer):
        author = serializers.StringRelatedField(read_only=True)
        likes_count = serializers.IntegerField(source='likes.count', read_only=True)
        comments_count = serializers.IntegerField(source='comments.count', read_only=True)
    
        class Meta:
            model = Post
            fields = ['id', 'author', 'content', 'image', 'created_at', 'likes_count', 'comments_count']
    

    API ViewSet

    # core/views.py (API part)
    from rest_framework import viewsets, permissions
    from .models import Post
    from .serializers import PostSerializer
    
    class PostViewSet(viewsets.ModelViewSet):
        queryset = Post.objects.select_related('author').prefetch_related('likes', 'comments')
        serializer_class = PostSerializer
        permission_classes = [permissions.IsAuthenticatedOrReadOnly]
    
        def perform_create(self, serializer):
            serializer.save(author=self.request.user)
    

    URL Configuration

    # core/urls.py
    from django.urls import path, include
    from rest_framework.routers import DefaultRouter
    from . import views
    
    router = DefaultRouter()
    router.register(r'posts', views.PostViewSet, basename='post')
    
    urlpatterns = [
        path('api/', include(router.urls)),
        path('signup/', views.signup_view, name='signup'),
        # Additional routes for login, logout, profile, etc.
    ]
    

    6. Crafting Responsive Templates with Bootstrap

    Using a modern CSS framework speeds up UI development and improves SEO through clean markup.

    <!-- core/templates/core/feed.html -->
    {% extends "base.html" %}
    {% block content %}
    
    <h2 class="mb-3">Your Feed</h2> {% for post in posts %} <div class="card mb-3"> <div class="card-body"> <h5 class="card-title">{{ post.author.username }}</h5> <p class="card-text">{{ post.content|linebreaks }}</p> {% if post.image %} <img src="{{ post.image.url }}" class="img-fluid" alt="Post image"> {% endif %} <small class="text-muted">{{ post.created_at|naturaltime }}</small> </div> </div> {% empty %} <p>No posts yet. Follow someone or create a new post!</p> {% endfor %}
    {% endblock %}

    7. Adding Real‑Time Features with Django Channels

    Live notifications and chat are essential for a modern social experience.

    Install and Configure Channels

    # Install
    pip install channels

    # socialapp/asgi.py
    import os
    from django.core.asgi import get_asgi_application
    from channels