<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0">
<channel>
<title>Coder Legion - Recent posts tagged fastapi</title>
<link>https://coderlegion.com/tag/fastapi</link>
<description>Powered by Question2Answer</description>
<item>
<title>Building an ATS Resume Scorer API with FastAPI, Celery, and Docker</title>
<link>https://coderlegion.com/33059/building-an-ats-resume-scorer-api-with-fastapi-celery-and-docker</link>
<description>&lt;p&gt;Most resumes are filtered by ATS software before a human ever reads them. This project is a backend API that checks how well a resume matches a job description and suggests how to improve it.&lt;/p&gt;
&lt;h3 id=&quot;what-it-does&quot;&gt;What it does&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;PDF resume parsing:&lt;/strong&gt; extracts text from uploaded resumes using pdfplumber&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ATS keyword matching:&lt;/strong&gt; compares the resume against a job description and calculates a match score&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Smart suggestions:&lt;/strong&gt; generates actionable tips to improve the resume for a specific role&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;JWT authentication:&lt;/strong&gt; secured endpoints with role-based access for Job Seekers and Recruiters&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Batch scoring:&lt;/strong&gt; recruiters can score many resumes at once, handled in the background by Celery + Redis&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;tech-stack&quot;&gt;Tech stack&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Layer &lt;/th&gt;&lt;th&gt;Technology&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Backend &lt;/td&gt;&lt;td&gt;Python, FastAPI&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Auth &lt;/td&gt;&lt;td&gt;JWT (python-jose, passlib)&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Database &lt;/td&gt;&lt;td&gt;MySQL 8.0, SQLAlchemy ORM, Alembic&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Task queue &lt;/td&gt;&lt;td&gt;Celery + Redis&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;PDF parsing &lt;/td&gt;&lt;td&gt;pdfplumber&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;DevOps &lt;/td&gt;&lt;td&gt;Docker, Docker Compose, AWS EC2&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;CI/CD &lt;/td&gt;&lt;td&gt;GitHub Actions&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Testing &lt;/td&gt;&lt;td&gt;Pytest&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id=&quot;design-highlights&quot;&gt;Design highlights&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Background processing:&lt;/strong&gt; batch scoring runs through a Celery queue, so the API stays responsive while many resumes are processed.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Role-based access:&lt;/strong&gt; Job Seekers and Recruiters get separate permissions on the same API.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Reproducible setup:&lt;/strong&gt; the whole stack (API, MySQL, Redis) starts with a single Docker Compose command.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Automated deployment:&lt;/strong&gt; a GitHub Actions pipeline deploys automatically to AWS EC2.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Database migrations:&lt;/strong&gt; schema changes are managed with Alembic.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;source-code&quot;&gt;Source code&lt;/h3&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/priyadharshiny13/ats-resume-scorer&quot;&gt;https://github.com/priyadharshiny13/ats-resume-scorer&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Feedback is welcome. If you were using a tool like this, what would matter more: a more accurate match score or more detailed improvement suggestions?&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/33059/building-an-ats-resume-scorer-api-with-fastapi-celery-and-docker</guid>
<pubDate>Fri, 30 Oct 2026 07:57:26 +0000</pubDate>
</item>
<item>
<title>How to Build a Multi User Dashboard with Role-Based Access</title>
<link>https://coderlegion.com/31802/how-to-build-a-multi-user-dashboard-with-role-based-access</link>
<description>&lt;p&gt;Modern web applications often serve multiple types of users, including administrators, managers, employees, customers, and external partners. Although these users may work within the same application, they do not necessarily need access to the same features, information, or operations.&lt;/p&gt;
&lt;p&gt;For example, an administrator might need to manage users, review reports, and configure application settings. A manager may need to monitor team performance and assign tasks, while an employee may only need to view assigned projects and update their progress. Giving every user the same level of access creates unnecessary security risks and makes the application more difficult to manage.&lt;/p&gt;
&lt;p&gt;This is where a multi-user dashboard with Role-Based Access Control (RBAC) becomes valuable.&lt;/p&gt;
&lt;p&gt;RBAC is a security approach that assigns permissions to roles rather than granting individual permissions to every user separately. When a user signs in, the application determines their role and allows them to access the appropriate features and resources.&lt;/p&gt;
&lt;p&gt;In this article, we will explore how to build a secure, scalable multi-user dashboard using React, FastAPI, PostgreSQL, and JSON Web Tokens (JWT). We will examine the architecture, database schema, authentication workflow, frontend implementation, backend authorization, testing strategies, and deployment considerations.&lt;/p&gt;
&lt;p&gt;By the end, you will understand how to create a dashboard that supports multiple user roles while maintaining a clear separation between authentication, authorization, and business logic.&lt;/p&gt;
&lt;h3 id=&quot;1-understanding-multi-user-dashboards&quot;&gt;1. Understanding Multi-User Dashboards&lt;/h3&gt;
&lt;p&gt;A multi-user dashboard is a centralized interface where different users can interact with an application according to their responsibilities and access rights.&lt;/p&gt;
&lt;p&gt;Unlike a basic dashboard that displays identical information to everyone, a role-aware dashboard adapts its navigation, available actions, and data visibility to the authenticated user.&lt;/p&gt;
&lt;p&gt;Consider a project management platform with three roles:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Administrator&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Create, update, and deactivate user accounts.&lt;/li&gt;
&lt;li&gt;Assign roles and permissions.&lt;/li&gt;
&lt;li&gt;Access organization-wide reports.&lt;/li&gt;
&lt;li&gt;Configure application settings.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Manager&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;View team performance.&lt;/li&gt;
&lt;li&gt;Create and assign tasks.&lt;/li&gt;
&lt;li&gt;Review employee progress.&lt;/li&gt;
&lt;li&gt;Access reports related to their team.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Employee&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;View assigned tasks.&lt;/li&gt;
&lt;li&gt;Update task status.&lt;/li&gt;
&lt;li&gt;Edit permitted profile information.&lt;/li&gt;
&lt;li&gt;Access personal performance metrics.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Each role has a different set of responsibilities. The dashboard should reflect those differences without requiring separate applications for every user type.&lt;/p&gt;
&lt;p&gt;A well-designed multi-user dashboard offers several benefits:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Improved security:&lt;/strong&gt; Users receive only the permissions necessary for their responsibilities.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Better user experience:&lt;/strong&gt; Unnecessary menus and actions remain hidden.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Simplified administration:&lt;/strong&gt; Permissions can be managed centrally.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Scalability:&lt;/strong&gt; New roles and features can be introduced without redesigning the entire application.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Maintainability:&lt;/strong&gt; Access rules remain separate from presentation components and business logic.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;However, hiding a button is not sufficient security. The backend must independently validate every protected operation.&lt;/p&gt;
&lt;h3 id=&quot;2-choosing-the-technology-stack&quot;&gt;2. Choosing the Technology Stack&lt;/h3&gt;
&lt;p&gt;Before writing code, it is important to choose technologies that support maintainability, security, and future growth.&lt;/p&gt;
&lt;p&gt;For this project, we will use the following stack.&lt;/p&gt;
&lt;h4 id=&quot;react&quot;&gt;React&lt;/h4&gt;
&lt;p&gt;React provides a component-based approach to building interactive interfaces. Components such as navigation menus, data tables, statistics cards, and protected pages can be reused across different parts of the dashboard.&lt;/p&gt;
&lt;p&gt;React is particularly useful when different roles require different interfaces but share common layout components.&lt;/p&gt;
&lt;h4 id=&quot;fastapi&quot;&gt;FastAPI&lt;/h4&gt;
&lt;p&gt;FastAPI is a Python framework for building APIs. It supports request validation, dependency injection, asynchronous programming, and automatic API documentation.&lt;/p&gt;
&lt;p&gt;We can use FastAPI dependencies to verify authenticated users and enforce role-based permissions before executing protected endpoints.&lt;/p&gt;
&lt;h4 id=&quot;postgresql&quot;&gt;PostgreSQL&lt;/h4&gt;
&lt;p&gt;PostgreSQL stores users, roles, permissions, and application data. Its relational structure makes it suitable for applications where users belong to teams or organizations and access depends on several related records.&lt;/p&gt;
&lt;h4 id=&quot;sqlalchemy&quot;&gt;SQLAlchemy&lt;/h4&gt;
&lt;p&gt;SQLAlchemy provides an object-relational mapping layer that allows Python code to interact with PostgreSQL using models and database queries.&lt;/p&gt;
&lt;h4 id=&quot;jwt-and-password-hashing&quot;&gt;JWT and password hashing&lt;/h4&gt;
&lt;p&gt;JWT access tokens can be used to authenticate API requests. Passwords should be stored using a dedicated password-hashing algorithm such as Argon2id or bcrypt, never as plain text.&lt;/p&gt;
&lt;h4 id=&quot;additional-tools&quot;&gt;Additional tools&lt;/h4&gt;
&lt;p&gt;A production application may also use:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;React Router for client-side navigation.&lt;/li&gt;
&lt;li&gt;Pydantic for data validation.&lt;/li&gt;
&lt;li&gt;Alembic for database migrations.&lt;/li&gt;
&lt;li&gt;Pytest for backend tests.&lt;/li&gt;
&lt;li&gt;Vitest and React Testing Library for frontend tests.&lt;/li&gt;
&lt;li&gt;Docker for consistent deployment environments.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The specific libraries can change, but the underlying principles of authentication, authorization, and least-privilege access remain the same.&lt;/p&gt;
&lt;h3 id=&quot;3-designing-the-application-architecture&quot;&gt;3. Designing the Application Architecture&lt;/h3&gt;
&lt;p&gt;A reliable dashboard separates the user interface, API, database, and authorization logic.&lt;/p&gt;
&lt;p&gt;The basic request flow looks like this:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;A user opens the React application.&lt;/li&gt;
&lt;li&gt;The user submits their login credentials.&lt;/li&gt;
&lt;li&gt;FastAPI verifies the credentials.&lt;/li&gt;
&lt;li&gt;The server issues an appropriate authentication token or session.&lt;/li&gt;
&lt;li&gt;React requests the current user's profile and permitted dashboard data.&lt;/li&gt;
&lt;li&gt;FastAPI authenticates each request and checks the required permissions.&lt;/li&gt;
&lt;li&gt;PostgreSQL returns only the data the user is authorized to access.&lt;/li&gt;
&lt;li&gt;React renders the permitted information and available actions.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This separation is important because the frontend should never be the ultimate authority for access decisions.&lt;/p&gt;
&lt;p&gt;For example, an employee might modify a JavaScript variable to pretend to be an administrator. If the backend trusts the role supplied by the browser, the employee could access restricted functionality.&lt;/p&gt;
&lt;p&gt;Instead, the backend must derive the authenticated identity from a verified session or token and enforce permissions independently.&lt;/p&gt;
&lt;p&gt;A practical project structure could look like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;multi-user-dashboard/
├── frontend/
│   ├── src/
│   │   ├── components/
│   │   │   ├── Sidebar.jsx
│   │   │   ├── Header.jsx
│   │   │   └── ProtectedRoute.jsx
│   │   ├── pages/
│   │   │   ├── Login.jsx
│   │   │   ├── Dashboard.jsx
│   │   │   ├── Users.jsx
│   │   │   └── Reports.jsx
│   │   ├── services/
│   │   │   └── api.js
│   │   ├── auth/
│   │   │   └── AuthContext.jsx
│   │   └── App.jsx
│   └── package.json
├── backend/
│   ├── app/
│   │   ├── main.py
│   │   ├── models.py
│   │   ├── schemas.py
│   │   ├── database.py
│   │   ├── security.py
│   │   ├── permissions.py
│   │   └── routes/
│   │       ├── auth.py
│   │       ├── users.py
│   │       └── reports.py
│   └── requirements.txt
├── tests/
└── docker-compose.yml
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This organization keeps related responsibilities together and prevents the main application file from becoming difficult to maintain.&lt;/p&gt;
&lt;h3 id=&quot;4-designing-the-database-schema&quot;&gt;4. Designing the Database Schema&lt;/h3&gt;
&lt;p&gt;Database design is one of the most important parts of a role-based dashboard.&lt;/p&gt;
&lt;p&gt;A simple application can begin with a users table containing a role column. However, a growing application benefits from separate tables for users, roles, and permissions.&lt;/p&gt;
&lt;p&gt;A flexible design uses four main tables:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Users:&lt;/strong&gt; Stores account information.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Roles:&lt;/strong&gt; Defines roles such as administrator, manager, and employee.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Permissions:&lt;/strong&gt; Defines individual actions, such as &lt;code&gt;users.read&lt;/code&gt;, &lt;code&gt;users.manage&lt;/code&gt;, and &lt;code&gt;reports.read&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Role permissions:&lt;/strong&gt; Connects roles to their permitted actions.&lt;/p&gt;
&lt;p&gt;The relationships can be represented as follows:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Users
  |
  | role_id
  v
Roles
  |
  | role_id
  v
RolePermissions
  |
  | permission_id
  v
Permissions
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A simplified SQL schema might look like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;CREATE TABLE roles (
    id SERIAL PRIMARY KEY,
    name VARCHAR(50) UNIQUE NOT NULL
);

CREATE TABLE users (
    id SERIAL PRIMARY KEY,
    email VARCHAR(255) UNIQUE NOT NULL,
    password_hash TEXT NOT NULL,
    role_id INTEGER NOT NULL REFERENCES roles(id),
    is_active BOOLEAN NOT NULL DEFAULT TRUE,
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

CREATE TABLE permissions (
    id SERIAL PRIMARY KEY,
    code VARCHAR(100) UNIQUE NOT NULL,
    description TEXT
);

CREATE TABLE role_permissions (
    role_id INTEGER NOT NULL REFERENCES roles(id)
        ON DELETE CASCADE,
    permission_id INTEGER NOT NULL REFERENCES permissions(id)
        ON DELETE CASCADE,
    PRIMARY KEY (role_id, permission_id)
);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In a real application, database migrations should create and modify these tables instead of relying on ad hoc manual changes.&lt;/p&gt;
&lt;p&gt;The schema can be extended with organizations, teams, projects, and audit logs when the business requirements demand them.&lt;/p&gt;
&lt;p&gt;It is also important to distinguish role-based permissions from data ownership. A manager may have permission to read reports but should not automatically be able to read reports belonging to every organization.&lt;/p&gt;
&lt;h3 id=&quot;5-implementing-authentication-with-fastapi&quot;&gt;5. Implementing Authentication with FastAPI&lt;/h3&gt;
&lt;p&gt;Authentication answers the question: Who is making this request?&lt;/p&gt;
&lt;p&gt;Authorization answers a different question: What is this user allowed to do?&lt;/p&gt;
&lt;p&gt;These concepts should remain separate.&lt;/p&gt;
&lt;p&gt;First, install the necessary backend packages:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;pip install fastapi uvicorn sqlalchemy \
    psycopg[binary] pyjwt pwdlib[argon2]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The precise package versions should be pinned and reviewed for compatibility in the project's dependency file.&lt;/p&gt;
&lt;h4 id=&quot;hashing-passwords&quot;&gt;Hashing passwords&lt;/h4&gt;
&lt;p&gt;Passwords must never be stored in plain text. Password hashing transforms a password into a one-way representation that can be verified later.&lt;/p&gt;
&lt;p&gt;A simplified example using &lt;code&gt;pwdlib&lt;/code&gt; is:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from pwdlib import PasswordHash

password_hasher = PasswordHash.recommended()

def hash_password(password: str) -&amp;gt; str:
    return password_hasher.hash(password)

def verify_password(
    password: str,
    password_hash: str
) -&amp;gt; bool:
    return password_hasher.verify(
        password,
        password_hash
    )
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When registering a user, save the resulting hash rather than the original password.&lt;/p&gt;
&lt;p&gt;When logging in, retrieve the account by email and verify the submitted password against the stored hash.&lt;/p&gt;
&lt;p&gt;The registration process should also validate email addresses, enforce suitable password policies, and avoid exposing sensitive details in error messages.&lt;/p&gt;
&lt;h4 id=&quot;creating-an-authentication-endpoint&quot;&gt;Creating an authentication endpoint&lt;/h4&gt;
&lt;p&gt;The login endpoint should validate credentials, check that the account is active, and establish an authenticated session.&lt;/p&gt;
&lt;p&gt;A simplified endpoint structure is:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from fastapi import APIRouter, HTTPException, status

router = APIRouter(prefix=&quot;/auth&quot;, tags=[&quot;Authentication&quot;])

@router.post(&quot;/login&quot;)
def login(credentials: LoginRequest):
    user = find_user_by_email(credentials.email)

    if (
        user is None
        or not verify_password(
            credentials.password,
            user.password_hash
        )
        or not user.is_active
    ):
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail=&quot;Invalid credentials&quot;
        )

    return create_authenticated_session(user)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The functions and request schema shown here represent application-specific helpers that must be implemented using the database and security configuration.&lt;/p&gt;
&lt;p&gt;For JWT-based authentication, the server signs tokens using a securely managed key and a suitable signing algorithm. Tokens should include an expiration time and an appropriate subject identifier.&lt;/p&gt;
&lt;p&gt;The application must validate the signature, expiration, issuer, audience, and other relevant claims.&lt;/p&gt;
&lt;p&gt;For browser applications, an alternative is a server-managed session in a cookie configured with &lt;code&gt;HttpOnly&lt;/code&gt;, &lt;code&gt;Secure&lt;/code&gt;, and an appropriate &lt;code&gt;SameSite&lt;/code&gt; policy. Cookie-based authentication also requires appropriate CSRF protections.&lt;/p&gt;
&lt;p&gt;Neither approach should expose long-lived credentials to unnecessary JavaScript access.&lt;/p&gt;
&lt;h3 id=&quot;6-building-the-authorization-layer&quot;&gt;6. Building the Authorization Layer&lt;/h3&gt;
&lt;p&gt;Authorization is where role-based access control becomes effective.&lt;/p&gt;
&lt;p&gt;Instead of writing separate role checks in every API endpoint, create reusable dependencies that verify a user's permissions.&lt;/p&gt;
&lt;p&gt;Consider a FastAPI dependency:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from fastapi import Depends, HTTPException, status

def require_permission(permission_code: str):
    def permission_checker(
        current_user = Depends(get_current_user)
    ):
        if not user_has_permission(
            current_user,
            permission_code
        ):
            raise HTTPException(
                status_code=status.HTTP_403_FORBIDDEN,
                detail=&quot;Insufficient permissions&quot;
            )

        return current_user

    return permission_checker
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here, &lt;code&gt;get_current_user&lt;/code&gt; must validate the incoming session or token, while &lt;code&gt;user_has_permission&lt;/code&gt; must resolve the user's current effective permissions.&lt;/p&gt;
&lt;p&gt;A protected endpoint can then declare its access requirement:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;@router.get(&quot;/users&quot;)
def list_users(
    current_user = Depends(
        require_permission(&quot;users.read&quot;)
    )
):
    return get_authorized_users(current_user)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The example assumes that the database query and data-scope restrictions are implemented correctly.&lt;/p&gt;
&lt;p&gt;The key advantage is consistency. Every endpoint using the dependency follows the same authorization policy.&lt;/p&gt;
&lt;p&gt;An administrator can be granted &lt;code&gt;users.read&lt;/code&gt; and &lt;code&gt;users.manage&lt;/code&gt;, while a manager might receive &lt;code&gt;users.read&lt;/code&gt; only for team members. An employee may have neither permission.&lt;/p&gt;
&lt;p&gt;For a small application, a role-based check can be sufficient:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;def require_role(*allowed_roles: str):
    def role_checker(
        current_user = Depends(get_current_user)
    ):
        if current_user.role.name not in allowed_roles:
            raise HTTPException(
                status_code=403,
                detail=&quot;Access denied&quot;
            )

        return current_user

    return role_checker
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;However, permission-based checks generally offer greater flexibility as the number of features and roles increases.&lt;/p&gt;
&lt;p&gt;A critical rule is that the backend must never trust a role name submitted by the frontend. It should derive authorization from a verified identity and trusted server-side data.&lt;/p&gt;
&lt;h3 id=&quot;7-implementing-role-based-permissions&quot;&gt;7. Implementing Role-Based Permissions&lt;/h3&gt;
&lt;p&gt;A permission model should describe actions rather than merely naming users.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Permission &lt;/th&gt;&lt;th&gt;Administrator &lt;/th&gt;&lt;th&gt;Manager &lt;/th&gt;&lt;th&gt;Employee&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;View own dashboard &lt;/td&gt;&lt;td&gt;Yes &lt;/td&gt;&lt;td&gt;Yes &lt;/td&gt;&lt;td&gt;Yes&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Manage all users &lt;/td&gt;&lt;td&gt;Yes &lt;/td&gt;&lt;td&gt;No &lt;/td&gt;&lt;td&gt;No&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;View team reports &lt;/td&gt;&lt;td&gt;Yes &lt;/td&gt;&lt;td&gt;Yes &lt;/td&gt;&lt;td&gt;No&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;View organization reports &lt;/td&gt;&lt;td&gt;Yes &lt;/td&gt;&lt;td&gt;No &lt;/td&gt;&lt;td&gt;No&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Update assigned tasks &lt;/td&gt;&lt;td&gt;Yes &lt;/td&gt;&lt;td&gt;Yes &lt;/td&gt;&lt;td&gt;Yes&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Change application settings &lt;/td&gt;&lt;td&gt;Yes &lt;/td&gt;&lt;td&gt;No &lt;/td&gt;&lt;td&gt;No&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;These are example policies, not universal rules. A real project should define permissions according to its business requirements.&lt;/p&gt;
&lt;p&gt;A permission can follow a naming convention such as:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;users.read&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;users.create&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;users.update&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;users.delete&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;tasks.read&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;tasks.update&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;reports.read&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;settings.manage&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This structure makes the policy easier to understand and extend.&lt;/p&gt;
&lt;p&gt;Instead of scattering conditions throughout the codebase, maintain a central authorization layer and test the policy systematically.&lt;/p&gt;
&lt;h4 id=&quot;avoiding-permission-escalation&quot;&gt;Avoiding permission escalation&lt;/h4&gt;
&lt;p&gt;Only authorized administrators should be able to assign privileged roles.&lt;/p&gt;
&lt;p&gt;For example, an employee update endpoint should not accept unrestricted changes to fields such as &lt;code&gt;role_id&lt;/code&gt;, &lt;code&gt;is_active&lt;/code&gt;, or &lt;code&gt;is_superuser&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Use separate request schemas for ordinary profile updates and administrative account management.&lt;/p&gt;
&lt;p&gt;Always validate the target user's permissions and the acting user's authority before changing roles.&lt;/p&gt;
&lt;p&gt;Role changes should also be logged so that unexpected privilege escalation can be investigated.&lt;/p&gt;
&lt;h3 id=&quot;8-building-the-react-authentication-context&quot;&gt;8. Building the React Authentication Context&lt;/h3&gt;
&lt;p&gt;The React frontend needs to know whether the user is authenticated and which interface to display.&lt;/p&gt;
&lt;p&gt;An authentication context can share this information between components without passing user details through every component manually.&lt;/p&gt;
&lt;p&gt;A simplified example is:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;import {
  createContext,
  useContext,
  useEffect,
  useState
} from &quot;react&quot;;

const AuthContext = createContext(null);

export function AuthProvider({ children }) {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(true);

  useEffect(() =&amp;gt; {
    async function loadCurrentUser() {
      try {
        const response = await fetch(&quot;/api/auth/me&quot;, {
          credentials: &quot;include&quot;
        });

        if (response.ok) {
          setUser(await response.json());
        }
      } finally {
        setLoading(false);
      }
    }

    loadCurrentUser();
  }, []);

  return (
    &amp;lt;AuthContext.Provider
      value={{ user, setUser, loading }}
    &amp;gt;
      {children}
    &amp;lt;/AuthContext.Provider&amp;gt;
  );
}

export function useAuth() {
  return useContext(AuthContext);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This example assumes that the application uses an authenticated cookie and that &lt;code&gt;/api/auth/me&lt;/code&gt; returns the current user's safe profile.&lt;/p&gt;
&lt;p&gt;The endpoint must not return password hashes, signing secrets, or other sensitive fields.&lt;/p&gt;
&lt;p&gt;In a complete implementation, network errors should be handled explicitly, session expiry should be addressed, and login and logout operations should update the context consistently.&lt;/p&gt;
&lt;p&gt;The loading state is important because React should not briefly display protected content before the authentication check finishes.&lt;/p&gt;
&lt;h3 id=&quot;9-protecting-react-routes&quot;&gt;9. Protecting React Routes&lt;/h3&gt;
&lt;p&gt;React Router can prevent users from navigating to pages that should not be visible to them.&lt;/p&gt;
&lt;p&gt;A route guard might look like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;import { Navigate, Outlet } from &quot;react-router-dom&quot;;
import { useAuth } from &quot;./AuthContext&quot;;

export default function ProtectedRoute({
  allowedRoles
}) {
  const { user, loading } = useAuth();

  if (loading) {
    return &amp;lt;p&amp;gt;Loading dashboard...&amp;lt;/p&amp;gt;;
  }

  if (!user) {
    return &amp;lt;Navigate to=&quot;/login&quot; replace /&amp;gt;;
  }

  if (
    allowedRoles &amp;amp;&amp;amp;
    !allowedRoles.includes(user.role)
  ) {
    return &amp;lt;Navigate to=&quot;/forbidden&quot; replace /&amp;gt;;
  }

  return &amp;lt;Outlet /&amp;gt;;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The route configuration can group pages by role:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;&amp;lt;Route element={
  &amp;lt;ProtectedRoute
    allowedRoles={[&quot;admin&quot;]}
  /&amp;gt;
}&amp;gt;
  &amp;lt;Route
    path=&quot;/admin/users&quot;
    element={&amp;lt;UserManagement /&amp;gt;}
  /&amp;gt;
&amp;lt;/Route&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The exact role representation must match the profile returned by the backend.&lt;/p&gt;
&lt;p&gt;Route protection improves the user experience, but it is not a substitute for server-side authorization. Anyone can bypass frontend navigation by calling an API directly.&lt;/p&gt;
&lt;p&gt;Therefore, every protected API endpoint must enforce the relevant permissions even when its corresponding React route is already protected.&lt;/p&gt;
&lt;h3 id=&quot;10-creating-a-role-aware-sidebar&quot;&gt;10. Creating a Role-Aware Sidebar&lt;/h3&gt;
&lt;p&gt;A useful dashboard should display only the navigation options that are relevant to the current user.&lt;/p&gt;
&lt;p&gt;Define the navigation items in one configuration:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;const navigationItems = [
  {
    label: &quot;Dashboard&quot;,
    path: &quot;/dashboard&quot;,
    roles: [&quot;admin&quot;, &quot;manager&quot;, &quot;employee&quot;]
  },
  {
    label: &quot;User Management&quot;,
    path: &quot;/admin/users&quot;,
    roles: [&quot;admin&quot;]
  },
  {
    label: &quot;Team Reports&quot;,
    path: &quot;/reports/team&quot;,
    roles: [&quot;admin&quot;, &quot;manager&quot;]
  },
  {
    label: &quot;My Tasks&quot;,
    path: &quot;/tasks&quot;,
    roles: [&quot;admin&quot;, &quot;manager&quot;, &quot;employee&quot;]
  }
];
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The sidebar can filter the list according to the authenticated user's role:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;function Sidebar({ role }) {
  const visibleItems = navigationItems.filter(
    item =&amp;gt; item.roles.includes(role)
  );

  return (
    &amp;lt;nav&amp;gt;
      {visibleItems.map(item =&amp;gt; (
        &amp;lt;a key={item.path} href={item.path}&amp;gt;
          {item.label}
        &amp;lt;/a&amp;gt;
      ))}
    &amp;lt;/nav&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In a real application, use React Router's &lt;code&gt;Link&lt;/code&gt; or &lt;code&gt;NavLink&lt;/code&gt; components instead of ordinary anchors for internal navigation.&lt;/p&gt;
&lt;p&gt;For permission-based navigation, filter items against a permission set rather than hard-coding role names. This approach supports custom roles without requiring every navigation component to understand the entire authorization model.&lt;/p&gt;
&lt;p&gt;The same principle applies to action buttons. For example, a user without &lt;code&gt;users.create&lt;/code&gt; should not see an option to create accounts.&lt;/p&gt;
&lt;p&gt;Nevertheless, the backend must reject unauthorized creation requests even if a user manually constructs one.&lt;/p&gt;
&lt;h3 id=&quot;11-creating-different-dashboard-views&quot;&gt;11. Creating Different Dashboard Views&lt;/h3&gt;
&lt;p&gt;After authentication and authorization are in place, the next step is designing the dashboard itself.&lt;/p&gt;
&lt;p&gt;A reusable layout should contain common elements such as the header, sidebar, content area, notification panel, and profile menu.&lt;/p&gt;
&lt;p&gt;Inside that layout, each user role can have a different dashboard view.&lt;/p&gt;
&lt;h4 id=&quot;administrator-dashboard&quot;&gt;Administrator dashboard&lt;/h4&gt;
&lt;p&gt;Administrators may need a high-level overview of the organization.&lt;/p&gt;
&lt;p&gt;Useful components include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Total registered users.&lt;/li&gt;
&lt;li&gt;Active and inactive accounts.&lt;/li&gt;
&lt;li&gt;Recent administrative activities.&lt;/li&gt;
&lt;li&gt;User management shortcuts.&lt;/li&gt;
&lt;li&gt;System health indicators.&lt;/li&gt;
&lt;li&gt;Organization-wide performance reports.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Sensitive administrative metrics should be retrieved through endpoints that enforce the required permissions.&lt;/p&gt;
&lt;h4 id=&quot;manager-dashboard&quot;&gt;Manager dashboard&lt;/h4&gt;
&lt;p&gt;Managers generally need team-level information.&lt;/p&gt;
&lt;p&gt;Their dashboard could include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Number of assigned employees.&lt;/li&gt;
&lt;li&gt;Tasks in progress.&lt;/li&gt;
&lt;li&gt;Overdue tasks.&lt;/li&gt;
&lt;li&gt;Team completion rates.&lt;/li&gt;
&lt;li&gt;Pending approvals.&lt;/li&gt;
&lt;li&gt;Team performance trends.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The backend should limit these metrics to teams the manager is authorized to oversee.&lt;/p&gt;
&lt;h4 id=&quot;employee-dashboard&quot;&gt;Employee dashboard&lt;/h4&gt;
&lt;p&gt;Employees benefit from a simpler interface focused on their daily responsibilities.&lt;/p&gt;
&lt;p&gt;Possible components include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Assigned tasks.&lt;/li&gt;
&lt;li&gt;Upcoming deadlines.&lt;/li&gt;
&lt;li&gt;Recent notifications.&lt;/li&gt;
&lt;li&gt;Personal performance metrics.&lt;/li&gt;
&lt;li&gt;Profile information.&lt;/li&gt;
&lt;li&gt;Quick links to common actions.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Avoid loading every dashboard widget from one enormous endpoint. Separate API resources make permission enforcement clearer and allow each page to load the data it actually needs.&lt;/p&gt;
&lt;h3 id=&quot;12-enforcing-data-level-access-control&quot;&gt;12. Enforcing Data-Level Access Control&lt;/h3&gt;
&lt;p&gt;One of the most common RBAC mistakes is checking whether a user can access a feature without checking which records the user may access.&lt;/p&gt;
&lt;p&gt;For example, a manager might be permitted to read task details but should not automatically be able to access every employee's tasks.&lt;/p&gt;
&lt;p&gt;Imagine a request to retrieve a task:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;@router.get(&quot;/tasks/{task_id}&quot;)
def get_task(
    task_id: int,
    current_user = Depends(get_current_user)
):
    task = get_task_from_database(task_id)

    if task is None:
        raise HTTPException(
            status_code=404,
            detail=&quot;Task not found&quot;
        )

    if not can_view_task(current_user, task):
        raise HTTPException(
            status_code=403,
            detail=&quot;Access denied&quot;
        )

    return task
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The helper &lt;code&gt;can_view_task&lt;/code&gt; must implement the application's actual ownership, team, and organizational rules.&lt;/p&gt;
&lt;p&gt;For greater reliability, enforce data scopes directly in database queries whenever practical.&lt;/p&gt;
&lt;p&gt;For example, an employee's task query should include the employee's authenticated user ID. A manager's query should include only the IDs of teams that manager is permitted to access.&lt;/p&gt;
&lt;p&gt;This reduces the risk of insecure direct object references, where changing a record identifier exposes another user's data.&lt;/p&gt;
&lt;p&gt;A robust access policy often combines RBAC with ownership checks or attribute-based access control (ABAC).&lt;/p&gt;
&lt;p&gt;RBAC answers which actions a role can perform. Ownership and attributes determine whether that action is permitted on a particular record.&lt;/p&gt;
&lt;h3 id=&quot;13-building-dashboard-apis-efficiently&quot;&gt;13. Building Dashboard APIs Efficiently&lt;/h3&gt;
&lt;p&gt;The frontend should retrieve data through clearly defined endpoints.&lt;/p&gt;
&lt;p&gt;A possible API design is:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;POST   /api/auth/login
POST   /api/auth/logout
GET    /api/auth/me

GET    /api/dashboard/summary

GET    /api/users
POST   /api/users
PATCH  /api/users/{user_id}

GET    /api/tasks
POST   /api/tasks
PATCH  /api/tasks/{task_id}

GET    /api/reports/team
GET    /api/reports/organization
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Every endpoint should have an explicit authentication requirement and authorization policy.&lt;/p&gt;
&lt;p&gt;For example, &lt;code&gt;GET /api/reports/organization&lt;/code&gt; may require an organization-wide reporting permission, while &lt;code&gt;GET /api/reports/team&lt;/code&gt; may allow managers to access only their assigned teams.&lt;/p&gt;
&lt;p&gt;Avoid relying on the endpoint's name or location to provide security. The policy must be implemented in the actual request handler or its dependencies.&lt;/p&gt;
&lt;h4 id=&quot;pagination-and-filtering&quot;&gt;Pagination and filtering&lt;/h4&gt;
&lt;p&gt;Dashboard tables can become slow when they retrieve thousands of records in a single request.&lt;/p&gt;
&lt;p&gt;Use pagination and server-side filtering.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;GET /api/tasks?page=1&amp;amp;page_size=25&amp;amp;status=pending
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The backend should validate pagination parameters, apply the authenticated user's data scope, and then execute the query.&lt;/p&gt;
&lt;p&gt;Filtering and sorting must be implemented safely. User-provided column names should not be inserted directly into raw SQL statements.&lt;/p&gt;
&lt;h4 id=&quot;caching-considerations&quot;&gt;Caching considerations&lt;/h4&gt;
&lt;p&gt;Caching can improve dashboard performance, but it can also expose data if cache entries are shared across users with different permissions.&lt;/p&gt;
&lt;p&gt;Cache keys should reflect the relevant user, organization, and authorization scope. Sensitive responses should not be placed in shared caches without a clear security model.&lt;/p&gt;
&lt;p&gt;When permissions change, the system should also consider how cached authorization decisions and dashboard data are invalidated.&lt;/p&gt;
&lt;h3 id=&quot;14-handling-authentication-and-session-security&quot;&gt;14. Handling Authentication and Session Security&lt;/h3&gt;
&lt;p&gt;Security must be part of the design from the beginning.&lt;/p&gt;
&lt;p&gt;A role-based dashboard may contain employee information, customer records, financial reports, or administrative operations. Poor session management can undermine even a carefully designed permission model.&lt;/p&gt;
&lt;p&gt;Important practices include the following.&lt;/p&gt;
&lt;h4 id=&quot;use-short-lived-access-credentials&quot;&gt;Use short-lived access credentials&lt;/h4&gt;
&lt;p&gt;Access tokens should have reasonable expiration times. If refresh tokens are used, implement rotation, revocation, and appropriate reuse detection.&lt;/p&gt;
&lt;h4 id=&quot;protect-browser-storage&quot;&gt;Protect browser storage&lt;/h4&gt;
&lt;p&gt;Avoid storing long-lived authentication tokens in browser storage where injected JavaScript could read them.&lt;/p&gt;
&lt;p&gt;HttpOnly cookies can reduce token theft through JavaScript, although they require proper CSRF defenses and careful cookie configuration.&lt;/p&gt;
&lt;h4 id=&quot;configure-cors-correctly&quot;&gt;Configure CORS correctly&lt;/h4&gt;
&lt;p&gt;Cross-Origin Resource Sharing should allow only the origins required by the application.&lt;/p&gt;
&lt;p&gt;Avoid permissive wildcard configurations, especially when credentials are involved.&lt;/p&gt;
&lt;h4 id=&quot;validate-every-request&quot;&gt;Validate every request&lt;/h4&gt;
&lt;p&gt;Validate request bodies, query parameters, identifiers, and update fields. Do not assume the frontend has already performed validation.&lt;/p&gt;
&lt;h4 id=&quot;prevent-brute-force-attacks&quot;&gt;Prevent brute-force attacks&lt;/h4&gt;
&lt;p&gt;Apply appropriate rate limits to login and other sensitive endpoints. Monitor repeated failed login attempts and consider additional protections for high-risk accounts.&lt;/p&gt;
&lt;h4 id=&quot;protect-privileged-accounts&quot;&gt;Protect privileged accounts&lt;/h4&gt;
&lt;p&gt;Require stronger authentication controls for administrative users where practical, including multifactor authentication.&lt;/p&gt;
&lt;h4 id=&quot;use-https&quot;&gt;Use HTTPS&lt;/h4&gt;
&lt;p&gt;All production authentication traffic should use HTTPS. Secure cookies and appropriate transport security configurations help protect credentials in transit.&lt;/p&gt;
&lt;h4 id=&quot;avoid-leaking-sensitive-information&quot;&gt;Avoid leaking sensitive information&lt;/h4&gt;
&lt;p&gt;Do not include secrets, password hashes, or internal authorization details in API responses or application logs.&lt;/p&gt;
&lt;p&gt;Security depends on the complete request lifecycle, not a single authentication library.&lt;/p&gt;
&lt;h3 id=&quot;15-managing-role-changes-and-permission-updates&quot;&gt;15. Managing Role Changes and Permission Updates&lt;/h3&gt;
&lt;p&gt;User roles often change as employees move between teams or receive new responsibilities.&lt;/p&gt;
&lt;p&gt;A manager might become an administrator, or an employee might lose access after leaving a project.&lt;/p&gt;
&lt;p&gt;These changes should take effect according to a clearly defined policy.&lt;/p&gt;
&lt;p&gt;If authorization depends on permissions stored in the database, the backend can resolve current permissions on each request or use a carefully designed cache with invalidation.&lt;/p&gt;
&lt;p&gt;If roles and permissions are embedded in JWT claims, those claims may become outdated before the token expires.&lt;/p&gt;
&lt;p&gt;For that reason, highly privileged operations should not rely exclusively on long-lived role claims without considering revocation and role-change behavior.&lt;/p&gt;
&lt;p&gt;Possible solutions include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Short-lived access tokens.&lt;/li&gt;
&lt;li&gt;Server-side sessions.&lt;/li&gt;
&lt;li&gt;Token versioning.&lt;/li&gt;
&lt;li&gt;Session revocation.&lt;/li&gt;
&lt;li&gt;Rechecking current permissions for sensitive operations.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The appropriate approach depends on the application's scale and risk profile.&lt;/p&gt;
&lt;p&gt;Role assignment should also be auditable. Record who changed a role, which account was affected, when the change occurred, and whether the operation succeeded.&lt;/p&gt;
&lt;h3 id=&quot;16-testing-the-authorization-model&quot;&gt;16. Testing the Authorization Model&lt;/h3&gt;
&lt;p&gt;A dashboard can look correct while containing serious access-control vulnerabilities. Testing should therefore cover both successful operations and forbidden requests.&lt;/p&gt;
&lt;p&gt;Start by creating test accounts for each role.&lt;/p&gt;
&lt;p&gt;Then build a permission matrix that identifies which actions should succeed or fail for each account.&lt;/p&gt;
&lt;p&gt;Example tests include:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Administrator tests&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Can access user management.&lt;/li&gt;
&lt;li&gt;Can create authorized user accounts.&lt;/li&gt;
&lt;li&gt;Can access permitted organization-wide reports.&lt;/li&gt;
&lt;li&gt;Cannot bypass validation or access data outside their organization.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Manager tests&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Can view permitted team reports.&lt;/li&gt;
&lt;li&gt;Can update tasks within their authorized scope.&lt;/li&gt;
&lt;li&gt;Cannot assign themselves administrative privileges.&lt;/li&gt;
&lt;li&gt;Cannot retrieve unrelated teams' private records.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Employee tests&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Can view their own dashboard.&lt;/li&gt;
&lt;li&gt;Can update assigned tasks where allowed.&lt;/li&gt;
&lt;li&gt;Cannot access administrator endpoints.&lt;/li&gt;
&lt;li&gt;Cannot modify another user's profile or task by changing an identifier.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;testing-with-pytest&quot;&gt;Testing with Pytest&lt;/h4&gt;
&lt;p&gt;A backend test might look like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;def test_employee_cannot_list_users(
    employee_client
):
    response = employee_client.get(&quot;/api/users&quot;)

    assert response.status_code == 403
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The test assumes that the employee lacks the permission required by the endpoint and that &lt;code&gt;employee_client&lt;/code&gt; represents a correctly authenticated test client.&lt;/p&gt;
&lt;p&gt;Additional tests should cover expired tokens, inactive accounts, revoked sessions, invalid permissions, missing credentials, and unexpected database failures.&lt;/p&gt;
&lt;p&gt;For resource-level access, create records belonging to different users and verify that changing the record identifier never grants unauthorized access.&lt;/p&gt;
&lt;p&gt;Frontend tests should also confirm that restricted navigation items and actions are hidden or disabled appropriately.&lt;/p&gt;
&lt;p&gt;However, frontend tests cannot establish backend security on their own. The API must be tested directly.&lt;/p&gt;
&lt;h3 id=&quot;17-improving-dashboard-performance&quot;&gt;17. Improving Dashboard Performance&lt;/h3&gt;
&lt;p&gt;Performance becomes important when a dashboard displays multiple data tables, charts, notifications, and activity feeds.&lt;/p&gt;
&lt;p&gt;A few architectural decisions can make a significant difference.&lt;/p&gt;
&lt;h4 id=&quot;optimize-database-queries&quot;&gt;Optimize database queries&lt;/h4&gt;
&lt;p&gt;Use appropriate indexes for commonly filtered fields, including foreign keys and frequently queried status columns.&lt;/p&gt;
&lt;p&gt;Inspect query plans before adding indexes indiscriminately.&lt;/p&gt;
&lt;h4 id=&quot;avoid-unnecessary-requests&quot;&gt;Avoid unnecessary requests&lt;/h4&gt;
&lt;p&gt;Fetch data when the relevant page needs it. Use request caching and invalidation through an appropriate data-fetching library when the application becomes more complex.&lt;/p&gt;
&lt;h4 id=&quot;split-large-components&quot;&gt;Split large components&lt;/h4&gt;
&lt;p&gt;Separate dashboard cards, tables, filters, and chart components into manageable units.&lt;/p&gt;
&lt;p&gt;This makes the interface easier to test and reduces unnecessary rendering.&lt;/p&gt;
&lt;h4 id=&quot;use-lazy-loading&quot;&gt;Use lazy loading&lt;/h4&gt;
&lt;p&gt;Load large reports or less frequently visited pages only when users open them.&lt;/p&gt;
&lt;h4 id=&quot;paginate-large-datasets&quot;&gt;Paginate large datasets&lt;/h4&gt;
&lt;p&gt;Return manageable pages of records instead of sending the entire dataset to the browser.&lt;/p&gt;
&lt;h4 id=&quot;monitor-real-world-behavior&quot;&gt;Monitor real-world behavior&lt;/h4&gt;
&lt;p&gt;Track API latency, error rates, database query performance, and frontend loading times.&lt;/p&gt;
&lt;p&gt;Performance optimization should be driven by actual measurements rather than assumptions.&lt;/p&gt;
&lt;h3 id=&quot;18-improving-the-user-experience&quot;&gt;18. Improving the User Experience&lt;/h3&gt;
&lt;p&gt;A secure dashboard should also be easy to understand.&lt;/p&gt;
&lt;p&gt;Users should be able to determine what they can do without navigating through irrelevant features.&lt;/p&gt;
&lt;p&gt;Consider these improvements:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Clear navigation:&lt;/strong&gt; Organize features according to the user's responsibilities.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Useful empty states:&lt;/strong&gt; Explain what a user can do when there are no tasks, reports, or notifications.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Accessible design:&lt;/strong&gt; Provide keyboard navigation, clear focus indicators, semantic HTML, and suitable color contrast.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Responsive layouts:&lt;/strong&gt; Ensure tables, menus, and dashboard cards work on desktop, tablet, and mobile screens.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Meaningful feedback:&lt;/strong&gt; Display clear success and error messages after important operations.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Confirmation for destructive actions:&lt;/strong&gt; Require appropriate confirmation before deleting records or performing other irreversible operations.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Consistent loading states:&lt;/strong&gt; Show skeletons, spinners, or loading indicators while data is being retrieved.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Permission-aware error handling:&lt;/strong&gt; Explain when a user lacks access without exposing internal security details.&lt;/p&gt;
&lt;p&gt;Avoid treating role-based design as an excuse to create completely separate user interfaces. Reuse common components wherever possible and customize only the sections that genuinely differ.&lt;/p&gt;
&lt;h3 id=&quot;19-logging-and-auditing-administrative-actions&quot;&gt;19. Logging and Auditing Administrative Actions&lt;/h3&gt;
&lt;p&gt;A production dashboard should provide visibility into important security events.&lt;/p&gt;
&lt;p&gt;Useful audit events include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Successful and failed authentication attempts.&lt;/li&gt;
&lt;li&gt;Role and permission changes.&lt;/li&gt;
&lt;li&gt;User account creation and deactivation.&lt;/li&gt;
&lt;li&gt;Sensitive report access where justified.&lt;/li&gt;
&lt;li&gt;Changes to important business records.&lt;/li&gt;
&lt;li&gt;Rejected privileged operations.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;An audit record might contain:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;event_id
actor_user_id
action
resource_type
resource_id
timestamp
request_id
outcome
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Additional fields can be introduced where necessary, but logs should not contain passwords, access tokens, or unnecessary personal information.&lt;/p&gt;
&lt;p&gt;Audit logs should be protected against unauthorized modification and access. For important compliance requirements, consider exporting them to a dedicated monitoring or logging platform.&lt;/p&gt;
&lt;p&gt;Logging is not a replacement for access control. Its purpose is to support monitoring, investigation, and accountability.&lt;/p&gt;
&lt;h3 id=&quot;20-deploying-the-application&quot;&gt;20. Deploying the Application&lt;/h3&gt;
&lt;p&gt;Once the dashboard passes testing, prepare it for production.&lt;/p&gt;
&lt;p&gt;The React frontend can be built as static assets and hosted on an appropriate web server or frontend hosting platform. FastAPI can run behind a production-ready application server and reverse proxy. PostgreSQL should run in a managed database service or a properly secured database environment.&lt;/p&gt;
&lt;p&gt;Docker can help make development and deployment environments more consistent.&lt;/p&gt;
&lt;p&gt;A production deployment checklist should include:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Configure environment variables and secret management.&lt;/li&gt;
&lt;li&gt;Use a dedicated database account with limited privileges.&lt;/li&gt;
&lt;li&gt;Apply database migrations.&lt;/li&gt;
&lt;li&gt;Enable HTTPS.&lt;/li&gt;
&lt;li&gt;Configure allowed frontend origins.&lt;/li&gt;
&lt;li&gt;Set appropriate cookie and session policies.&lt;/li&gt;
&lt;li&gt;Configure database backups and recovery procedures.&lt;/li&gt;
&lt;li&gt;Add health checks and application monitoring.&lt;/li&gt;
&lt;li&gt;Apply rate limits to sensitive endpoints.&lt;/li&gt;
&lt;li&gt;Verify role and resource-level authorization.&lt;/li&gt;
&lt;li&gt;Test account deactivation and session revocation.&lt;/li&gt;
&lt;li&gt;Review dependency updates and security advisories.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Never commit production secrets, signing keys, or database passwords to a public repository.&lt;/p&gt;
&lt;p&gt;Also, avoid running database migrations automatically in every application worker at startup. A controlled deployment step is easier to reason about and reduces migration race conditions.&lt;/p&gt;
&lt;p&gt;After deployment, monitor errors and latency, test backup restoration periodically, and establish a process for responding to security incidents.&lt;/p&gt;
&lt;h3 id=&quot;21-common-mistakes-to-avoid&quot;&gt;21. Common Mistakes to Avoid&lt;/h3&gt;
&lt;p&gt;Several mistakes repeatedly appear in role-based dashboard implementations.&lt;/p&gt;
&lt;h4 id=&quot;trusting-frontend-roles&quot;&gt;Trusting frontend roles&lt;/h4&gt;
&lt;p&gt;A role stored in React state can be changed by a user. The backend must verify permissions independently.&lt;/p&gt;
&lt;h4 id=&quot;giving-every-administrator-unlimited-access&quot;&gt;Giving every administrator unlimited access&lt;/h4&gt;
&lt;p&gt;Administrative privileges should be carefully defined and restricted to legitimate needs. Separate high-risk permissions when appropriate.&lt;/p&gt;
&lt;h4 id=&quot;ignoring-record-ownership&quot;&gt;Ignoring record ownership&lt;/h4&gt;
&lt;p&gt;Permission to access a feature does not necessarily mean permission to access every record within that feature.&lt;/p&gt;
&lt;h4 id=&quot;using-one-oversized-api-endpoint&quot;&gt;Using one oversized API endpoint&lt;/h4&gt;
&lt;p&gt;Returning all application data from a single endpoint complicates authorization, increases response size, and can reveal information unnecessarily.&lt;/p&gt;
&lt;h4 id=&quot;hard-coding-role-checks-everywhere&quot;&gt;Hard-coding role checks everywhere&lt;/h4&gt;
&lt;p&gt;Repeated conditional logic becomes difficult to maintain. Centralize authorization rules and test them systematically.&lt;/p&gt;
&lt;h4 id=&quot;forgetting-permission-revocation&quot;&gt;Forgetting permission revocation&lt;/h4&gt;
&lt;p&gt;Role changes, account deactivation, and session revocation must have defined effects on existing sessions and tokens.&lt;/p&gt;
&lt;h4 id=&quot;skipping-negative-tests&quot;&gt;Skipping negative tests&lt;/h4&gt;
&lt;p&gt;It is not enough to prove that administrators can access restricted resources. Tests must also prove that unauthorized users cannot.&lt;/p&gt;
&lt;h4 id=&quot;returning-sensitive-fields&quot;&gt;Returning sensitive fields&lt;/h4&gt;
&lt;p&gt;Use response schemas that expose only the data a particular endpoint is intended to return.&lt;/p&gt;
&lt;p&gt;Avoiding these mistakes early is generally easier than correcting them after a security incident.&lt;/p&gt;
&lt;h3 id=&quot;22-a-practical-development-roadmap&quot;&gt;22. A Practical Development Roadmap&lt;/h3&gt;
&lt;p&gt;For developers building their first multi-user dashboard, an incremental implementation plan is often the most effective approach.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Phase 1: Requirements and design&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Identify user roles, permissions, data ownership rules, and core dashboard features. Document the permission matrix before writing authorization code.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Phase 2: Database and authentication&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Create the user and role models, implement password hashing, establish login and logout flows, and define how sessions will be validated.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Phase 3: Backend authorization&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Implement reusable permission dependencies and apply them to every protected endpoint. Add data-scope checks for teams, organizations, and owned records.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Phase 4: React application&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Build the authentication context, protected routes, reusable dashboard layout, and role-aware navigation.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Phase 5: Dashboard functionality&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Implement the statistics cards, data tables, filters, reports, task management, and user management features required by the application.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Phase 6: Testing&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Test each role against both permitted and forbidden actions. Include direct API requests, cross-user record access, expired sessions, and role changes.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Phase 7: Production readiness&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Configure HTTPS, secure cookies or tokens, monitoring, audit logs, database backups, and deployment automation.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Phase 8: Iteration&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Review user feedback and performance metrics. Introduce new permissions or roles only when there is a clear business need.&lt;/p&gt;
&lt;p&gt;This roadmap keeps the implementation manageable while ensuring that security is considered throughout development.&lt;/p&gt;
&lt;h3 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;Building a multi-user dashboard with role-based access requires more than creating a few conditional React components. It involves designing a clear authorization model, securing authentication, protecting API endpoints, limiting access to individual records, and testing the complete system against unauthorized requests.&lt;/p&gt;
&lt;p&gt;React provides a flexible interface for presenting role-specific dashboards. FastAPI offers a practical foundation for reusable authentication and authorization dependencies, while PostgreSQL provides reliable storage for users, roles, permissions, and application data.&lt;/p&gt;
&lt;p&gt;By combining least-privilege permissions, resource-level authorization, secure session management, comprehensive testing, and structured deployment practices, developers can build dashboards that are maintainable today and ready to grow with future business requirements.&lt;/p&gt;
&lt;p&gt;A well-designed role-based dashboard is not simply an interface that changes depending on who logs in. It is a secure application architecture that ensures every user can perform the right actions, access the right information, and do so without compromising the privacy or integrity of other users' data.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/31802/how-to-build-a-multi-user-dashboard-with-role-based-access</guid>
<pubDate>Tue, 20 Oct 2026 06:04:12 +0000</pubDate>
</item>
<item>
<title>How to Build a Lead Management Dashboard from Scratch</title>
<link>https://coderlegion.com/31356/how-to-build-a-lead-management-dashboard-from-scratch</link>
<description>&lt;p&gt;In today's competitive digital business environment, generating leads is only the first step toward acquiring customers. Businesses also need an effective way to organize prospects, track communication, monitor sales activities, and measure conversion rates. Without a centralized system, important leads can get lost, follow-ups may be delayed, and sales opportunities can disappear.&lt;/p&gt;
&lt;p&gt;A lead management dashboard solves these problems by bringing essential customer and sales information into one organized interface. It helps sales representatives, marketing teams, business owners, and customer relationship managers understand what is happening throughout the sales pipeline.&lt;/p&gt;
&lt;p&gt;Building a lead management dashboard from scratch is an excellent full-stack development project because it combines frontend design, backend development, database management, authentication, data visualization, and business logic.&lt;/p&gt;
&lt;p&gt;In this article, we will explore how to plan, design, develop, secure, test, and deploy a lead management dashboard using modern web technologies such as React, FastAPI, PostgreSQL, and supporting tools.&lt;/p&gt;
&lt;p&gt;By the end, you will understand the architecture and essential features needed to build a practical lead management application.&lt;/p&gt;
&lt;h3 id=&quot;1-what-is-a-lead-management-dashboard-&quot;&gt;1. What Is a Lead Management Dashboard?&lt;/h3&gt;
&lt;p&gt;A lead management dashboard is a centralized web application that allows businesses to manage potential customers from their initial interaction through qualification, follow-up, and conversion.&lt;/p&gt;
&lt;p&gt;A lead may come from a website contact form, social media campaign, paid advertisement, email subscription, referral, or direct sales inquiry.&lt;/p&gt;
&lt;p&gt;The dashboard stores this information and makes it accessible to authorized team members.&lt;/p&gt;
&lt;p&gt;For example, imagine a digital marketing agency receiving 200 leads each month from Google Ads, Facebook campaigns, and its website. Managing every prospect manually through spreadsheets can quickly become confusing.&lt;/p&gt;
&lt;p&gt;A lead management dashboard helps the agency:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Store lead information in one place.&lt;/li&gt;
&lt;li&gt;Identify the source of each lead.&lt;/li&gt;
&lt;li&gt;Assign leads to sales representatives.&lt;/li&gt;
&lt;li&gt;Track follow-up activities.&lt;/li&gt;
&lt;li&gt;Monitor lead status and sales progress.&lt;/li&gt;
&lt;li&gt;Measure campaign performance.&lt;/li&gt;
&lt;li&gt;Identify leads that need immediate attention.&lt;/li&gt;
&lt;li&gt;Generate reports for business decisions.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Unlike a simple contact list, a lead management dashboard focuses on the complete process of turning prospects into customers.&lt;/p&gt;
&lt;h3 id=&quot;2-define-the-project-requirements&quot;&gt;2. Define the Project Requirements&lt;/h3&gt;
&lt;p&gt;Before writing code, establish what the application should accomplish. Clear requirements prevent unnecessary complexity and help developers prioritize the features that deliver the most value.&lt;/p&gt;
&lt;p&gt;For the first version, focus on a minimum viable product (MVP).&lt;/p&gt;
&lt;h4 id=&quot;essential-features&quot;&gt;Essential features&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;Lead management:&lt;/strong&gt; Users should be able to create, view, edit, and delete lead records according to their permissions.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Dashboard statistics:&lt;/strong&gt; Display total leads, new leads, qualified leads, converted leads, and other relevant metrics.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Sales pipeline:&lt;/strong&gt; Organize prospects into stages such as New, Contacted, Qualified, Proposal, Won, and Lost.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Search and filtering:&lt;/strong&gt; Allow users to find leads by name, email, source, status, or assigned sales representative.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Task and follow-up management:&lt;/strong&gt; Record upcoming calls, meetings, emails, and reminders.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Lead assignment:&lt;/strong&gt; Assign prospects to individual sales representatives or teams.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Reports and analytics:&lt;/strong&gt; Visualize lead sources, conversion rates, and changes in lead volume over time.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Authentication and authorization:&lt;/strong&gt; Ensure that users can access only the features and records permitted by their roles.&lt;/p&gt;
&lt;h4 id=&quot;advanced-features&quot;&gt;Advanced features&lt;/h4&gt;
&lt;p&gt;Once the core application works reliably, consider adding email integrations, automated notifications, CSV imports and exports, duplicate detection, lead scoring, and marketing platform integrations.&lt;/p&gt;
&lt;p&gt;Starting with a manageable scope is better than attempting to build an entire enterprise CRM in the first release.&lt;/p&gt;
&lt;h3 id=&quot;3-choose-the-technology-stack&quot;&gt;3. Choose the Technology Stack&lt;/h3&gt;
&lt;p&gt;Selecting the right technology stack affects development speed, performance, maintainability, and scalability.&lt;/p&gt;
&lt;p&gt;For this project, React, FastAPI, and PostgreSQL provide a practical combination.&lt;/p&gt;
&lt;h4 id=&quot;frontend-react&quot;&gt;Frontend: React&lt;/h4&gt;
&lt;p&gt;React is a JavaScript library for building interactive user interfaces. It allows developers to create reusable components for dashboard cards, tables, filters, forms, and charts.&lt;/p&gt;
&lt;p&gt;Useful supporting tools include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;React Router for page navigation.&lt;/li&gt;
&lt;li&gt;Axios or the Fetch API for HTTP requests.&lt;/li&gt;
&lt;li&gt;Recharts for data visualization.&lt;/li&gt;
&lt;li&gt;React Hook Form for form management.&lt;/li&gt;
&lt;li&gt;Tailwind CSS for responsive styling.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;backend-fastapi&quot;&gt;Backend: FastAPI&lt;/h4&gt;
&lt;p&gt;FastAPI is a Python framework for developing APIs. It supports request validation, asynchronous endpoints, automatic API documentation, and integration with Python libraries.&lt;/p&gt;
&lt;p&gt;It can handle lead creation, authentication, filtering, reporting, assignment, and other business operations.&lt;/p&gt;
&lt;h4 id=&quot;database-postgresql&quot;&gt;Database: PostgreSQL&lt;/h4&gt;
&lt;p&gt;PostgreSQL is a relational database suitable for structured business data. It supports relationships, transactions, indexing, constraints, and advanced queries.&lt;/p&gt;
&lt;p&gt;These features are important when leads belong to organizations, sales representatives own leads, and activities reference individual prospects.&lt;/p&gt;
&lt;h4 id=&quot;additional-tools-1&quot;&gt;Additional tools&lt;/h4&gt;
&lt;p&gt;Consider using SQLAlchemy for database access, Alembic for schema migrations, Pydantic for validation, pytest for automated testing, Git for version control, and Docker for consistent deployment environments.&lt;/p&gt;
&lt;p&gt;The objective is not to use every available library. Choose tools that solve specific project requirements.&lt;/p&gt;
&lt;h3 id=&quot;4-design-the-application-architecture&quot;&gt;4. Design the Application Architecture&lt;/h3&gt;
&lt;p&gt;A clean architecture separates the user interface, business logic, and data storage.&lt;/p&gt;
&lt;p&gt;A typical request follows this path:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;A user interacts with the React dashboard.&lt;/li&gt;
&lt;li&gt;React sends an HTTP request to the FastAPI backend.&lt;/li&gt;
&lt;li&gt;FastAPI validates the request and checks the user's permissions.&lt;/li&gt;
&lt;li&gt;The backend executes the required business logic.&lt;/li&gt;
&lt;li&gt;SQLAlchemy communicates with PostgreSQL.&lt;/li&gt;
&lt;li&gt;The backend returns a structured JSON response.&lt;/li&gt;
&lt;li&gt;React updates the dashboard with the latest information.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;For example, when a sales representative creates a new lead, the frontend submits the form data to an API endpoint. The backend validates the information, associates the record with the appropriate organization, saves it in PostgreSQL, and returns the newly created lead.&lt;/p&gt;
&lt;p&gt;The frontend then updates the lead table and relevant statistics.&lt;/p&gt;
&lt;p&gt;Separating these responsibilities makes the application easier to maintain, test, and extend.&lt;/p&gt;
&lt;h3 id=&quot;5-plan-the-database-schema&quot;&gt;5. Plan the Database Schema&lt;/h3&gt;
&lt;p&gt;Database design is one of the most important steps in building a reliable lead management system.&lt;/p&gt;
&lt;p&gt;A poorly designed schema can produce duplicate records, inconsistent statuses, slow queries, and difficult reporting.&lt;/p&gt;
&lt;p&gt;Start by identifying the main entities.&lt;/p&gt;
&lt;h4 id=&quot;leads-table&quot;&gt;Leads table&lt;/h4&gt;
&lt;p&gt;The leads table stores prospect information.&lt;/p&gt;
&lt;p&gt;Recommended fields include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;id&lt;/code&gt;: Unique lead identifier.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;first_name&lt;/code&gt;: Prospect's first name.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;last_name&lt;/code&gt;: Prospect's last name.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;email&lt;/code&gt;: Email address.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;phone&lt;/code&gt;: Contact number.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;company&lt;/code&gt;: Company name.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;source&lt;/code&gt;: Origin of the lead.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;status&lt;/code&gt;: Current sales stage.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;assigned_to&lt;/code&gt;: Assigned user identifier.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;organization_id&lt;/code&gt;: Organization that owns the record.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;created_at&lt;/code&gt;: Creation timestamp.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;updated_at&lt;/code&gt;: Last modification timestamp.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;users-table&quot;&gt;Users table&lt;/h4&gt;
&lt;p&gt;The users table stores employee or account information.&lt;/p&gt;
&lt;p&gt;Typical fields include a unique identifier, name, email address, password hash, role, organization identifier, and account status.&lt;/p&gt;
&lt;p&gt;Never store passwords as plain text.&lt;/p&gt;
&lt;h4 id=&quot;activities-table&quot;&gt;Activities table&lt;/h4&gt;
&lt;p&gt;An activities table records interactions associated with leads.&lt;/p&gt;
&lt;p&gt;Its fields may include the lead identifier, activity type, description, scheduled time, completion status, creator, and creation timestamp.&lt;/p&gt;
&lt;p&gt;Examples of activities include phone calls, email conversations, meetings, and follow-up tasks.&lt;/p&gt;
&lt;h4 id=&quot;organizations-table&quot;&gt;Organizations table&lt;/h4&gt;
&lt;p&gt;If the dashboard serves multiple businesses, create an organizations table. Every relevant record should be associated with the correct organization.&lt;/p&gt;
&lt;p&gt;This helps prevent one company's users from accessing another company's customer information.&lt;/p&gt;
&lt;h4 id=&quot;campaigns-table&quot;&gt;Campaigns table&lt;/h4&gt;
&lt;p&gt;A campaigns table can store marketing campaign names, channels, budgets, and external identifiers. Leads can reference campaigns to support more detailed attribution reporting.&lt;/p&gt;
&lt;p&gt;Use foreign keys to maintain relationships and database constraints to enforce important rules.&lt;/p&gt;
&lt;h3 id=&quot;6-create-the-backend-project&quot;&gt;6. Create the Backend Project&lt;/h3&gt;
&lt;p&gt;Begin by creating a dedicated backend directory.&lt;/p&gt;
&lt;p&gt;A simple project structure could look like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;backend/
├── app/
│   ├── main.py
│   ├── database.py
│   ├── models.py
│   ├── schemas.py
│   ├── auth.py
│   ├── dependencies.py
│   └── routers/
│       ├── leads.py
│       ├── users.py
│       ├── activities.py
│       └── analytics.py
├── tests/
├── alembic/
├── requirements.txt
└── .env
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This structure separates API endpoints from database models, validation schemas, and authentication logic.&lt;/p&gt;
&lt;p&gt;Install FastAPI, Uvicorn, SQLAlchemy, a PostgreSQL driver, Pydantic settings, and the authentication libraries you choose.&lt;/p&gt;
&lt;p&gt;Create the FastAPI application in &lt;code&gt;main.py&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from fastapi import FastAPI
from app.routers import leads, users, activities, analytics

app = FastAPI(title=&quot;Lead Management API&quot;)

app.include_router(leads.router, prefix=&quot;/api/leads&quot;)
app.include_router(users.router, prefix=&quot;/api/users&quot;)
app.include_router(
    activities.router,
    prefix=&quot;/api/activities&quot;
)
app.include_router(
    analytics.router,
    prefix=&quot;/api/analytics&quot;
)

@app.get(&quot;/health&quot;)
def health_check():
    return {&quot;status&quot;: &quot;healthy&quot;}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This example assumes each router module exports a configured &lt;code&gt;router&lt;/code&gt; object.&lt;/p&gt;
&lt;p&gt;The health endpoint provides a simple way to verify that the backend is running.&lt;/p&gt;
&lt;p&gt;Keep configuration values such as database credentials in environment variables rather than hardcoding them into source files.&lt;/p&gt;
&lt;h3 id=&quot;7-connect-postgresql-to-fastapi&quot;&gt;7. Connect PostgreSQL to FastAPI&lt;/h3&gt;
&lt;p&gt;Create a PostgreSQL database for the application and configure its connection string through an environment variable.&lt;/p&gt;
&lt;p&gt;SQLAlchemy can manage database connections and transactions.&lt;/p&gt;
&lt;p&gt;A simplified database setup might look like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import os
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker

DATABASE_URL = os.environ[&quot;DATABASE_URL&quot;]

engine = create_engine(
    DATABASE_URL,
    pool_pre_ping=True
)

SessionLocal = sessionmaker(
    bind=engine,
    autoflush=False,
    expire_on_commit=False
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In production, choose an appropriate connection pool size and configure database timeouts according to the hosting environment.&lt;/p&gt;
&lt;p&gt;Create a FastAPI dependency that provides a database session for each request and reliably closes it afterward.&lt;/p&gt;
&lt;p&gt;Use Alembic to manage schema changes. Avoid relying on automatic table creation as your only production migration strategy.&lt;/p&gt;
&lt;h3 id=&quot;8-build-the-lead-api&quot;&gt;8. Build the Lead API&lt;/h3&gt;
&lt;p&gt;The backend should expose endpoints that represent the core operations of the application.&lt;/p&gt;
&lt;p&gt;A practical API design might include:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Method &lt;/th&gt;&lt;th&gt;Endpoint &lt;/th&gt;&lt;th&gt;Purpose&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;GET &lt;/td&gt;&lt;td&gt;&lt;code&gt;/api/leads&lt;/code&gt; &lt;/td&gt;&lt;td&gt;List and filter leads&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;POST &lt;/td&gt;&lt;td&gt;&lt;code&gt;/api/leads&lt;/code&gt; &lt;/td&gt;&lt;td&gt;Create a lead&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;GET &lt;/td&gt;&lt;td&gt;&lt;code&gt;/api/leads/{id}&lt;/code&gt; &lt;/td&gt;&lt;td&gt;Retrieve one lead&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;PATCH &lt;/td&gt;&lt;td&gt;&lt;code&gt;/api/leads/{id}&lt;/code&gt; &lt;/td&gt;&lt;td&gt;Update lead details&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;DELETE &lt;/td&gt;&lt;td&gt;&lt;code&gt;/api/leads/{id}&lt;/code&gt; &lt;/td&gt;&lt;td&gt;Delete or archive a lead&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;GET &lt;/td&gt;&lt;td&gt;&lt;code&gt;/api/analytics/summary&lt;/code&gt; &lt;/td&gt;&lt;td&gt;Retrieve dashboard statistics&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Use Pydantic schemas to validate incoming data and control which fields appear in responses.&lt;/p&gt;
&lt;p&gt;For example, a lead creation schema could look like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from pydantic import BaseModel, EmailStr
from typing import Optional

class LeadCreate(BaseModel):
    first_name: str
    last_name: str
    email: EmailStr
    phone: Optional[str] = None
    company: Optional[str] = None
    source: str = &quot;Website&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Additional validation should check allowed source values, required fields, field lengths, and business-specific rules.&lt;/p&gt;
&lt;p&gt;Do not allow a client to choose arbitrary ownership or organization identifiers without verifying authorization on the server.&lt;/p&gt;
&lt;h4 id=&quot;implement-lead-creation&quot;&gt;Implement lead creation&lt;/h4&gt;
&lt;p&gt;The create endpoint should:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Authenticate the user.&lt;/li&gt;
&lt;li&gt;Validate the submitted fields.&lt;/li&gt;
&lt;li&gt;Determine the organization from the authenticated user's context.&lt;/li&gt;
&lt;li&gt;Apply the correct assignment rules.&lt;/li&gt;
&lt;li&gt;Save the record within a database transaction.&lt;/li&gt;
&lt;li&gt;Return the created lead using a response schema.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Return appropriate HTTP status codes. For example, a successful creation typically returns &lt;code&gt;201 Created&lt;/code&gt;, invalid input returns &lt;code&gt;422 Unprocessable Entity&lt;/code&gt;, and an unauthorized request may return &lt;code&gt;401 Unauthorized&lt;/code&gt; or &lt;code&gt;403 Forbidden&lt;/code&gt;.&lt;/p&gt;
&lt;h4 id=&quot;implement-filtering-and-pagination&quot;&gt;Implement filtering and pagination&lt;/h4&gt;
&lt;p&gt;As the database grows, returning every lead in a single request becomes inefficient.&lt;/p&gt;
&lt;p&gt;Support query parameters such as:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;GET /api/leads?status=Qualified&amp;amp;page=1&amp;amp;page_size=20
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The backend should apply filters before pagination, enforce a reasonable maximum page size, and use deterministic sorting.&lt;/p&gt;
&lt;p&gt;For larger datasets, consider cursor-based pagination.&lt;/p&gt;
&lt;h3 id=&quot;9-implement-authentication-and-role-based-access&quot;&gt;9. Implement Authentication and Role-Based Access&lt;/h3&gt;
&lt;p&gt;A lead management dashboard often contains personal information, customer communications, and commercially sensitive data.&lt;/p&gt;
&lt;p&gt;Authentication determines who the user is. Authorization determines what that user can do.&lt;/p&gt;
&lt;p&gt;Common roles include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Administrator:&lt;/strong&gt; Manages organization settings and users.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Sales manager:&lt;/strong&gt; Views team performance and assigns leads.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Sales representative:&lt;/strong&gt; Manages assigned prospects and follow-ups.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Marketing analyst:&lt;/strong&gt; Reviews campaign sources and permitted analytics.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These roles are examples; actual permissions should reflect the business requirements.&lt;/p&gt;
&lt;p&gt;Use a well-established authentication approach, such as secure server-managed sessions or carefully implemented OAuth2-based authentication.&lt;/p&gt;
&lt;p&gt;If using JWTs, validate token signatures, expiration, issuer, and audience where applicable. Use secure password hashing such as Argon2id or an appropriately configured bcrypt implementation.&lt;/p&gt;
&lt;p&gt;For browser applications, secure, HTTP-only cookies can reduce exposure to token theft through JavaScript, but cookie-based authentication also requires appropriate CSRF protections.&lt;/p&gt;
&lt;p&gt;Most importantly, authorization checks must happen on the backend. Hiding a button in React does not prevent a user from directly calling the API.&lt;/p&gt;
&lt;p&gt;Every query involving a lead should enforce organization and user access restrictions.&lt;/p&gt;
&lt;h3 id=&quot;10-build-the-react-dashboard-interface&quot;&gt;10. Build the React Dashboard Interface&lt;/h3&gt;
&lt;p&gt;Once the API is available, build a responsive frontend that makes sales information easy to understand.&lt;/p&gt;
&lt;p&gt;A useful dashboard layout includes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A sidebar for navigation.&lt;/li&gt;
&lt;li&gt;A top navigation bar with search and account controls.&lt;/li&gt;
&lt;li&gt;Summary cards for important metrics.&lt;/li&gt;
&lt;li&gt;A sales pipeline overview.&lt;/li&gt;
&lt;li&gt;A recent leads table.&lt;/li&gt;
&lt;li&gt;A follow-up activity panel.&lt;/li&gt;
&lt;li&gt;Charts for sources and conversion performance.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Suggested pages include Dashboard, Leads, Pipeline, Activities, Reports, Team, and Settings.&lt;/p&gt;
&lt;p&gt;Use reusable components instead of building every page independently.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;src/
├── components/
│   ├── Sidebar.jsx
│   ├── Header.jsx
│   ├── MetricCard.jsx
│   ├── LeadTable.jsx
│   ├── LeadForm.jsx
│   └── StatusBadge.jsx
├── pages/
│   ├── Dashboard.jsx
│   ├── Leads.jsx
│   ├── Pipeline.jsx
│   └── Reports.jsx
├── services/
│   └── api.js
├── hooks/
├── App.jsx
└── main.jsx
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Keep API communication in a dedicated service layer. This prevents request logic from becoming scattered throughout the user interface.&lt;/p&gt;
&lt;p&gt;The dashboard should provide clear loading indicators, helpful error messages, empty states, and confirmation messages after successful actions.&lt;/p&gt;
&lt;h3 id=&quot;11-create-dashboard-metric-cards&quot;&gt;11. Create Dashboard Metric Cards&lt;/h3&gt;
&lt;p&gt;Metric cards provide an immediate overview of sales activity.&lt;/p&gt;
&lt;p&gt;Useful metrics include:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Total leads:&lt;/strong&gt; The number of accessible lead records.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;New leads:&lt;/strong&gt; Prospects created during a selected reporting period.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Qualified leads:&lt;/strong&gt; Prospects that meet the organization's qualification criteria.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Converted leads:&lt;/strong&gt; Prospects that have become customers or reached the defined Won stage.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Pending follow-ups:&lt;/strong&gt; Incomplete follow-up activities that are due or overdue.&lt;/p&gt;
&lt;p&gt;Each metric should have a clearly defined calculation.&lt;/p&gt;
&lt;p&gt;For example, if a business has 500 leads and 50 have converted, its simple lead conversion rate is:&lt;/p&gt;
&lt;p&gt;Conversion rate = (Converted leads ÷ Total leads) × 100&lt;/p&gt;
&lt;p&gt;In this example, the rate is 10%.&lt;/p&gt;
&lt;p&gt;However, the denominator matters. A conversion rate calculated from all historical leads differs from a rate calculated only from leads created during a specific month.&lt;/p&gt;
&lt;p&gt;For accurate analysis, define the reporting period, time zone, lead population, and conversion event.&lt;/p&gt;
&lt;p&gt;Avoid presenting numbers without explaining their meaning.&lt;/p&gt;
&lt;h3 id=&quot;12-build-data-visualization-and-analytics&quot;&gt;12. Build Data Visualization and Analytics&lt;/h3&gt;
&lt;p&gt;Charts help users identify patterns that may not be obvious in tables.&lt;/p&gt;
&lt;p&gt;Consider adding the following visualizations.&lt;/p&gt;
&lt;h4 id=&quot;leads-by-source&quot;&gt;Leads by source&lt;/h4&gt;
&lt;p&gt;A bar chart can compare leads generated by website forms, Google Ads, Facebook Ads, referrals, and other channels.&lt;/p&gt;
&lt;p&gt;This allows marketers to understand which sources contribute the most prospects.&lt;/p&gt;
&lt;h4 id=&quot;lead-pipeline&quot;&gt;Lead pipeline&lt;/h4&gt;
&lt;p&gt;A funnel or horizontal bar chart can show how many leads are in each stage.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;New: 120&lt;/li&gt;
&lt;li&gt;Contacted: 85&lt;/li&gt;
&lt;li&gt;Qualified: 50&lt;/li&gt;
&lt;li&gt;Proposal: 25&lt;/li&gt;
&lt;li&gt;Won: 12&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These numbers are illustrative. Real dashboards should calculate values directly from the database.&lt;/p&gt;
&lt;h4 id=&quot;conversion-trends&quot;&gt;Conversion trends&lt;/h4&gt;
&lt;p&gt;A line chart can show how conversion counts or rates change over time. This helps managers compare reporting periods and evaluate the effects of marketing campaigns.&lt;/p&gt;
&lt;h4 id=&quot;sales-representative-performance&quot;&gt;Sales representative performance&lt;/h4&gt;
&lt;p&gt;A report can compare assigned leads, completed follow-ups, qualified prospects, and conversions across team members.&lt;/p&gt;
&lt;p&gt;Use consistent metric definitions and ensure that users can see only the performance data they are authorized to access.&lt;/p&gt;
&lt;p&gt;Charts should include readable labels, useful tooltips, accessible colors, and alternative text or tabular data where appropriate.&lt;/p&gt;
&lt;h3 id=&quot;13-connect-react-to-the-backend-api&quot;&gt;13. Connect React to the Backend API&lt;/h3&gt;
&lt;p&gt;React needs a reliable way to communicate with FastAPI.&lt;/p&gt;
&lt;p&gt;Create a shared API client that defines the backend base URL, request defaults, and common error handling.&lt;/p&gt;
&lt;p&gt;A basic Fetch API request might look like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;const response = await fetch(
  `${API_URL}/api/leads?page=1&amp;amp;page_size=20`,
  {
    credentials: &quot;include&quot;,
  }
);

if (!response.ok) {
  throw new Error(&quot;Unable to load leads&quot;);
}

const leads = await response.json();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This example assumes cookie-based authentication and an appropriately configured backend. If the application uses bearer tokens, include the authorization header using the chosen secure token-management strategy.&lt;/p&gt;
&lt;p&gt;When developing locally, configure CORS to allow only the frontend origins that need access. CORS is not an authentication mechanism and does not replace server-side permission checks.&lt;/p&gt;
&lt;p&gt;For larger applications, consider a data-fetching library that supports caching, retries, request deduplication, and cache invalidation.&lt;/p&gt;
&lt;p&gt;After a lead is created or updated, refresh the affected list and statistics so the dashboard stays consistent.&lt;/p&gt;
&lt;h3 id=&quot;14-add-lead-search-and-advanced-filtering&quot;&gt;14. Add Lead Search and Advanced Filtering&lt;/h3&gt;
&lt;p&gt;A dashboard becomes significantly more useful when users can locate the right prospect quickly.&lt;/p&gt;
&lt;p&gt;Implement filters for:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Lead status.&lt;/li&gt;
&lt;li&gt;Lead source.&lt;/li&gt;
&lt;li&gt;Assigned sales representative.&lt;/li&gt;
&lt;li&gt;Creation date.&lt;/li&gt;
&lt;li&gt;Company.&lt;/li&gt;
&lt;li&gt;Follow-up date.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Combine filters with keyword search.&lt;/p&gt;
&lt;p&gt;For example, a sales manager might search for qualified leads from Facebook Ads created during the current month.&lt;/p&gt;
&lt;p&gt;The frontend should send filter parameters to the backend rather than downloading the entire database and filtering everything in the browser.&lt;/p&gt;
&lt;p&gt;Use database indexes on frequently queried columns where measurements justify them. For text search, PostgreSQL offers several options, including case-insensitive matching and full-text search.&lt;/p&gt;
&lt;p&gt;Debounce keyword input to avoid sending a request for every keystroke.&lt;/p&gt;
&lt;p&gt;Also, make filters visible and easy to clear. Users should be able to understand why a particular result appears in the table.&lt;/p&gt;
&lt;h3 id=&quot;15-create-the-sales-pipeline&quot;&gt;15. Create the Sales Pipeline&lt;/h3&gt;
&lt;p&gt;The sales pipeline represents the journey from a new prospect to a completed sale.&lt;/p&gt;
&lt;p&gt;A common workflow includes:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;New.&lt;/li&gt;
&lt;li&gt;Contacted.&lt;/li&gt;
&lt;li&gt;Qualified.&lt;/li&gt;
&lt;li&gt;Proposal Sent.&lt;/li&gt;
&lt;li&gt;Won.&lt;/li&gt;
&lt;li&gt;Lost.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Organizations should customize these stages according to their actual sales process.&lt;/p&gt;
&lt;p&gt;When a representative changes a lead's status, the backend should validate whether the transition is allowed and record the change.&lt;/p&gt;
&lt;p&gt;A lead history table can preserve the previous status, new status, user who made the change, and timestamp.&lt;/p&gt;
&lt;p&gt;This history supports reporting and helps managers understand how opportunities move through the pipeline.&lt;/p&gt;
&lt;p&gt;A drag-and-drop interface can improve usability, but it should call the same validated backend endpoint as a normal status-editing form.&lt;/p&gt;
&lt;p&gt;Do not rely on frontend restrictions alone to enforce workflow rules.&lt;/p&gt;
&lt;h3 id=&quot;16-add-follow-up-tasks-and-reminders&quot;&gt;16. Add Follow-Up Tasks and Reminders&lt;/h3&gt;
&lt;p&gt;Many sales opportunities are lost because representatives forget to follow up.&lt;/p&gt;
&lt;p&gt;A follow-up module helps prevent this problem by associating tasks with leads.&lt;/p&gt;
&lt;p&gt;Each task may contain:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Lead identifier.&lt;/li&gt;
&lt;li&gt;Activity type.&lt;/li&gt;
&lt;li&gt;Task description.&lt;/li&gt;
&lt;li&gt;Due date and time.&lt;/li&gt;
&lt;li&gt;Assigned user.&lt;/li&gt;
&lt;li&gt;Completion status.&lt;/li&gt;
&lt;li&gt;Creation timestamp.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The dashboard can display upcoming tasks and highlight overdue activities.&lt;/p&gt;
&lt;p&gt;For example, a representative may see a reminder to call a qualified prospect tomorrow morning.&lt;/p&gt;
&lt;p&gt;For automatic reminders, use a background job system or a scheduled worker. Avoid relying on a browser tab remaining open to execute important scheduled tasks.&lt;/p&gt;
&lt;p&gt;Notifications can initially appear inside the dashboard. Later, email or other supported notification channels can be integrated.&lt;/p&gt;
&lt;p&gt;When sending reminders, handle retries carefully to avoid delivering duplicate messages.&lt;/p&gt;
&lt;h3 id=&quot;17-implement-lead-assignment&quot;&gt;17. Implement Lead Assignment&lt;/h3&gt;
&lt;p&gt;Lead assignment distributes incoming prospects among sales representatives.&lt;/p&gt;
&lt;p&gt;The simplest implementation allows managers to assign leads manually.&lt;/p&gt;
&lt;p&gt;More advanced systems can use rules such as:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Round-robin assignment.&lt;/li&gt;
&lt;li&gt;Assignment based on product category.&lt;/li&gt;
&lt;li&gt;Assignment by region.&lt;/li&gt;
&lt;li&gt;Assignment based on representative workload.&lt;/li&gt;
&lt;li&gt;Assignment according to lead source.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For example, a round-robin system distributes new leads among eligible representatives in sequence.&lt;/p&gt;
&lt;p&gt;A production implementation must account for simultaneous incoming leads, inactive users, and assignment failures. Database transactions or suitable locking strategies can help prevent multiple requests from incorrectly assigning the same sequence position.&lt;/p&gt;
&lt;p&gt;Keep assignment rules transparent so managers understand why a prospect was assigned to a particular representative.&lt;/p&gt;
&lt;h3 id=&quot;18-build-reporting-and-campaign-attribution&quot;&gt;18. Build Reporting and Campaign Attribution&lt;/h3&gt;
&lt;p&gt;Marketing teams need to understand not only how many leads they generate but also how those leads perform.&lt;/p&gt;
&lt;p&gt;Store source and campaign information when a lead enters the system.&lt;/p&gt;
&lt;p&gt;Where appropriate, capture UTM parameters such as:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;utm_source&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;utm_medium&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;utm_campaign&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;utm_content&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;utm_term&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Preserve attribution information according to a documented policy. First-touch attribution, last-touch attribution, and multi-touch attribution answer different questions.&lt;/p&gt;
&lt;p&gt;A dashboard can report leads by source, qualified leads by campaign, conversion rates, and cost per lead when reliable cost data is available.&lt;/p&gt;
&lt;p&gt;Cost per lead is calculated as:&lt;/p&gt;
&lt;p&gt;Cost per lead = Marketing spend ÷ Leads generated&lt;/p&gt;
&lt;p&gt;For example, a campaign spending $1,000 and generating 200 leads has a cost per lead of $5.&lt;/p&gt;
&lt;p&gt;However, inexpensive leads are not necessarily valuable leads. Comparing qualified-lead rates and eventual revenue can produce better marketing decisions.&lt;/p&gt;
&lt;p&gt;Avoid attributing conversions to campaigns without a consistent methodology and adequate tracking data.&lt;/p&gt;
&lt;h3 id=&quot;19-improve-performance-as-the-dataset-grows&quot;&gt;19. Improve Performance as the Dataset Grows&lt;/h3&gt;
&lt;p&gt;A dashboard that performs well with 100 records may become slow when it contains hundreds of thousands of leads and activities.&lt;/p&gt;
&lt;p&gt;Performance should be measured before optimization, but several practices provide a strong starting point.&lt;/p&gt;
&lt;h4 id=&quot;database-optimization&quot;&gt;Database optimization&lt;/h4&gt;
&lt;p&gt;Use indexes for common filters and joins. Review slow queries with PostgreSQL's query-planning tools. Avoid fetching columns that the current page does not need.&lt;/p&gt;
&lt;h4 id=&quot;efficient-api-responses&quot;&gt;Efficient API responses&lt;/h4&gt;
&lt;p&gt;Use pagination, limit response sizes, and avoid unnecessary database queries.&lt;/p&gt;
&lt;p&gt;Be careful with ORM relationships that can generate an excessive number of queries. Eager loading or carefully designed aggregate queries may improve performance.&lt;/p&gt;
&lt;h4 id=&quot;frontend-optimization&quot;&gt;Frontend optimization&lt;/h4&gt;
&lt;p&gt;Render only the data needed for the current page. Consider table virtualization for exceptionally large lists.&lt;/p&gt;
&lt;p&gt;Use code splitting when the application contains many pages, and avoid recalculating expensive derived values on every render.&lt;/p&gt;
&lt;h4 id=&quot;caching&quot;&gt;Caching&lt;/h4&gt;
&lt;p&gt;Cache suitable, frequently requested information when doing so provides a measurable benefit.&lt;/p&gt;
&lt;p&gt;Analytics caching must account for updates, permissions, and reporting periods. Never share cached user-specific results across organizations.&lt;/p&gt;
&lt;h3 id=&quot;20-secure-the-application&quot;&gt;20. Secure the Application&lt;/h3&gt;
&lt;p&gt;Security should be part of the initial architecture rather than a final checklist.&lt;/p&gt;
&lt;p&gt;A lead dashboard may store names, email addresses, telephone numbers, company details, and sales notes.&lt;/p&gt;
&lt;p&gt;Important security measures include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Enforce authentication and authorization on every protected API.&lt;/li&gt;
&lt;li&gt;Scope database queries to the correct organization.&lt;/li&gt;
&lt;li&gt;Hash passwords using established algorithms.&lt;/li&gt;
&lt;li&gt;Use HTTPS in production.&lt;/li&gt;
&lt;li&gt;Store secrets in environment variables or a secret manager.&lt;/li&gt;
&lt;li&gt;Validate and normalize input.&lt;/li&gt;
&lt;li&gt;Apply rate limits to sensitive endpoints.&lt;/li&gt;
&lt;li&gt;Protect cookie-based authentication against CSRF.&lt;/li&gt;
&lt;li&gt;Configure appropriate security headers.&lt;/li&gt;
&lt;li&gt;Avoid exposing internal errors and stack traces.&lt;/li&gt;
&lt;li&gt;Restrict access to exports and reports.&lt;/li&gt;
&lt;li&gt;Keep dependencies updated.&lt;/li&gt;
&lt;li&gt;Record important administrative and data-access events when appropriate.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;SQLAlchemy parameterizes normal query operations, but developers should still avoid unsafe raw SQL construction.&lt;/p&gt;
&lt;p&gt;Prevent mass-assignment vulnerabilities by explicitly controlling which fields users can update.&lt;/p&gt;
&lt;p&gt;Establish retention and deletion policies for customer information. Depending on where the business operates and where its customers are located, applicable privacy requirements may include the GDPR or other local data-protection laws.&lt;/p&gt;
&lt;p&gt;Security is an ongoing responsibility that includes code review, testing, monitoring, and incident response.&lt;/p&gt;
&lt;h3 id=&quot;21-make-the-dashboard-responsive-and-accessible&quot;&gt;21. Make the Dashboard Responsive and Accessible&lt;/h3&gt;
&lt;p&gt;Sales teams may use the dashboard on desktops, laptops, tablets, and mobile phones.&lt;/p&gt;
&lt;p&gt;A responsive layout should adapt to different screen widths without making important actions difficult to reach.&lt;/p&gt;
&lt;p&gt;On smaller screens, collapse the sidebar into a navigation menu and allow wide tables to scroll horizontally or switch to a compact card layout.&lt;/p&gt;
&lt;p&gt;Accessibility is equally important.&lt;/p&gt;
&lt;p&gt;Use semantic HTML, properly labeled form fields, keyboard-accessible controls, visible focus indicators, and sufficient color contrast.&lt;/p&gt;
&lt;p&gt;Do not communicate lead status through color alone. Pair status colors with descriptive labels.&lt;/p&gt;
&lt;p&gt;Provide clear validation messages when users submit incomplete or invalid forms.&lt;/p&gt;
&lt;p&gt;Charts should communicate their meaning without requiring users to distinguish subtle colors. Where possible, provide equivalent data tables.&lt;/p&gt;
&lt;p&gt;A dashboard that is accessible and responsive is easier for more people to use effectively.&lt;/p&gt;
&lt;h3 id=&quot;22-test-the-application-thoroughly&quot;&gt;22. Test the Application Thoroughly&lt;/h3&gt;
&lt;p&gt;Testing verifies that the dashboard behaves correctly under normal conditions and unexpected situations.&lt;/p&gt;
&lt;h4 id=&quot;backend-tests&quot;&gt;Backend tests&lt;/h4&gt;
&lt;p&gt;Use pytest to test API endpoints, validation rules, permissions, and business logic.&lt;/p&gt;
&lt;p&gt;Examples include verifying that a valid lead can be created, invalid email addresses are rejected, unauthorized users cannot access restricted records, and status changes follow the defined workflow.&lt;/p&gt;
&lt;h4 id=&quot;frontend-tests&quot;&gt;Frontend tests&lt;/h4&gt;
&lt;p&gt;Use tools such as Vitest and React Testing Library to test forms, filters, navigation, loading states, and error handling.&lt;/p&gt;
&lt;h4 id=&quot;integration-tests&quot;&gt;Integration tests&lt;/h4&gt;
&lt;p&gt;Verify that the frontend, backend, and database work together correctly.&lt;/p&gt;
&lt;p&gt;For example, create a lead through the API and confirm that it appears in the filtered lead list and contributes to the appropriate dashboard metric.&lt;/p&gt;
&lt;h4 id=&quot;end-to-end-tests&quot;&gt;End-to-end tests&lt;/h4&gt;
&lt;p&gt;Playwright can simulate a complete user journey:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Sign in.&lt;/li&gt;
&lt;li&gt;Open the leads page.&lt;/li&gt;
&lt;li&gt;Create a prospect.&lt;/li&gt;
&lt;li&gt;Assign the prospect.&lt;/li&gt;
&lt;li&gt;Schedule a follow-up.&lt;/li&gt;
&lt;li&gt;Update the lead's status.&lt;/li&gt;
&lt;li&gt;Verify the updated dashboard.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Also test empty datasets, duplicate submissions, expired sessions, slow network connections, and concurrent updates.&lt;/p&gt;
&lt;p&gt;Use a separate test database and avoid running destructive tests against production data.&lt;/p&gt;
&lt;h3 id=&quot;23-deploy-the-application&quot;&gt;23. Deploy the Application&lt;/h3&gt;
&lt;p&gt;Once the application has passed its tests, prepare it for deployment.&lt;/p&gt;
&lt;p&gt;A typical production architecture includes a React frontend, a FastAPI application server, a managed PostgreSQL database, and secure HTTPS access.&lt;/p&gt;
&lt;p&gt;Possible deployment options include static frontend hosting, a container platform for FastAPI, and a managed PostgreSQL service.&lt;/p&gt;
&lt;p&gt;Docker can package the backend and its dependencies consistently. A production setup should also define health checks, restart policies, resource limits, and appropriate logging.&lt;/p&gt;
&lt;p&gt;Before deploying:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Configure production environment variables.&lt;/li&gt;
&lt;li&gt;Set up the production database.&lt;/li&gt;
&lt;li&gt;Run database migrations.&lt;/li&gt;
&lt;li&gt;Build and deploy the React frontend.&lt;/li&gt;
&lt;li&gt;Deploy the FastAPI application.&lt;/li&gt;
&lt;li&gt;Configure the correct frontend origins and API URL.&lt;/li&gt;
&lt;li&gt;Enable HTTPS.&lt;/li&gt;
&lt;li&gt;Create a database backup policy.&lt;/li&gt;
&lt;li&gt;Test authentication and organization-level permissions.&lt;/li&gt;
&lt;li&gt;Verify logging, monitoring, and error reporting.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Do not expose PostgreSQL directly to the public internet unless there is a carefully justified and secured requirement.&lt;/p&gt;
&lt;p&gt;Run database migrations as a controlled deployment step. For changes that may affect existing records, use a migration strategy that supports safe rollout and recovery.&lt;/p&gt;
&lt;p&gt;A successful deployment means more than opening the homepage. Users must be able to authenticate, create leads, update records, view analytics, and recover from errors reliably.&lt;/p&gt;
&lt;h3 id=&quot;24-monitor-and-maintain-the-dashboard&quot;&gt;24. Monitor and Maintain the Dashboard&lt;/h3&gt;
&lt;p&gt;After launch, monitor how the application behaves in real usage.&lt;/p&gt;
&lt;p&gt;Track API response times, server errors, database connections, slow queries, background job failures, and frontend exceptions.&lt;/p&gt;
&lt;p&gt;Add structured logs with request identifiers so developers can investigate issues without recording unnecessary personal information.&lt;/p&gt;
&lt;p&gt;Monitor the health of reminder jobs and integrations. A dashboard may appear operational even when its scheduled notifications have silently stopped working.&lt;/p&gt;
&lt;p&gt;Regularly review dependencies, access permissions, backups, and recovery procedures.&lt;/p&gt;
&lt;p&gt;Gather feedback from sales representatives and managers. They may identify practical improvements, such as faster lead entry, clearer status labels, or better overdue-task visibility.&lt;/p&gt;
&lt;p&gt;Use actual user behavior to prioritize improvements instead of adding features simply because they are technically interesting.&lt;/p&gt;
&lt;h3 id=&quot;25-common-mistakes-to-avoid&quot;&gt;25. Common Mistakes to Avoid&lt;/h3&gt;
&lt;p&gt;Developers frequently encounter similar problems when building CRM-style applications.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Building too many features too early:&lt;/strong&gt; Start with the core workflow and expand after the MVP is stable.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Using an unclear database structure:&lt;/strong&gt; Define relationships, constraints, and ownership rules before implementing complex features.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Trusting the frontend for security:&lt;/strong&gt; Enforce permissions and validation on the backend.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Loading every record at once:&lt;/strong&gt; Use server-side filtering and pagination.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Calculating metrics inconsistently:&lt;/strong&gt; Document reporting definitions and use consistent date boundaries.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Ignoring follow-up workflows:&lt;/strong&gt; Make reminders and activity tracking part of the initial design if sales follow-ups are central to the business.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Skipping automated tests:&lt;/strong&gt; Test critical workflows and access controls before deployment.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Ignoring backups and monitoring:&lt;/strong&gt; Prepare for failures rather than assuming the application will always remain available.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Overcomplicating the architecture:&lt;/strong&gt; Start with a well-organized modular application. Introduce microservices only when specific requirements justify their additional operational cost.&lt;/p&gt;
&lt;p&gt;Avoiding these mistakes can save significant development time and reduce the risk of expensive problems after launch.&lt;/p&gt;
&lt;h3 id=&quot;26-suggested-development-roadmap&quot;&gt;26. Suggested Development Roadmap&lt;/h3&gt;
&lt;p&gt;A phased roadmap makes the project easier to manage.&lt;/p&gt;
&lt;h4 id=&quot;phase-1-planning-and-design&quot;&gt;Phase 1: Planning and design&lt;/h4&gt;
&lt;p&gt;Define user roles, lead statuses, database relationships, wireframes, and API contracts.&lt;/p&gt;
&lt;h4 id=&quot;phase-2-backend-development&quot;&gt;Phase 2: Backend development&lt;/h4&gt;
&lt;p&gt;Create the database schema, authentication system, lead CRUD endpoints, filtering, pagination, and validation.&lt;/p&gt;
&lt;h4 id=&quot;phase-3-frontend-development&quot;&gt;Phase 3: Frontend development&lt;/h4&gt;
&lt;p&gt;Build the navigation, dashboard layout, lead forms, data tables, filters, and API integration.&lt;/p&gt;
&lt;h4 id=&quot;phase-4-sales-workflows&quot;&gt;Phase 4: Sales workflows&lt;/h4&gt;
&lt;p&gt;Implement lead assignment, pipeline stages, activity tracking, and follow-up reminders.&lt;/p&gt;
&lt;h4 id=&quot;phase-5-analytics&quot;&gt;Phase 5: Analytics&lt;/h4&gt;
&lt;p&gt;Add metric cards, source reports, conversion charts, and campaign attribution.&lt;/p&gt;
&lt;h4 id=&quot;phase-6-security-and-testing&quot;&gt;Phase 6: Security and testing&lt;/h4&gt;
&lt;p&gt;Test permissions, validate business rules, audit sensitive operations, and run automated tests.&lt;/p&gt;
&lt;h4 id=&quot;phase-7-deployment-and-improvement&quot;&gt;Phase 7: Deployment and improvement&lt;/h4&gt;
&lt;p&gt;Deploy the application, configure monitoring and backups, gather feedback, and prioritize improvements.&lt;/p&gt;
&lt;p&gt;The duration of each phase depends on the developer's experience, team size, integration requirements, and project scope.&lt;/p&gt;
&lt;h3 id=&quot;conclusion-1&quot;&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;Building a lead management dashboard from scratch is a valuable full-stack development project that combines practical business requirements with modern engineering techniques.&lt;/p&gt;
&lt;p&gt;React provides an interactive frontend, FastAPI handles API requests and business logic, and PostgreSQL stores structured lead and activity data. Together, these technologies support a maintainable foundation for lead tracking, sales pipeline management, team collaboration, and marketing analytics.&lt;/p&gt;
&lt;p&gt;The most important part of the project is not simply creating attractive charts or displaying numbers. It is building a reliable workflow that helps businesses capture prospects, assign responsibility, maintain consistent records, follow up at the right time, and measure results accurately.&lt;/p&gt;
&lt;p&gt;Start with a small, secure, well-tested MVP. Focus on clear database relationships, consistent business rules, meaningful dashboard metrics, and a responsive user experience. Then introduce automation, advanced reporting, and external integrations as the business needs evolve.&lt;/p&gt;
&lt;p&gt;For full-stack developers, this project demonstrates much more than the ability to write React components or create API endpoints. It shows an understanding of system architecture, data modeling, authentication, analytics, testing, deployment, and real-world business processes.&lt;/p&gt;
&lt;p&gt;A well-designed lead management dashboard can become the foundation for a broader CRM platform and a strong portfolio project that demonstrates practical software development skills.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/31356/how-to-build-a-lead-management-dashboard-from-scratch</guid>
<pubDate>Fri, 16 Oct 2026 13:24:35 +0000</pubDate>
</item>
<item>
<title>How to Build a Customer Management System with React and FastAPI</title>
<link>https://coderlegion.com/31007/how-to-build-a-customer-management-system-with-react-and-fastapi</link>
<description>&lt;p&gt;Customer relationships are one of the most important parts of running a successful business. Whether a company operates an e-commerce store, a digital marketing agency, a software development firm, or a consulting service, managing customer information efficiently can make a significant difference in productivity and customer satisfaction.&lt;/p&gt;
&lt;p&gt;Many businesses begin by storing customer information in spreadsheets, documents, or disconnected applications. Although these methods may work for a small number of customers, they become increasingly difficult to maintain as a business grows. Duplicate records, missing information, delayed follow-ups, and limited reporting can eventually affect the entire customer management process.&lt;/p&gt;
&lt;p&gt;A Customer Management System (CMS) provides a centralized place to organize customer information, track interactions, update contact details, and manage business relationships. When designed properly, it helps teams access accurate information, collaborate more effectively, and make better decisions.&lt;/p&gt;
&lt;p&gt;In this article, we will explore how to build a customer management system using React for the frontend and FastAPI for the backend. We will discuss the architecture, database design, API development, authentication, CRUD operations, frontend integration, testing, deployment, and security considerations.&lt;/p&gt;
&lt;p&gt;The goal is not simply to create an application that displays customer records. Instead, we will examine how to build a maintainable, secure, and scalable application using modern full-stack development practices.&lt;/p&gt;
&lt;h3 id=&quot;1-understanding-the-customer-management-system&quot;&gt;1. Understanding the Customer Management System&lt;/h3&gt;
&lt;p&gt;Before writing code, we need to understand what our application should accomplish.&lt;/p&gt;
&lt;p&gt;A customer management system stores customer information and provides tools for managing that information throughout the customer lifecycle.&lt;/p&gt;
&lt;p&gt;For our example project, we will build a web application that allows authorized users to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;View a list of customers.&lt;/li&gt;
&lt;li&gt;Add new customer records.&lt;/li&gt;
&lt;li&gt;Update existing customer information.&lt;/li&gt;
&lt;li&gt;Delete customer records when appropriate.&lt;/li&gt;
&lt;li&gt;Search for customers by name or email.&lt;/li&gt;
&lt;li&gt;Filter records by customer status.&lt;/li&gt;
&lt;li&gt;View individual customer details.&lt;/li&gt;
&lt;li&gt;Access the system through an authenticated account.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Additional features, such as activity histories, notes, sales opportunities, and reporting dashboards, can be added as the application evolves.&lt;/p&gt;
&lt;h4 id=&quot;why-choose-react-and-fastapi-&quot;&gt;Why choose React and FastAPI?&lt;/h4&gt;
&lt;p&gt;React and FastAPI complement each other well because they solve different parts of the application.&lt;/p&gt;
&lt;p&gt;React is a JavaScript library for building interactive user interfaces. It allows developers to create reusable components, manage application state, and update the interface when data changes.&lt;/p&gt;
&lt;p&gt;FastAPI is a Python framework for developing APIs. It supports request validation, automatic API documentation, asynchronous programming, and type hints.&lt;/p&gt;
&lt;p&gt;Together, these technologies provide a practical foundation for a modern customer management application.&lt;/p&gt;
&lt;p&gt;React handles presentation and user interactions, while FastAPI handles business logic, validation, authorization, and communication with the database.&lt;/p&gt;
&lt;h3 id=&quot;2-planning-the-application-architecture&quot;&gt;2. Planning the Application Architecture&lt;/h3&gt;
&lt;p&gt;A well-designed application separates responsibilities into distinct layers.&lt;/p&gt;
&lt;p&gt;Our customer management system will use three primary components:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Frontend:&lt;/strong&gt; React provides the user interface, including forms, tables, search fields, and customer detail views.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Backend:&lt;/strong&gt; FastAPI exposes HTTP endpoints that receive requests, validate input, enforce business rules, and return responses.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Database:&lt;/strong&gt; PostgreSQL stores customer records persistently and supports structured queries.&lt;/p&gt;
&lt;p&gt;The application follows a typical client-server architecture.&lt;/p&gt;
&lt;p&gt;When a user opens the customer list, React sends an HTTP request to FastAPI. The backend queries PostgreSQL, retrieves the relevant records, and returns them as JSON. React then renders the returned information in the interface.&lt;/p&gt;
&lt;p&gt;When a user creates a customer, the frontend sends the form data to the backend. FastAPI validates the request, saves the record in the database, and returns the newly created customer.&lt;/p&gt;
&lt;p&gt;This separation makes the application easier to test, maintain, and extend.&lt;/p&gt;
&lt;p&gt;For example, we could later replace the React frontend with a mobile application without rewriting the core customer management logic, provided the API remains compatible.&lt;/p&gt;
&lt;h3 id=&quot;3-setting-up-the-development-environment&quot;&gt;3. Setting Up the Development Environment&lt;/h3&gt;
&lt;p&gt;Before building the application, install the required development tools:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Python 3.11 or a compatible modern Python version.&lt;/li&gt;
&lt;li&gt;Node.js and npm.&lt;/li&gt;
&lt;li&gt;PostgreSQL.&lt;/li&gt;
&lt;li&gt;A code editor such as Visual Studio Code.&lt;/li&gt;
&lt;li&gt;Git for version control.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Create a project directory with separate frontend and backend folders.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;customer-management-system/
├── backend/
│   ├── app/
│   │   ├── main.py
│   │   ├── database.py
│   │   ├── models.py
│   │   ├── schemas.py
│   │   └── routers/
│   │       └── customers.py
│   ├── requirements.txt
│   └── .env
│
├── frontend/
│   ├── src/
│   │   ├── components/
│   │   ├── pages/
│   │   ├── services/
│   │   └── App.jsx
│   └── package.json
│
└── README.md
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This structure is intentionally simple. As the application grows, we can introduce dedicated service layers, authentication modules, tests, and database migration files.&lt;/p&gt;
&lt;h4 id=&quot;creating-the-react-application&quot;&gt;Creating the React application&lt;/h4&gt;
&lt;p&gt;Use Vite to initialize the frontend.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npm create vite@latest frontend -- --template react
cd frontend
npm install
npm run dev
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Vite provides a fast development server and a straightforward production build process.&lt;/p&gt;
&lt;h4 id=&quot;creating-the-fastapi-backend&quot;&gt;Creating the FastAPI backend&lt;/h4&gt;
&lt;p&gt;Create a Python virtual environment inside the backend directory.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;cd backend
python -m venv venv
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Activate the environment on Windows:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;venv\Scripts\activate
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;On Linux or macOS, use:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;source venv/bin/activate
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Install the main backend dependencies.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;pip install fastapi uvicorn sqlalchemy \
  psycopg[binary] pydantic-settings
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;SQLAlchemy will manage database interactions, while the PostgreSQL driver allows Python to connect to the database.&lt;/p&gt;
&lt;p&gt;Record the installed dependencies in &lt;code&gt;requirements.txt&lt;/code&gt; so other developers can reproduce the environment.&lt;/p&gt;
&lt;h3 id=&quot;4-designing-the-customer-database&quot;&gt;4. Designing the Customer Database&lt;/h3&gt;
&lt;p&gt;Database design is a critical step because customer information must remain consistent as the application grows.&lt;/p&gt;
&lt;p&gt;For our initial version, a customer record will include:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Field &lt;/th&gt;&lt;th&gt;Purpose&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;id &lt;/td&gt;&lt;td&gt;Unique customer identifier&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;full_name &lt;/td&gt;&lt;td&gt;Customer's full name&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;email &lt;/td&gt;&lt;td&gt;Contact email address&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;phone &lt;/td&gt;&lt;td&gt;Optional phone number&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;company &lt;/td&gt;&lt;td&gt;Optional company name&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;status &lt;/td&gt;&lt;td&gt;Current customer status&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;created_at &lt;/td&gt;&lt;td&gt;Record creation timestamp&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The &lt;code&gt;id&lt;/code&gt; field serves as the primary key. Email addresses should generally be unique if the business considers one email address to represent one customer account.&lt;/p&gt;
&lt;p&gt;The status field might contain values such as &lt;code&gt;lead&lt;/code&gt;, &lt;code&gt;active&lt;/code&gt;, or &lt;code&gt;inactive&lt;/code&gt;. The exact categories should reflect the business's actual workflow.&lt;br&gt;
In PostgreSQL, the conceptual table could look like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;CREATE TABLE customers (
    id SERIAL PRIMARY KEY,
    full_name VARCHAR(150) NOT NULL,
    email VARCHAR(255) NOT NULL UNIQUE,
    phone VARCHAR(30),
    company VARCHAR(150),
    status VARCHAR(20) NOT NULL DEFAULT 'lead',
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This SQL example illustrates the table structure. In the application, we should use a database migration system, such as Alembic, to manage schema changes safely.&lt;/p&gt;
&lt;p&gt;We should also consider indexes for frequently searched fields. A unique constraint on email already creates a suitable database index in PostgreSQL. Additional indexes should be introduced when query patterns and performance measurements justify them.&lt;/p&gt;
&lt;p&gt;For a production application, consider storing customer interactions in separate tables rather than placing every activity into the customer record. This allows one customer to have many notes, calls, emails, or sales opportunities without creating an unnecessarily large table.&lt;/p&gt;
&lt;h3 id=&quot;5-connecting-fastapi-to-postgresql&quot;&gt;5. Connecting FastAPI to PostgreSQL&lt;/h3&gt;
&lt;p&gt;The backend needs a reliable database connection before it can retrieve or modify customer records.&lt;/p&gt;
&lt;p&gt;Create a &lt;code&gt;.env&lt;/code&gt; file containing the database connection string.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-env&quot;&gt;DATABASE_URL=postgresql+psycopg://user:password@localhost/customer_db
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Replace the example credentials with your own development database details. Never commit real production credentials to Git.&lt;br&gt;
Next, create &lt;code&gt;database.py&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import os

from sqlalchemy import create_engine
from sqlalchemy.orm import DeclarativeBase, sessionmaker

DATABASE_URL = os.environ[&quot;DATABASE_URL&quot;]

engine = create_engine(
    DATABASE_URL,
    pool_pre_ping=True,
)

SessionLocal = sessionmaker(
    bind=engine,
    autoflush=False,
    expire_on_commit=False,
)

class Base(DeclarativeBase):
    pass
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For local development, you can load environment variables using a configuration library or a development environment loader. In production, prefer your hosting platform's secret management system.&lt;/p&gt;
&lt;p&gt;We can provide a database session to FastAPI endpoints through a dependency.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from collections.abc import Generator

from sqlalchemy.orm import Session

from app.database import SessionLocal

def get_db() -&amp;gt; Generator[Session, None, None]:
    db = SessionLocal()
    try:
        yield db
    finally:
        db.close()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This pattern ensures that each request receives a database session and that the session is closed afterward.&lt;/p&gt;
&lt;p&gt;For synchronous SQLAlchemy sessions, use regular &lt;code&gt;def&lt;/code&gt; endpoints rather than performing blocking database operations inside an &lt;code&gt;async def&lt;/code&gt; endpoint. Alternatively, use SQLAlchemy's asynchronous engine and session APIs when an asynchronous database architecture is appropriate.&lt;/p&gt;
&lt;h3 id=&quot;6-creating-the-customer-model&quot;&gt;6. Creating the Customer Model&lt;/h3&gt;
&lt;p&gt;SQLAlchemy models describe how application objects map to database tables.&lt;/p&gt;
&lt;p&gt;Create &lt;code&gt;models.py&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from datetime import datetime

from sqlalchemy import (
    DateTime,
    String,
    func,
)
from sqlalchemy.orm import Mapped, mapped_column

from app.database import Base

class Customer(Base):
    __tablename__ = &quot;customers&quot;

    id: Mapped[int] = mapped_column(
        primary_key=True
    )

    full_name: Mapped[str] = mapped_column(
        String(150),
        nullable=False,
    )
    email: Mapped[str] = mapped_column(
        String(255),
        unique=True,
        index=True,
        nullable=False,
    )
    phone: Mapped[str | None] = mapped_column(
        String(30),
        nullable=True,
    )
    company: Mapped[str | None] = mapped_column(
        String(150),
        nullable=True,
    )
    status: Mapped[str] = mapped_column(
        String(20),
        default=&quot;lead&quot;,
        nullable=False,
    )
    created_at: Mapped[datetime] = mapped_column(
        DateTime(timezone=True),
        server_default=func.now(),
        nullable=False,
    )
The model establishes the relationship between Python objects and the customer table.

Notice that optional values use nullable fields, while required values are explicitly marked as non-nullable.

The database should enforce critical constraints independently of frontend validation. A malicious or incorrectly configured client must not be able to bypass important data integrity rules simply by sending a direct HTTP request.

In this example, `status` is a string. A production implementation should validate it against an explicit set of allowed values and may use a database enum or a check constraint for stronger consistency.

## 7. Defining Request and Response Schemas

Database models and API schemas serve different purposes.

A database model describes how data is stored. A Pydantic schema describes what the API accepts and returns.

Separating these responsibilities prevents clients from controlling internal fields such as database identifiers, timestamps, or administrative flags.

Create `schemas.py`.

```python
from datetime import datetime
from typing import Literal

from pydantic import BaseModel, ConfigDict, EmailStr, Field

CustomerStatus = Literal[&quot;lead&quot;, &quot;active&quot;, &quot;inactive&quot;]

class CustomerCreate(BaseModel):
    full_name: str = Field(
        min_length=2,
        max_length=150,
    )
    email: EmailStr
    phone: str | None = Field(
        default=None,
        max_length=30,
    )
    company: str | None = Field(
        default=None,
        max_length=150,
    )
    status: CustomerStatus = &quot;lead&quot;

class CustomerUpdate(BaseModel):
    full_name: str | None = Field(
        default=None,
        min_length=2,
        max_length=150,
    )
    email: EmailStr | None = None
    phone: str | None = Field(
        default=None,
        max_length=30,
    )
    company: str | None = Field(
        default=None,
        max_length=150,
    )
    status: CustomerStatus | None = None

class CustomerResponse(BaseModel):
    model_config = ConfigDict(from_attributes=True)

    id: int
    full_name: str
    email: str
    phone: str | None
    company: str | None
    status: CustomerStatus
    created_at: datetime
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Install the email validation dependency if needed:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;pip install email-validator
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Pydantic validates incoming values and helps FastAPI produce consistent API responses.&lt;/p&gt;
&lt;p&gt;One important detail concerns partial updates. We will use &lt;code&gt;model_dump(exclude_unset=True)&lt;/code&gt; so that fields omitted from a PATCH request remain unchanged. If the client explicitly sends &lt;code&gt;null&lt;/code&gt; for an optional field, the backend can distinguish that from an omitted field.&lt;/p&gt;
&lt;p&gt;We should also ensure that a missing or null &lt;code&gt;full_name&lt;/code&gt; is rejected during a partial update rather than being written into a required database column.&lt;/p&gt;
&lt;h3 id=&quot;8-building-the-customer-api&quot;&gt;8. Building the Customer API&lt;/h3&gt;
&lt;p&gt;Now we can create the endpoints that power the application.&lt;/p&gt;
&lt;p&gt;A practical API design might include:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Method &lt;/th&gt;&lt;th&gt;Endpoint &lt;/th&gt;&lt;th&gt;Purpose&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;GET &lt;/td&gt;&lt;td&gt;&lt;code&gt;/api/customers&lt;/code&gt; &lt;/td&gt;&lt;td&gt;List customers&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;GET &lt;/td&gt;&lt;td&gt;&lt;code&gt;/api/customers/{id}&lt;/code&gt; &lt;/td&gt;&lt;td&gt;Retrieve one customer&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;POST &lt;/td&gt;&lt;td&gt;&lt;code&gt;/api/customers&lt;/code&gt; &lt;/td&gt;&lt;td&gt;Create a customer&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;PATCH &lt;/td&gt;&lt;td&gt;&lt;code&gt;/api/customers/{id}&lt;/code&gt; &lt;/td&gt;&lt;td&gt;Update selected fields&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;DELETE &lt;/td&gt;&lt;td&gt;&lt;code&gt;/api/customers/{id}&lt;/code&gt; &lt;/td&gt;&lt;td&gt;Delete a customer&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;These endpoints follow common REST conventions.&lt;/p&gt;
&lt;h4 id=&quot;listing-customers&quot;&gt;Listing customers&lt;/h4&gt;
&lt;p&gt;Create &lt;code&gt;routers/customers.py&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy import select
from sqlalchemy.orm import Session

from app.database import get_db
from app.models import Customer
from app.schemas import (
    CustomerCreate,
    CustomerResponse,
    CustomerUpdate,
)
router = APIRouter(
    prefix=&quot;/api/customers&quot;,
    tags=[&quot;Customers&quot;],
)
@router.get(&quot;/&quot;, response_model=list[CustomerResponse])
def list_customers(
    q: str | None = None,
    limit: int = Query(default=20, ge=1, le=100),
    offset: int = Query(default=0, ge=0),
    db: Session = Depends(get_db),
):
    statement = select(Customer)

    if q:
        statement = statement.where(
            Customer.full_name.ilike(f&quot;%{q}%&quot;)
            | Customer.email.ilike(f&quot;%{q}%&quot;)
        )

    statement = (
        statement
        .order_by(Customer.id.desc())
        .limit(limit)
        .offset(offset)
    )

    return db.scalars(statement).all()
This endpoint retrieves customers with pagination and a simple search option.

The `limit` and `offset` parameters prevent the backend from returning an unlimited number of records in a single request.

For larger datasets, consider full-text search or a dedicated search service. Also, escape SQL `LIKE` wildcard characters if the application needs literal substring-search behavior. SQLAlchemy parameterizes values, protecting against ordinary SQL injection, but wildcard characters still have search semantics.

A further improvement would return pagination metadata, including the total record count and the next page token or offset.

### Retrieving a single customer

```python
@router.get(&quot;/{customer_id}&quot;, response_model=CustomerResponse)
def get_customer(
    customer_id: int,
    db: Session = Depends(get_db),
):
    customer = db.get(Customer, customer_id)

    if customer is None:
        raise HTTPException(
            status_code=404,
            detail=&quot;Customer not found&quot;,
        )

    return customer
The endpoint returns a clear `404 Not Found` response when the requested customer does not exist.

### Creating a customer

```python
@router.post(
    &quot;/&quot;,
    response_model=CustomerResponse,
    status_code=201,
)
def create_customer(
    payload: CustomerCreate,
    db: Session = Depends(get_db),
):
    existing = db.scalar(
        select(Customer).where(
            Customer.email == str(payload.email)
        )
    )

    if existing:
        raise HTTPException(
            status_code=409,
            detail=&quot;A customer with this email already exists&quot;,
        )

    customer = Customer(
        **payload.model_dump()
    )

    db.add(customer)

    try:
        db.commit()
        db.refresh(customer)
    except Exception:
        db.rollback()
        raise

    return customer
The endpoint validates the incoming payload, checks for an existing email, saves the new customer, and returns the stored record.

The database's unique constraint remains essential because two simultaneous requests could pass the initial duplicate check. A robust production implementation should handle the resulting database integrity violation and return a consistent conflict response rather than exposing a raw database error.

Email normalization should also be considered. For example, an application may choose to trim surrounding whitespace and normalize email addresses consistently before storage.

### Updating customer information

```python
@router.patch(
    &quot;/{customer_id}&quot;,
    response_model=CustomerResponse,
)
def update_customer(
    customer_id: int,
    payload: CustomerUpdate,
    db: Session = Depends(get_db),
):
    customer = db.get(Customer, customer_id)

    if customer is None:
        raise HTTPException(
            status_code=404,
            detail=&quot;Customer not found&quot;,
        )

    changes = payload.model_dump(exclude_unset=True)

    if changes.get(&quot;full_name&quot;) is None:
        if &quot;full_name&quot; in changes:
            raise HTTPException(
                status_code=422,
                detail=&quot;Full name cannot be null&quot;,
            )

    if &quot;email&quot; in changes and changes[&quot;email&quot;] is not None:
        duplicate = db.scalar(
            select(Customer).where(
                Customer.email == str(changes[&quot;email&quot;]),
                Customer.id != customer_id,
            )
        )

        if duplicate:
            raise HTTPException(
                status_code=409,
                detail=&quot;Email is already assigned to another customer&quot;,
            )

        changes[&quot;email&quot;] = str(changes[&quot;email&quot;])

    for field, value in changes.items():
        setattr(customer, field, value)

    try:
        db.commit()
        db.refresh(customer)
    except Exception:
        db.rollback()
        raise

    return customer
This endpoint updates only the fields included in the request.

In a production codebase, the database integrity error handling should be more specific, and the backend should reject explicit null values for all required fields. We should also define whether an empty update request is valid.

### Deleting a customer

```python
@router.delete(&quot;/{customer_id}&quot;, status_code=204)
def delete_customer(
    customer_id: int,
    db: Session = Depends(get_db),
):
    customer = db.get(Customer, customer_id)

    if customer is None:
        raise HTTPException(
            status_code=404,
            detail=&quot;Customer not found&quot;,
        )

    db.delete(customer)
    db.commit()
    return None
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For business applications, permanent deletion is not always the best choice. Soft deletion or archival may be preferable when the business needs an audit trail or must preserve historical transactions.&lt;/p&gt;
&lt;p&gt;Deletion rules should also account for related records and applicable privacy requirements.&lt;/p&gt;
&lt;h3 id=&quot;9-registering-the-api-router&quot;&gt;9. Registering the API Router&lt;/h3&gt;
&lt;p&gt;Create &lt;code&gt;main.py&lt;/code&gt; to initialize the FastAPI application.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware

from app.routers.customers import router as customers_router

app = FastAPI(
    title=&quot;Customer Management API&quot;,
    version=&quot;1.0.0&quot;,
)

app.add_middleware(
    CORSMiddleware,
    allow_origins=[&quot;http://localhost:5173&quot;],
    allow_credentials=False,
    allow_methods=[&quot;GET&quot;, &quot;POST&quot;, &quot;PATCH&quot;, &quot;DELETE&quot;],
    allow_headers=[&quot;Content-Type&quot;, &quot;Authorization&quot;],
)

app.include_router(customers_router)

@app.get(&quot;/health&quot;)
def health_check():
    return {&quot;status&quot;: &quot;ok&quot;}
The CORS configuration allows the local React development server to communicate with the backend.

In production, configure the actual frontend origin rather than allowing every website to make cross-origin requests. CORS is a browser security mechanism, not a substitute for authentication or authorization.

Start the development server:

```bash
uvicorn app.main:app --reload
``
Visit `http://localhost:8000/docs` to explore FastAPI's interactive API documentation.

Before running the application, ensure the database schema has been created through migrations. The model definitions alone do not automatically create the tables in this setup.

## 10. Connecting React to the Backend

The frontend should communicate with FastAPI through a dedicated service module rather than scattering request logic across every component.

Create `src/services/customerService.js`.

```javascript
const API_BASE_URL =
  import.meta.env.VITE_API_BASE_URL ||
  &quot;http://localhost:8000&quot;;

export async function getCustomers(query = &quot;&quot;) {
  const params = new URLSearchParams({
    limit: &quot;20&quot;,
    offset: &quot;0&quot;,
  });

  if (query.trim()) {
    params.set(&quot;q&quot;, query.trim());
  }

  const response = await fetch(
    `${API_BASE_URL}/api/customers/?${params}`
  );

  if (!response.ok) {
    throw new Error(&quot;Failed to load customers&quot;);
  }

  return response.json();
}

export async function createCustomer(customer) {
  const response = await fetch(
    `${API_BASE_URL}/api/customers/`,
    {
      method: &quot;POST&quot;,
      headers: {
        &quot;Content-Type&quot;: &quot;application/json&quot;,
      },
      body: JSON.stringify(customer),
    }
  );

  if (!response.ok) {
    const error = await response.json();
    throw new Error(
      error.detail || &quot;Failed to create customer&quot;
    );
  }

  return response.json();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The service module centralizes API communication and error handling.&lt;/p&gt;
&lt;p&gt;For a larger application, add functions for retrieving a single customer, updating records, deleting customers, and handling authentication.&lt;/p&gt;
&lt;p&gt;The API base URL should be configurable through environment variables. Remember that frontend environment variables are generally included in the built application, so they must never contain secrets.&lt;/p&gt;
&lt;h3 id=&quot;11-building-the-customer-list-component&quot;&gt;11. Building the Customer List Component&lt;/h3&gt;
&lt;p&gt;React components turn API data into an interactive interface.&lt;/p&gt;
&lt;p&gt;Create a basic customer list in &lt;code&gt;src/components/CustomerList.jsx&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;import { useEffect, useState } from &quot;react&quot;;
import { getCustomers } from &quot;../services/customerService&quot;;

export default function CustomerList() {
  const [customers, setCustomers] = useState([]);
  const [query, setQuery] = useState(&quot;&quot;);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(&quot;&quot;);

  useEffect(() =&amp;gt; {
    let cancelled = false;

    async function loadCustomers() {
      setLoading(true);
      setError(&quot;&quot;);

      try {
        const data = await getCustomers(query);

        if (!cancelled) {
          setCustomers(data);
        }
      } catch {
        if (!cancelled) {
          setError(&quot;Unable to load customer records.&quot;);
        }
      } finally {
        if (!cancelled) {
          setLoading(false);
        }
      }
    }

    loadCustomers();

    return () =&amp;gt; {
      cancelled = true;
    };
  }, [query]);

  if (loading) {
    return &amp;lt;p&amp;gt;Loading customers...&amp;lt;/p&amp;gt;;
  }

  return (
    &amp;lt;section&amp;gt;
      &amp;lt;h1&amp;gt;Customers&amp;lt;/h1&amp;gt;

      &amp;lt;input
        value={query}
        onChange={(event) =&amp;gt; setQuery(event.target.value)}
        placeholder=&quot;Search customers&quot;
        aria-label=&quot;Search customers&quot;
      /&amp;gt;

      {error &amp;amp;&amp;amp; &amp;lt;p role=&quot;alert&quot;&amp;gt;{error}&amp;lt;/p&amp;gt;}

      {!error &amp;amp;&amp;amp; customers.length === 0 &amp;amp;&amp;amp; (
        &amp;lt;p&amp;gt;No customers found.&amp;lt;/p&amp;gt;
      )}

      &amp;lt;ul&amp;gt;
        {customers.map((customer) =&amp;gt; (
          &amp;lt;li key={customer.id}&amp;gt;
            &amp;lt;strong&amp;gt;{customer.full_name}&amp;lt;/strong&amp;gt;
            &amp;lt;span&amp;gt; — {customer.email}&amp;lt;/span&amp;gt;
            &amp;lt;span&amp;gt; ({customer.status})&amp;lt;/span&amp;gt;
          &amp;lt;/li&amp;gt;
        ))}
      &amp;lt;/ul&amp;gt;
    &amp;lt;/section&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This component demonstrates several essential React concepts.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;useState&lt;/code&gt; hook stores the customer list, search query, loading status, and error message. The &lt;code&gt;useEffect&lt;/code&gt; hook fetches data when the query changes. The &lt;code&gt;map&lt;/code&gt; method renders each customer using a stable key.&lt;/p&gt;
&lt;p&gt;The cancellation flag prevents a completed request from updating state after the effect has been cleaned up. In a larger application, &lt;code&gt;AbortController&lt;/code&gt; can cancel obsolete requests, especially when users type quickly into the search field.&lt;/p&gt;
&lt;p&gt;For production search, add a debounce delay to avoid sending a new request for every keystroke. You should also handle HTTP errors, network failures, and authentication expiration consistently.&lt;/p&gt;
&lt;h3 id=&quot;12-creating-a-customer-form&quot;&gt;12. Creating a Customer Form&lt;/h3&gt;
&lt;p&gt;A customer management application needs a form for creating and updating records.&lt;/p&gt;
&lt;p&gt;A simple form can use controlled React inputs, meaning React state holds the current field values.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;import { useState } from &quot;react&quot;;
import { createCustomer } from &quot;../services/customerService&quot;;

const initialForm = {
  full_name: &quot;&quot;,
  email: &quot;&quot;,
  phone: &quot;&quot;,
  company: &quot;&quot;,
  status: &quot;lead&quot;,
};

export default function CustomerForm({ onCreated }) {
  const [form, setForm] = useState(initialForm);
  const [error, setError] = useState(&quot;&quot;);
  const [saving, setSaving] = useState(false);

  function updateField(event) {
    const { name, value } = event.target;

    setForm((current) =&amp;gt; ({
      ...current,
      [name]: value,
    }));
  }

  async function handleSubmit(event) {
    event.preventDefault();
    setError(&quot;&quot;);
    setSaving(true);

    try {
      const customer = await createCustomer({
        ...form,
        phone: form.phone || null,
        company: form.company || null,
      });

      setForm(initialForm);
      onCreated?.(customer);
    } catch (err) {
      setError(err.message || &quot;Unable to save customer.&quot;);
    } finally {
      setSaving(false);
    }
  }

  return (
    &amp;lt;form onSubmit={handleSubmit}&amp;gt;
      &amp;lt;h2&amp;gt;Add Customer&amp;lt;/h2&amp;gt;

      &amp;lt;label&amp;gt;
        Full name
        &amp;lt;input
          name=&quot;full_name&quot;
          value={form.full_name}
          onChange={updateField}
          required
          minLength={2}
          maxLength={150}
        /&amp;gt;
      &amp;lt;/label&amp;gt;

      &amp;lt;label&amp;gt;
        Email
        &amp;lt;input
          type=&quot;email&quot;
          name=&quot;email&quot;
          value={form.email}
          onChange={updateField}
          required
        /&amp;gt;
      &amp;lt;/label&amp;gt;

      &amp;lt;label&amp;gt;
        Phone
        &amp;lt;input
          name=&quot;phone&quot;
          value={form.phone}
          onChange={updateField}
        /&amp;gt;
      &amp;lt;/label&amp;gt;

      &amp;lt;label&amp;gt;
        Company
        &amp;lt;input
          name=&quot;company&quot;
          value={form.company}
          onChange={updateField}
        /&amp;gt;
      &amp;lt;/label&amp;gt;

      &amp;lt;label&amp;gt;
        Status
        &amp;lt;select
          name=&quot;status&quot;
          value={form.status}
          onChange={updateField}
        &amp;gt;
          &amp;lt;option value=&quot;lead&quot;&amp;gt;Lead&amp;lt;/option&amp;gt;
          &amp;lt;option value=&quot;active&quot;&amp;gt;Active&amp;lt;/option&amp;gt;
          &amp;lt;option value=&quot;inactive&quot;&amp;gt;Inactive&amp;lt;/option&amp;gt;
        &amp;lt;/select&amp;gt;
      &amp;lt;/label&amp;gt;

      {error &amp;amp;&amp;amp; &amp;lt;p role=&quot;alert&quot;&amp;gt;{error}&amp;lt;/p&amp;gt;}

      &amp;lt;button type=&quot;submit&quot; disabled={saving}&amp;gt;
        {saving ? &quot;Saving...&quot; : &quot;Save Customer&quot;}
      &amp;lt;/button&amp;gt;
    &amp;lt;/form&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The form uses native HTML validation for a better user experience, but frontend validation is not sufficient on its own. FastAPI and Pydantic must continue to validate every incoming request.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;onCreated&lt;/code&gt; callback allows a parent component to refresh the customer list after a successful submission.&lt;/p&gt;
&lt;p&gt;For editing, reuse the form with initial values loaded from an existing customer. Make sure the application distinguishes between creating a new record and updating an existing one.&lt;/p&gt;
&lt;h3 id=&quot;13-organizing-the-dashboard&quot;&gt;13. Organizing the Dashboard&lt;/h3&gt;
&lt;p&gt;A useful customer management dashboard should make important information easy to understand.&lt;/p&gt;
&lt;p&gt;Consider organizing the interface into the following sections:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Navigation sidebar:&lt;/strong&gt; Links to Dashboard, Customers, Activities, Reports, and Settings.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Summary cards:&lt;/strong&gt; Display the total number of customers, active customers, new leads, and inactive records.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Customer table:&lt;/strong&gt; Show names, email addresses, companies, statuses, and available actions.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Search and filters:&lt;/strong&gt; Help users locate records quickly.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Customer detail page:&lt;/strong&gt; Display contact information and a history of interactions.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Create and edit dialogs:&lt;/strong&gt; Provide a focused interface for changing customer information.&lt;/p&gt;
&lt;p&gt;React supports this structure through reusable components. For example, a &lt;code&gt;StatCard&lt;/code&gt; component can display different metrics, while a &lt;code&gt;CustomerTable&lt;/code&gt; component handles the presentation of customer records.&lt;/p&gt;
&lt;p&gt;A consistent design system helps avoid duplicated styling and makes future improvements easier.&lt;/p&gt;
&lt;p&gt;Accessibility should be part of the interface from the beginning. Use meaningful labels, visible focus indicators, sufficient color contrast, keyboard-accessible controls, and clear validation messages. Do not rely on color alone to communicate a customer's status.&lt;/p&gt;
&lt;h3 id=&quot;14-implementing-search-filtering-and-pagination&quot;&gt;14. Implementing Search, Filtering, and Pagination&lt;/h3&gt;
&lt;p&gt;Search becomes increasingly important when a business manages hundreds or thousands of customers.&lt;/p&gt;
&lt;p&gt;The backend can accept query parameters such as:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;/api/customers/?q=alex&amp;amp;limit=20&amp;amp;offset=0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The search term can match a customer's name or email address.&lt;/p&gt;
&lt;p&gt;Filtering by status can be added through another parameter:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;/api/customers/?status=active&amp;amp;limit=20&amp;amp;offset=0
``
The backend should validate the status value and apply it to the database query.

Pagination allows the application to load records in manageable batches. Without pagination, a large response may increase database load, network transfer, and rendering time.
For a straightforward administrative application, limit-and-offset pagination is often sufficient. For very large or rapidly changing datasets, cursor-based pagination may offer more predictable performance.

Search and filtering should be performed on the backend when the dataset is large. Filtering only the currently loaded React array may incorrectly suggest that no matching records exist elsewhere in the database.

## 15. Adding Authentication and Authorization

A customer management system often contains personal information, so authentication and authorization must be designed carefully.

Authentication determines who the user is. Authorization determines what that user is allowed to do.

A practical production implementation should include:

- User registration or administrator-created accounts.
- Secure login and logout.
- Password hashing with a modern password-hashing algorithm.
- Access control for customer endpoints.
- Role-based permissions where required.
- Session expiration and revocation.
- Audit logging for sensitive changes.

FastAPI supports security dependencies and OAuth2-compatible authentication flows. Depending on the application, you may use short-lived access tokens, refresh-token rotation, or secure server-managed sessions.

If browser cookies are used for authentication, configure `HttpOnly`, `Secure`, and appropriate `SameSite` attributes, and implement appropriate CSRF protection for state-changing requests.

If bearer tokens are used, protect token storage and transmission. Avoid storing long-lived sensitive tokens in browser-accessible storage without carefully considering the risks.

Most importantly, every protected endpoint must enforce authorization on the server. Hiding a Delete button in React does not prevent an unauthorized user from calling the delete endpoint directly.

For a multi-company application, every customer query must also be scoped to the authenticated user's organization. Guessing another customer's numeric ID must never grant access to that record.

## 16. Securing Customer Data

Security should be treated as an ongoing engineering responsibility rather than a final checklist.

First, use HTTPS in production to protect data in transit.

Second, keep database credentials, signing keys, and other secrets outside source control.

Third, validate and normalize incoming data before storing it. Use parameterized database operations and avoid building SQL statements through string concatenation.

Fourth, apply the principle of least privilege. The application's database account should have only the permissions required for normal operations.

Fifth, limit the information returned by API endpoints. Internal notes, private account details, and administrative fields should not be exposed unless the requesting user has permission to access them.

Sixth, implement rate limiting and appropriate request-size limits to reduce abuse.

Seventh, maintain backups and periodically test the recovery process.

Finally, establish data retention and deletion policies that fit the business's legal and operational requirements. Logging should avoid exposing passwords, access tokens, or unnecessary personal information.

A secure application combines technical controls, appropriate operational procedures, and clear rules about who can access customer information.

## 17. Testing the Application

Testing helps ensure that customer records remain reliable as the code changes.

### Backend testing

Use `pytest` and FastAPI's `TestClient` to test API behavior.

Important test cases include:

- Creating a customer with valid data.
- Rejecting an invalid email address.
- Rejecting a duplicate email address.
- Returning a `404` response for a missing customer.
- Updating one field without changing others.
- Rejecting an invalid customer status.
- Preventing unauthorized access.
- Preventing users from accessing another organization's records.

Use a dedicated test database rather than running destructive tests against production data.

### Frontend testing

Use a React testing library to verify user interactions.

For example, test that the customer form displays validation feedback, disables the submit button while saving, and shows the newly created customer after a successful response.

Also test loading states, empty search results, server errors, and network failures.

End-to-end tests can verify the complete workflow, from logging in and creating a customer to editing the record and confirming that the change persists.

Automated tests should be integrated into a continuous integration pipeline so that regressions are detected before deployment.

## 18. Improving Performance and Scalability

An application that works well with twenty customers may struggle with fifty thousand. Performance decisions should be guided by measurements and expected usage.

Start with database indexes for common filters and sorting operations. Use query plans to identify expensive database operations instead of adding indexes without evidence.

Keep API responses focused. Return only the fields the frontend needs, and use pagination to control response sizes.

Avoid repeatedly fetching the same data when a safe caching strategy would work. However, cached customer information must respect authorization boundaries and must be invalidated when necessary.

React performance improvements may include component memoization, virtualization for very large lists, and reducing unnecessary state updates. These techniques should be introduced when profiling shows they are useful.

If the application needs to process large imports, send notifications, or generate complex reports, consider background workers and a task queue rather than making every HTTP request wait for a long-running job.

For multi-tenant systems, tenant isolation should be built into database access patterns and tested thoroughly.

As usage increases, monitor request latency, database connection utilization, error rates, and resource consumption. Good observability makes it easier to identify the actual cause of performance problems.

## 19. Deploying the Application

Deployment turns the development project into a service that real users can access.

A typical production environment includes:

- A hosting platform for the React frontend.
- A hosting platform or container environment for FastAPI.
- A managed PostgreSQL database.
- HTTPS and correctly configured DNS.
- Environment-based configuration.
- Automated backups and monitoring.

Build the React application for production:

```bash
npm run build
``
## 20. Future Features Worth Adding

Once the basic system is reliable, several features can make it more useful for real business operations.

**Customer activity timeline:** Record calls, emails, meetings, and important interactions.

**Notes and reminders:** Help staff remember follow-ups and customer requirements.

**Import and export:** Support controlled CSV imports and exports, including duplicate detection and validation reports.

**Advanced reporting:** Track new leads, customer retention, status changes, and team activity.

**Email integration:** Connect customer records to relevant email conversations, with appropriate permissions.

**Team collaboration:** Assign customers to staff members and track ownership changes.

**Audit trail:** Record who changed important fields and when.

**CRM integration:** Synchronize records with other business systems through documented APIs.

Avoid adding features simply because they sound impressive. Prioritize those that solve genuine problems for the intended users.

## 21. Common Mistakes to Avoid

Several mistakes can make a customer management system difficult to maintain.

**Mixing frontend and backend responsibilities:** React should not connect directly to the production database. Database access belongs in the backend.

**Ignoring validation:** Validate incoming data at the API boundary and enforce important constraints in the database.

**Returning every customer in one response:** Large datasets need pagination and carefully designed queries.

**Skipping authentication:** A public customer endpoint is inappropriate for private business information.

**Ignoring authorization:** A logged-in user should not automatically have permission to access every record.

**Handling errors inconsistently:** Use clear HTTP status codes and structured error responses.

**Hardcoding configuration:** Use environment-specific configuration instead of committing secrets or production URLs into application code.

**Skipping automated tests:** Basic tests can catch duplicate handling, data corruption, and permission regressions early.

**Overcomplicating the architecture:** Start with a clear modular design. Introduce additional services and infrastructure when the project's requirements justify them.

Avoiding these mistakes early will save time and reduce the cost of future maintenance.

For developers building a portfolio project, this application is also a valuable opportunity to demonstrate practical engineering skills. A well-documented repository, automated tests, a working deployment, and a clear explanation of architectural decisions can show potential employers or clients that you understand how to build more than a simple user interface.

With careful planning and iterative development, React and FastAPI can provide a strong foundation for a maintainable customer management application that grows alongside the business.
&lt;/code&gt;&lt;/pre&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/31007/how-to-build-a-customer-management-system-with-react-and-fastapi</guid>
<pubDate>Wed, 14 Oct 2026 03:47:00 +0000</pubDate>
</item>
<item>
<title>Building Your First Production-Ready API with Python: A Complete Guide for Full Stack Developers</title>
<link>https://coderlegion.com/30727/building-your-first-production-ready-api-with-python-complete-guide-full-stack-developers</link>
<description>&lt;p&gt;Application Programming Interfaces (APIs) are the backbone of modern software development. They allow different applications, services, and systems to communicate with each other. Whether you are building a web application, a mobile app, an e-commerce platform, or an AI-powered SaaS product, APIs play a critical role in delivering reliable and scalable functionality.&lt;/p&gt;
&lt;p&gt;Python has become one of the most popular programming languages for API development because of its clean syntax, extensive ecosystem, and powerful frameworks. Developers can use Python to build everything from simple REST APIs to complex microservices that support thousands of users.&lt;/p&gt;
&lt;p&gt;However, creating an API that works on your local computer is only the first step. A production-ready API must be secure, maintainable, well-documented, properly tested, observable, and capable of handling real-world traffic.&lt;/p&gt;
&lt;p&gt;In this article, we will explore how to build your first production-ready API with Python. We will use FastAPI as our primary framework and examine essential concepts such as project structure, request validation, database integration, authentication, error handling, automated testing, logging, performance, and deployment.&lt;/p&gt;
&lt;p&gt;The goal is not just to build an endpoint that returns data. It is to understand how professional developers design APIs that can be maintained and improved over time.&lt;/p&gt;
&lt;h3 id=&quot;1-understanding-what-makes-an-api-production-ready&quot;&gt;1. Understanding What Makes an API Production-Ready&lt;/h3&gt;
&lt;p&gt;Before writing code, it is important to understand the difference between a basic API and a production-ready API.&lt;/p&gt;
&lt;p&gt;A basic API might accept a request, perform an operation, and return a response. That is sufficient for learning, but production environments introduce additional challenges.&lt;/p&gt;
&lt;p&gt;For example, users may send invalid input, databases may become temporarily unavailable, attackers may attempt unauthorized access, and traffic may increase unexpectedly. Without proper safeguards, these situations can cause failures or expose sensitive information.&lt;/p&gt;
&lt;p&gt;A production-ready API should include several essential characteristics.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Reliability:&lt;/strong&gt; The API should handle expected failures gracefully and return consistent responses.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Security:&lt;/strong&gt; Sensitive resources must be protected through authentication, authorization, input validation, and secure configuration.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Maintainability:&lt;/strong&gt; The codebase should have a clear structure that makes it easy to understand, test, and extend.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Performance:&lt;/strong&gt; Database queries, network operations, and request processing should be efficient.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Observability:&lt;/strong&gt; Logs, metrics, and error reports should help developers understand what happens inside the application.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Documentation:&lt;/strong&gt; Developers should be able to understand endpoints, request formats, response schemas, and authentication requirements.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Testing:&lt;/strong&gt; Automated tests should verify that important functionality continues working after changes.&lt;/p&gt;
&lt;p&gt;These qualities should influence the design from the beginning rather than being added as an afterthought.&lt;/p&gt;
&lt;h3 id=&quot;2-choosing-python-and-fastapi&quot;&gt;2. Choosing Python and FastAPI&lt;/h3&gt;
&lt;p&gt;Python offers several frameworks for API development, including Flask, Django REST Framework, and FastAPI.&lt;/p&gt;
&lt;p&gt;Flask is lightweight and flexible, making it suitable for small applications and projects that need a minimal framework. Django REST Framework is particularly useful when building APIs around Django applications that require an integrated ORM, administrative interface, and established authentication features.&lt;/p&gt;
&lt;p&gt;FastAPI is an excellent choice for modern API projects because it supports asynchronous programming, automatic request validation, Python type hints, and automatically generated OpenAPI documentation.&lt;/p&gt;
&lt;p&gt;It also works well with Pydantic, which provides structured data validation and serialization.&lt;/p&gt;
&lt;p&gt;Consider a simple example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from fastapi import FastAPI

app = FastAPI()

@app.get(&quot;/&quot;)
def home():
    return {&quot;message&quot;: &quot;Welcome to my API&quot;}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When the application receives a GET request at the root endpoint, it returns a JSON response.&lt;/p&gt;
&lt;p&gt;Although this example is small, it demonstrates the basic structure of a FastAPI application. The framework handles routing and converts the returned Python dictionary into a JSON response.&lt;/p&gt;
&lt;p&gt;For a production project, we will expand this foundation with validation, persistence, testing, and security.&lt;/p&gt;
&lt;h3 id=&quot;3-setting-up-the-development-environment-1&quot;&gt;3. Setting Up the Development Environment&lt;/h3&gt;
&lt;p&gt;A clean development environment helps prevent dependency conflicts and makes a project easier to reproduce.&lt;/p&gt;
&lt;p&gt;First, install Python and create a directory for the project.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;mkdir production-api
cd production-api
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Create a virtual environment:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;python -m venv .venv
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Activate it on Windows:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;.venv\Scripts\activate
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;On macOS or Linux:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;source .venv/bin/activate
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Next, install the primary dependencies:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;pip install fastapi &quot;uvicorn[standard]&quot; sqlalchemy \
  psycopg[binary] pydantic-settings \
  pyjwt &quot;pwdlib[argon2]&quot; pytest httpx
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;These packages serve different purposes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;FastAPI provides API routing and request handling.&lt;/li&gt;
&lt;li&gt;Uvicorn runs the application as an ASGI server.&lt;/li&gt;
&lt;li&gt;SQLAlchemy provides database access and ORM functionality.&lt;/li&gt;
&lt;li&gt;Psycopg connects Python applications to PostgreSQL.&lt;/li&gt;
&lt;li&gt;Pydantic Settings manages configuration.&lt;/li&gt;
&lt;li&gt;PyJWT supports JSON Web Token operations.&lt;/li&gt;
&lt;li&gt;pwdlib with Argon2 supports secure password hashing.&lt;/li&gt;
&lt;li&gt;Pytest and HTTPX support automated API testing.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For a real project, record the exact tested dependency versions in a lockfile or another reproducible dependency-management workflow. Keep development tools separate from production dependencies where appropriate.&lt;/p&gt;
&lt;h3 id=&quot;4-designing-a-maintainable-project-structure&quot;&gt;4. Designing a Maintainable Project Structure&lt;/h3&gt;
&lt;p&gt;As an application grows, placing every endpoint, database operation, and validation rule inside one Python file becomes difficult to maintain.&lt;/p&gt;
&lt;p&gt;A modular structure separates responsibilities and improves readability.&lt;/p&gt;
&lt;p&gt;One possible project layout is:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;production-api/
├── app/
│   ├── __init__.py
│   ├── main.py
│   ├── config.py
│   ├── database.py
│   ├── models.py
│   ├── schemas.py
│   ├── security.py
│   └── routers/
│       ├── __init__.py
│       └── tasks.py
├── tests/
│   └── test_tasks.py
├── .env
├── .gitignore
└── requirements.txt
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Each module has a specific responsibility.&lt;/p&gt;
&lt;p&gt;The main application initializes FastAPI and registers routers. Configuration manages environment-specific settings. Database code creates database connections and sessions. Models describe database tables, while schemas define the structure of incoming and outgoing data.&lt;/p&gt;
&lt;p&gt;Routers group related endpoints, and tests verify application behavior.&lt;/p&gt;
&lt;p&gt;This organization makes it easier to add features without turning the codebase into a tightly connected collection of functions.&lt;/p&gt;
&lt;p&gt;The ideal structure depends on the size of the application. A small API does not need dozens of folders, but even a small project benefits from clear separation between business logic, persistence, and HTTP handling.&lt;/p&gt;
&lt;h3 id=&quot;5-building-the-main-application&quot;&gt;5. Building the Main Application&lt;/h3&gt;
&lt;p&gt;Create &lt;code&gt;app/main.py&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from fastapi import FastAPI
from app.routers import tasks

app = FastAPI(
    title=&quot;Production Tasks API&quot;,
    version=&quot;1.0.0&quot;,
    description=&quot;A practical task management API&quot;
)

app.include_router(tasks.router, prefix=&quot;/api/v1&quot;)

@app.get(&quot;/health/live&quot;, tags=[&quot;Health&quot;])
def liveness():
    return {&quot;status&quot;: &quot;alive&quot;}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This creates the application and registers a router under the &lt;code&gt;/api/v1&lt;/code&gt; prefix.&lt;/p&gt;
&lt;p&gt;Versioning endpoints helps developers evolve an API without unexpectedly breaking existing clients. A future version can introduce different response formats or behavior while the previous version remains available during migration.&lt;/p&gt;
&lt;p&gt;The liveness endpoint provides a simple indication that the application process can respond. It does not prove that PostgreSQL or every external dependency is available. A separate readiness check can verify whether the service is ready to receive traffic.&lt;/p&gt;
&lt;p&gt;FastAPI also generates interactive API documentation by default at &lt;code&gt;/docs&lt;/code&gt; and an OpenAPI schema at &lt;code&gt;/openapi.json&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;These features are particularly helpful when frontend developers, mobile developers, and third-party integrators need to understand how the API works.&lt;/p&gt;
&lt;h3 id=&quot;6-managing-configuration-safely&quot;&gt;6. Managing Configuration Safely&lt;/h3&gt;
&lt;p&gt;Production applications should not depend on hardcoded database passwords, secret keys, or environment-specific URLs.&lt;/p&gt;
&lt;p&gt;Instead, use environment variables or a dedicated secrets manager.&lt;/p&gt;
&lt;p&gt;Create &lt;code&gt;app/config.py&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from pydantic_settings import BaseSettings, SettingsConfigDict

class Settings(BaseSettings):
    database_url: str
    jwt_secret: str

    model_config = SettingsConfigDict(
        env_file=&quot;.env&quot;,
        extra=&quot;ignore&quot;
    )

settings = Settings()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A local &lt;code&gt;.env&lt;/code&gt; file might contain:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;DATABASE_URL=postgresql+psycopg://user:password@localhost/tasks
JWT_SECRET=replace-with-a-long-random-secret
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The example values are placeholders. Replace them with actual credentials for your development environment.&lt;/p&gt;
&lt;p&gt;Add &lt;code&gt;.env&lt;/code&gt; to &lt;code&gt;.gitignore&lt;/code&gt; so credentials are not accidentally committed to Git.&lt;/p&gt;
&lt;p&gt;In production, inject secrets through the deployment platform or a secrets manager instead of distributing a local &lt;code&gt;.env&lt;/code&gt; file. Use a strong, randomly generated signing key, rotate credentials when necessary, and never expose them in API responses or logs.&lt;/p&gt;
&lt;p&gt;Configuration validation also helps the application fail early when required settings are missing.&lt;/p&gt;
&lt;h3 id=&quot;7-connecting-to-postgresql&quot;&gt;7. Connecting to PostgreSQL&lt;/h3&gt;
&lt;p&gt;Many APIs need persistent storage. PostgreSQL is a strong choice because it supports relational data, transactions, indexes, and advanced querying capabilities.&lt;/p&gt;
&lt;p&gt;Create &lt;code&gt;app/database.py&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from sqlalchemy import create_engine
from sqlalchemy.orm import DeclarativeBase, sessionmaker

from app.config import settings

engine = create_engine(
    settings.database_url,
    pool_pre_ping=True
)

SessionLocal = sessionmaker(
    bind=engine,
    autoflush=False,
    expire_on_commit=False
)

class Base(DeclarativeBase):
    pass
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The engine manages connections to the database, while &lt;code&gt;SessionLocal&lt;/code&gt; creates database sessions.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;pool_pre_ping=True&lt;/code&gt; option checks pooled connections before using them, helping detect connections that have become invalid.&lt;/p&gt;
&lt;p&gt;Next, define a database model in &lt;code&gt;app/models.py&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from sqlalchemy import Boolean, String, Text
from sqlalchemy.orm import Mapped, mapped_column

from app.database import Base

class Task(Base):
    __tablename__ = &quot;tasks&quot;

    id: Mapped[int] = mapped_column(
        primary_key=True
    )
    title: Mapped[str] = mapped_column(
        String(200),
        index=True
    )
    description: Mapped[str | None] = mapped_column(
        Text,
        nullable=True
    )
    completed: Mapped[bool] = mapped_column(
        Boolean,
        default=False
    )
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The model defines a task with an identifier, title, optional description, and completion status.&lt;/p&gt;
&lt;p&gt;In a production system, database schema changes should be managed with migrations, such as Alembic, instead of relying on automatic table creation during application startup.&lt;/p&gt;
&lt;p&gt;Migrations provide a controlled way to introduce columns, indexes, constraints, and other structural changes without losing existing data.&lt;/p&gt;
&lt;h3 id=&quot;8-validating-incoming-and-outgoing-data&quot;&gt;8. Validating Incoming and Outgoing Data&lt;/h3&gt;
&lt;p&gt;Input validation is one of the most important responsibilities of an API.&lt;/p&gt;
&lt;p&gt;Without validation, clients may submit missing fields, excessively long strings, unexpected values, or data in the wrong format.&lt;/p&gt;
&lt;p&gt;Create &lt;code&gt;app/schemas.py&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from pydantic import BaseModel, ConfigDict, Field

class TaskCreate(BaseModel):
    title: str = Field(
        min_length=1,
        max_length=200
    )
    description: str | None = Field(
        default=None,
        max_length=2000
    )

class TaskUpdate(BaseModel):
    title: str | None = Field(
        default=None,
        min_length=1,
        max_length=200
    )
    description: str | None = Field(
        default=None,
        max_length=2000
    )
    completed: bool | None = None

class TaskRead(BaseModel):
    id: int
    title: str
    description: str | None
    completed: bool

    model_config = ConfigDict(
        from_attributes=True
    )
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;These schemas define the expected shape of task data.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;TaskCreate&lt;/code&gt; validates new tasks. &lt;code&gt;TaskUpdate&lt;/code&gt; supports partial updates, while &lt;code&gt;TaskRead&lt;/code&gt; defines the public representation returned by the API.&lt;/p&gt;
&lt;p&gt;Using separate schemas prevents clients from directly controlling internal database fields.&lt;/p&gt;
&lt;p&gt;For example, a client should not be able to set an administrative flag or assign a different user's identifier simply by including extra JSON properties in a request.&lt;/p&gt;
&lt;p&gt;Validation does not replace authorization, but it reduces the amount of unexpected data that reaches the application's business logic.&lt;/p&gt;
&lt;h3 id=&quot;9-implementing-crud-endpoints&quot;&gt;9. Implementing CRUD Endpoints&lt;/h3&gt;
&lt;p&gt;CRUD stands for Create, Read, Update, and Delete. These operations form the foundation of many business APIs.&lt;/p&gt;
&lt;p&gt;First, create a database-session dependency in &lt;code&gt;app/database.py&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from collections.abc import Generator

def get_db() -&amp;gt; Generator:
    db = SessionLocal()
    try:
        yield db
    finally:
        db.close()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The dependency creates a session for each request and closes it when the request finishes.&lt;/p&gt;
&lt;p&gt;Now create &lt;code&gt;app/routers/tasks.py&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from fastapi import APIRouter, Depends, HTTPException, Response
from sqlalchemy import select
from sqlalchemy.orm import Session

from app.database import get_db
from app.models import Task
from app.schemas import TaskCreate, TaskRead, TaskUpdate

router = APIRouter(
    prefix=&quot;/tasks&quot;,
    tags=[&quot;Tasks&quot;]
)

@router.post(&quot;/&quot;, response_model=TaskRead, status_code=201)
def create_task(
    payload: TaskCreate,
    db: Session = Depends(get_db)
):
    task = Task(**payload.model_dump())
    db.add(task)
    db.commit()
    db.refresh(task)
    return task

@router.get(&quot;/&quot;, response_model=list[TaskRead])
def list_tasks(
    db: Session = Depends(get_db)
):
    return db.scalars(
        select(Task).order_by(Task.id)
    ).all()

@router.get(&quot;/{task_id}&quot;, response_model=TaskRead)
def get_task(
    task_id: int,
    db: Session = Depends(get_db)
):
    task = db.get(Task, task_id)

    if task is None:
        raise HTTPException(
            status_code=404,
            detail=&quot;Task not found&quot;
        )

    return task

@router.patch(&quot;/{task_id}&quot;, response_model=TaskRead)
def update_task(
    task_id: int,
    payload: TaskUpdate,
    db: Session = Depends(get_db)
):
    task = db.get(Task, task_id)

    if task is None:
        raise HTTPException(
            status_code=404,
            detail=&quot;Task not found&quot;
        )

    changes = payload.model_dump(exclude_unset=True)

    for field, value in changes.items():
        setattr(task, field, value)

    db.commit()
    db.refresh(task)
    return task

@router.delete(&quot;/{task_id}&quot;, status_code=204)
def delete_task(
    task_id: int,
    db: Session = Depends(get_db)
):
    task = db.get(Task, task_id)

    if task is None:
        raise HTTPException(
            status_code=404,
            detail=&quot;Task not found&quot;
        )

    db.delete(task)
    db.commit()
    return Response(status_code=204)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This router provides a basic task management API.&lt;/p&gt;
&lt;p&gt;The POST endpoint creates a task. GET endpoints retrieve all tasks or a single task. PATCH updates selected fields, and DELETE removes a task.&lt;/p&gt;
&lt;p&gt;Returning appropriate HTTP status codes improves consistency. For example, &lt;code&gt;201 Created&lt;/code&gt; indicates successful resource creation, &lt;code&gt;404 Not Found&lt;/code&gt; indicates a missing resource, and &lt;code&gt;204 No Content&lt;/code&gt; indicates successful deletion without a response body.&lt;/p&gt;
&lt;p&gt;This implementation is a useful learning foundation, but it is not yet suitable for unrestricted public deployment. The list endpoint needs pagination, database errors need appropriate handling, and every task operation must enforce authorization once users are introduced.&lt;/p&gt;
&lt;h3 id=&quot;10-adding-authentication-and-authorization&quot;&gt;10. Adding Authentication and Authorization&lt;/h3&gt;
&lt;p&gt;Authentication answers the question, &quot;Who is making this request?&quot; Authorization answers, &quot;What is this user allowed to access?&quot;&lt;/p&gt;
&lt;p&gt;These are different responsibilities.&lt;/p&gt;
&lt;p&gt;An API that allows users to manage private tasks must ensure that one user cannot read, update, or delete another user's data.&lt;/p&gt;
&lt;p&gt;A common approach is to authenticate users with securely hashed passwords and issue access tokens after successful login.&lt;/p&gt;
&lt;p&gt;Password hashing should use a dedicated password-hashing algorithm such as Argon2. Passwords must never be stored in plain text or encrypted with a reversible method when hashing is appropriate.&lt;/p&gt;
&lt;p&gt;JSON Web Tokens (JWTs) are often used to represent authenticated sessions. A signed access token may contain a subject identifying the user and an expiration timestamp.&lt;/p&gt;
&lt;p&gt;However, a signed token is not automatically encrypted. Its contents should not include passwords, private secrets, or unnecessary sensitive information.&lt;/p&gt;
&lt;p&gt;A production authentication flow should generally include:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;User registration with strict input validation.&lt;/li&gt;
&lt;li&gt;Password hashing using an appropriate password-hashing algorithm.&lt;/li&gt;
&lt;li&gt;Login verification and controlled token issuance.&lt;/li&gt;
&lt;li&gt;Short-lived access tokens with validated expiration and signing algorithms.&lt;/li&gt;
&lt;li&gt;Authorization checks on protected resources.&lt;/li&gt;
&lt;li&gt;A secure token renewal or reauthentication strategy.&lt;/li&gt;
&lt;li&gt;Rate limiting and monitoring for repeated failed login attempts.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;When using OAuth2 or JWT bearer tokens with FastAPI, developers can use the framework's security utilities and validate tokens centrally.&lt;/p&gt;
&lt;p&gt;Most importantly, authentication must be connected to data ownership. Adding a login endpoint alone does not protect task records.&lt;/p&gt;
&lt;p&gt;For example, a task model might include an &lt;code&gt;owner_id&lt;/code&gt; foreign key. A database query for a task should then verify that both the task identifier and the authenticated user's identifier match.&lt;/p&gt;
&lt;p&gt;Conceptually:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;task = db.scalar(
    select(Task).where(
        Task.id == task_id,
        Task.owner_id == current_user.id
    )
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This query illustrates the ownership check; it assumes that the model contains &lt;code&gt;owner_id&lt;/code&gt; and that &lt;code&gt;current_user&lt;/code&gt; comes from a correctly implemented authentication dependency.&lt;/p&gt;
&lt;p&gt;For a private resource, returning a not-found response for records the user cannot access can also reduce unintended disclosure of resource existence.&lt;/p&gt;
&lt;h3 id=&quot;11-handling-errors-consistently&quot;&gt;11. Handling Errors Consistently&lt;/h3&gt;
&lt;p&gt;Errors are inevitable in production systems. A client may send invalid data, a requested record may not exist, or a database operation may fail.&lt;/p&gt;
&lt;p&gt;The API should return understandable errors without exposing internal implementation details.&lt;/p&gt;
&lt;p&gt;FastAPI's &lt;code&gt;HTTPException&lt;/code&gt; is useful for expected HTTP failures.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from fastapi import HTTPException

def require_task(task):
    if task is None:
        raise HTTPException(
            status_code=404,
            detail=&quot;Task not found&quot;
        )
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;FastAPI automatically converts this exception into an appropriate HTTP response.&lt;/p&gt;
&lt;p&gt;Validation errors are also handled by the framework. Developers should avoid catching every exception and returning a successful response because this hides failures and makes debugging difficult.&lt;/p&gt;
&lt;p&gt;Unexpected exceptions should be logged internally and translated into safe client-facing error responses. The response should not contain database connection strings, stack traces, secret keys, or sensitive user information.&lt;/p&gt;
&lt;p&gt;A consistent error format helps frontend applications display useful messages and handle failures predictably.&lt;/p&gt;
&lt;p&gt;For example, a team may adopt a standard structure containing an error code, a human-readable message, and an optional request identifier.&lt;/p&gt;
&lt;p&gt;The exact format matters less than applying it consistently across the application.&lt;/p&gt;
&lt;h3 id=&quot;12-improving-performance-with-pagination-and-indexing&quot;&gt;12. Improving Performance with Pagination and Indexing&lt;/h3&gt;
&lt;p&gt;An API that works well with ten records may struggle when a database contains millions of records.&lt;/p&gt;
&lt;p&gt;Returning every row from a table is rarely a good default for a public endpoint. It can consume memory, increase response times, and place unnecessary pressure on the database.&lt;/p&gt;
&lt;p&gt;Pagination allows clients to request a manageable subset of records.&lt;/p&gt;
&lt;p&gt;For example, a list endpoint might accept a page size and a cursor or offset. The API should enforce a maximum page size to prevent clients from requesting excessively large responses.&lt;/p&gt;
&lt;p&gt;Cursor-based pagination is often preferable for large or frequently changing datasets because it can provide more stable navigation when new records are added.&lt;/p&gt;
&lt;p&gt;Database indexes also improve performance when queries repeatedly filter or sort by particular fields. However, indexes require additional storage and can slow down writes, so they should be selected based on actual query patterns.&lt;/p&gt;
&lt;p&gt;Other performance improvements include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Selecting only the columns the application needs.&lt;/li&gt;
&lt;li&gt;Avoiding unnecessary database queries.&lt;/li&gt;
&lt;li&gt;Using appropriate connection-pool settings.&lt;/li&gt;
&lt;li&gt;Applying caching to suitable read-heavy endpoints.&lt;/li&gt;
&lt;li&gt;Moving long-running jobs to background workers.&lt;/li&gt;
&lt;li&gt;Profiling slow endpoints before attempting complex optimizations.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Asynchronous endpoints can improve concurrency for I/O-bound workloads, but they are not automatically faster in every situation. Blocking operations inside an asynchronous handler can undermine the expected benefits.&lt;/p&gt;
&lt;p&gt;Use asynchronous database drivers and compatible libraries when an asynchronous architecture is appropriate, and measure the result rather than relying on assumptions.&lt;/p&gt;
&lt;h3 id=&quot;13-testing-the-api-before-deployment&quot;&gt;13. Testing the API Before Deployment&lt;/h3&gt;
&lt;p&gt;Testing helps developers identify defects before users encounter them.&lt;/p&gt;
&lt;p&gt;A production-ready API should have tests for normal behavior, invalid input, missing resources, permissions, and important failure scenarios.&lt;/p&gt;
&lt;p&gt;Create &lt;code&gt;tests/test_tasks.py&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from fastapi.testclient import TestClient

from app.main import app

client = TestClient(app)

def test_liveness_endpoint():
    response = client.get(&quot;/health/live&quot;)

    assert response.status_code == 200
    assert response.json() == {&quot;status&quot;: &quot;alive&quot;}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Run the test with:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;pytest
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This test verifies that the application responds correctly to a liveness request.&lt;/p&gt;
&lt;p&gt;For the task endpoints, additional tests should verify that valid requests create records, invalid requests return validation errors, nonexistent tasks return 404, and updates change only the intended fields.&lt;/p&gt;
&lt;p&gt;Database tests should use an isolated test database or a controlled test fixture rather than modifying production data.&lt;/p&gt;
&lt;p&gt;Authentication tests should also confirm that anonymous users cannot access protected resources and that one user cannot access another user's records.&lt;/p&gt;
&lt;p&gt;Integration tests can verify interactions between FastAPI, PostgreSQL, and other services. End-to-end tests can validate complete workflows from the client's perspective.&lt;/p&gt;
&lt;p&gt;A useful testing strategy combines fast unit tests with integration tests that cover the boundaries where failures are most likely.&lt;/p&gt;
&lt;h3 id=&quot;14-logging-and-monitoring-in-production&quot;&gt;14. Logging and Monitoring in Production&lt;/h3&gt;
&lt;p&gt;Once an API is deployed, developers need visibility into its behavior.&lt;/p&gt;
&lt;p&gt;Logging provides a record of important events, while monitoring helps teams identify trends and emerging problems.&lt;/p&gt;
&lt;p&gt;Useful production metrics include request volume, latency, error rates, database connection usage, and resource consumption.&lt;/p&gt;
&lt;p&gt;Structured logging makes events easier to search and analyze. For example, a log entry may include the endpoint, HTTP status code, duration, and a request identifier.&lt;/p&gt;
&lt;p&gt;However, logs should not contain passwords, access tokens, sensitive request bodies, or unnecessary personal information.&lt;/p&gt;
&lt;p&gt;A request identifier can help developers trace an error across several services without exposing user credentials.&lt;/p&gt;
&lt;p&gt;Monitoring alerts should focus on meaningful conditions, such as a sustained increase in server errors, a sharp rise in response times, or database connection exhaustion.&lt;/p&gt;
&lt;p&gt;Health checks should be designed carefully. A liveness check should determine whether the process is functioning, while a readiness check can indicate whether the service can accept traffic based on critical dependencies.&lt;/p&gt;
&lt;p&gt;Logging and monitoring are not simply operational extras. They help teams discover issues before those issues affect a large number of users.&lt;/p&gt;
&lt;h3 id=&quot;15-protecting-the-api-against-common-security-risks&quot;&gt;15. Protecting the API Against Common Security Risks&lt;/h3&gt;
&lt;p&gt;Security needs to be considered throughout the development process.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Input validation:&lt;/strong&gt; Validate request bodies, query parameters, path parameters, and file uploads. Set reasonable limits on field lengths and payload sizes.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Authentication:&lt;/strong&gt; Require valid credentials for protected endpoints and validate token expiration, signatures, and expected claims.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Authorization:&lt;/strong&gt; Check permissions for every protected operation, including access to individual records.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Rate limiting:&lt;/strong&gt; Limit excessive requests, especially for login, password recovery, expensive searches, and other abuse-prone operations.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Transport security:&lt;/strong&gt; Use HTTPS in production to protect information transmitted between clients and the API.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;CORS configuration:&lt;/strong&gt; Allow only the browser origins that need access. CORS is not a substitute for authentication or authorization.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Secret management:&lt;/strong&gt; Keep credentials outside source control and restrict access to production secrets.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Dependency management:&lt;/strong&gt; Regularly update dependencies, review security advisories, and remove unnecessary packages.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Database security:&lt;/strong&gt; Use a dedicated database account with only the permissions required by the application.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Resource limits:&lt;/strong&gt; Restrict request sizes, page sizes, execution times, and other resource-intensive operations.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Safe error responses:&lt;/strong&gt; Avoid returning stack traces or internal system details to clients.&lt;/p&gt;
&lt;p&gt;Security testing should include attempts to access resources without permission and attempts to bypass validation rules.&lt;/p&gt;
&lt;p&gt;A secure API is not defined by the number of security libraries it uses. It is defined by whether its controls correctly protect data and enforce the application's rules.&lt;/p&gt;
&lt;h3 id=&quot;16-documenting-the-api-for-other-developers&quot;&gt;16. Documenting the API for Other Developers&lt;/h3&gt;
&lt;p&gt;Good documentation reduces confusion and speeds up integration.&lt;/p&gt;
&lt;p&gt;FastAPI generates interactive documentation based on route definitions, type hints, and Pydantic schemas.&lt;/p&gt;
&lt;p&gt;Developers can visit &lt;code&gt;/docs&lt;/code&gt; to inspect endpoints, review request and response formats, and try API calls.&lt;/p&gt;
&lt;p&gt;For a production API, documentation should also explain authentication requirements, pagination behavior, error codes, rate limits, and any important business rules.&lt;/p&gt;
&lt;p&gt;Endpoint descriptions should be clear enough that another developer can understand how to use the API without reading its internal implementation.&lt;/p&gt;
&lt;p&gt;When an API changes, its documentation and tests should be updated together.&lt;/p&gt;
&lt;p&gt;For teams maintaining multiple API versions, a documented deprecation policy helps clients migrate without unexpected interruptions.&lt;/p&gt;
&lt;p&gt;Clear documentation is part of the API's developer experience, not merely a feature of the framework.&lt;/p&gt;
&lt;h3 id=&quot;17-preparing-the-api-for-deployment&quot;&gt;17. Preparing the API for Deployment&lt;/h3&gt;
&lt;p&gt;After implementation and testing, the API needs a reliable deployment process.&lt;/p&gt;
&lt;p&gt;A typical deployment pipeline includes installing dependencies, running automated tests, checking configuration, applying database migrations, and deploying the application to a server or container platform.&lt;/p&gt;
&lt;p&gt;For a basic local run, use:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;uvicorn app.main:app --reload
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The reload option is intended for development because it automatically restarts the application when files change.&lt;/p&gt;
&lt;p&gt;For production, run the application using a suitable ASGI server configuration and a managed process or container platform. Configure worker processes according to the platform, available resources, and workload.&lt;/p&gt;
&lt;p&gt;A Docker image can package the application and its dependencies into a reproducible deployment unit. PostgreSQL should generally run as a separate service rather than being embedded in the API container.&lt;/p&gt;
&lt;p&gt;Production configuration should include the database connection string, signing keys, allowed origins, logging settings, and other environment-specific values.&lt;/p&gt;
&lt;p&gt;Database migrations should run through a controlled release process. The application should not unexpectedly change the database schema every time a worker starts.&lt;/p&gt;
&lt;p&gt;A mature deployment process also needs automated rollback or recovery procedures, database backups, and a strategy for handling incompatible schema changes.&lt;/p&gt;
&lt;p&gt;Deploying the code is only one part of releasing a reliable service. The surrounding operational process matters just as much.&lt;/p&gt;
&lt;h3 id=&quot;18-using-docker-for-consistent-environments&quot;&gt;18. Using Docker for Consistent Environments&lt;/h3&gt;
&lt;p&gt;Docker helps teams reduce differences between development, testing, and production environments.&lt;/p&gt;
&lt;p&gt;A simplified Dockerfile might look like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-dockerfile&quot;&gt;FROM python:3.12-slim

ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONUNBUFFERED=1

WORKDIR /app

COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

COPY app ./app

RUN useradd --create-home appuser
USER appuser

EXPOSE 8000

CMD [&quot;uvicorn&quot;, &quot;app.main:app&quot;, &quot;--host&quot;, &quot;0.0.0.0&quot;, &quot;--port&quot;, &quot;8000&quot;]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This example uses a slim Python image, installs dependencies, copies the application code, and runs the server as a non-root user.&lt;/p&gt;
&lt;p&gt;A complete production setup should pin and verify dependency versions, use a maintained base image, scan images for vulnerabilities, and configure appropriate resource limits.&lt;/p&gt;
&lt;p&gt;The application also needs valid environment variables and access to its database when the container starts.&lt;/p&gt;
&lt;p&gt;Do not include &lt;code&gt;.env&lt;/code&gt; files, passwords, or private keys in the Docker image. Inject sensitive configuration securely at runtime.&lt;/p&gt;
&lt;p&gt;For a real deployment, add a suitable readiness check and coordinate application startup with database availability. Container orchestration platforms can manage restarts and scaling, but they cannot compensate for incorrect application logic or unsafe database migrations.&lt;/p&gt;
&lt;h3 id=&quot;19-automating-quality-checks-with-ci-cd&quot;&gt;19. Automating Quality Checks with CI/CD&lt;/h3&gt;
&lt;p&gt;Continuous Integration and Continuous Deployment (CI/CD) help teams deliver changes more reliably.&lt;/p&gt;
&lt;p&gt;A CI pipeline can run automatically whenever a developer pushes code or opens a pull request.&lt;/p&gt;
&lt;p&gt;Typical pipeline steps include:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Installing pinned dependencies.&lt;/li&gt;
&lt;li&gt;Checking code formatting and style.&lt;/li&gt;
&lt;li&gt;Running static analysis and type checks.&lt;/li&gt;
&lt;li&gt;Running unit tests.&lt;/li&gt;
&lt;li&gt;Running integration tests against a test database.&lt;/li&gt;
&lt;li&gt;Scanning dependencies for known vulnerabilities.&lt;/li&gt;
&lt;li&gt;Building the deployment image.&lt;/li&gt;
&lt;li&gt;Publishing and deploying an approved release.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Automation catches problems earlier and reduces the risk of deploying code that has not been tested.&lt;/p&gt;
&lt;p&gt;Secrets should be stored in the CI/CD platform's protected secret-management facilities, not committed to the repository.&lt;/p&gt;
&lt;p&gt;Production deployments should also include approval controls where appropriate, health verification after release, and a rollback strategy if the new version fails.&lt;/p&gt;
&lt;p&gt;Even a small personal project can benefit from a simple pipeline that runs tests and checks before each release.&lt;/p&gt;
&lt;h3 id=&quot;20-common-mistakes-developers-should-avoid&quot;&gt;20. Common Mistakes Developers Should Avoid&lt;/h3&gt;
&lt;p&gt;Many API problems result from shortcuts that appear harmless during early development.&lt;/p&gt;
&lt;p&gt;One common mistake is putting all application logic in a single file. This makes it difficult to isolate responsibilities as the codebase grows.&lt;/p&gt;
&lt;p&gt;Another is returning every database record without pagination. Large responses can degrade both server and client performance.&lt;/p&gt;
&lt;p&gt;Developers also sometimes assume that hidden buttons or frontend checks provide security. They do not. Authorization must be enforced by the server.&lt;/p&gt;
&lt;p&gt;Hardcoding secrets is another dangerous practice because credentials can leak through repositories, logs, and deployment artifacts.&lt;/p&gt;
&lt;p&gt;Poor error handling creates additional problems. Returning raw exception messages may reveal sensitive implementation details, while silently ignoring exceptions makes failures difficult to diagnose.&lt;/p&gt;
&lt;p&gt;Skipping automated tests increases the chance that a small code change will break an existing feature.&lt;/p&gt;
&lt;p&gt;Finally, many developers optimize too early. Introducing complex caching, asynchronous patterns, or microservices before measuring actual bottlenecks can increase complexity without improving performance.&lt;/p&gt;
&lt;p&gt;The better approach is to establish clear requirements, build a maintainable foundation, test critical behavior, and use production measurements to guide improvements.&lt;/p&gt;
&lt;h3 id=&quot;21-a-practical-production-readiness-checklist&quot;&gt;21. A Practical Production-Readiness Checklist&lt;/h3&gt;
&lt;p&gt;Before releasing your Python API, review the following areas.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Application design&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Clear project structure and separation of responsibilities.&lt;/li&gt;
&lt;li&gt;Versioned endpoints and documented request/response schemas.&lt;/li&gt;
&lt;li&gt;Consistent HTTP status codes and error handling.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Database&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Controlled schema migrations.&lt;/li&gt;
&lt;li&gt;Proper indexes and efficient queries.&lt;/li&gt;
&lt;li&gt;Connection-pool configuration and backup procedures.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Security&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Authentication and resource-level authorization.&lt;/li&gt;
&lt;li&gt;Secure password hashing and token validation.&lt;/li&gt;
&lt;li&gt;HTTPS, safe CORS settings, rate limiting, and secret management.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Testing&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Unit and integration tests.&lt;/li&gt;
&lt;li&gt;Validation and error-handling tests.&lt;/li&gt;
&lt;li&gt;Permission and cross-user access tests.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Operations&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Structured logs and useful metrics.&lt;/li&gt;
&lt;li&gt;Liveness and readiness checks.&lt;/li&gt;
&lt;li&gt;Reproducible deployments and recovery procedures.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Maintenance&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Dependency updates and security reviews.&lt;/li&gt;
&lt;li&gt;Automated CI checks.&lt;/li&gt;
&lt;li&gt;Current documentation and a plan for API version changes.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Not every project requires a complex infrastructure from day one. The objective is to identify the risks that matter for the application's users and address them proportionately.&lt;/p&gt;
&lt;h3 id=&quot;conclusion-2&quot;&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;Building your first production-ready API with Python is an important milestone for any full stack developer. It teaches much more than defining routes and returning JSON responses. It requires thinking about data integrity, authentication, authorization, error handling, performance, testing, deployment, and long-term maintenance.&lt;/p&gt;
&lt;p&gt;FastAPI provides a powerful foundation for this work through type-driven validation, automatic documentation, and support for modern Python application patterns. Combined with PostgreSQL, SQLAlchemy, automated testing, and a secure deployment workflow, it can support applications ranging from small business tools to sophisticated backend services.&lt;/p&gt;
&lt;p&gt;The most important lesson is that production readiness is not a single feature or final development step. It is the result of many small engineering decisions made throughout the project.&lt;/p&gt;
&lt;p&gt;Start with a clear architecture, implement one feature at a time, validate inputs, protect user data, and test the behavior that matters most. Add monitoring and automation before real users depend on the service.&lt;/p&gt;
&lt;p&gt;Once you understand these fundamentals, you will be better prepared to build reliable APIs for web applications, mobile platforms, e-commerce systems, and AI-powered products.&lt;/p&gt;
&lt;p&gt;A successful API is not simply one that works when everything goes right. It is one that behaves predictably, protects its users, and remains maintainable when real-world conditions become complicated.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/30727/building-your-first-production-ready-api-with-python-complete-guide-full-stack-developers</guid>
<pubDate>Sun, 11 Oct 2026 17:37:55 +0000</pubDate>
</item>
<item>
<title>Building a High-Throughput Article-to-Markdown API for LLM Ingestion with FastAPI and Playwright</title>
<link>https://coderlegion.com/29821/building-high-throughput-article-markdown-api-for-llm-ingestion-with-fastapi-playwright</link>
<description>&lt;p&gt;Feeding raw HTML into LLM context windows is one of the most expensive and inefficient mistakes in modern AI engineering. &lt;/p&gt;
&lt;p&gt;A standard modern news or blog page easily spans 1.5MB to 4MB of raw DOM payload. When passed straight into an LLM or vector database, 90% of those tokens are spent on tracking scripts, serialized JSON-LD blobs, cookie banners, navigation menus, and inline CSS styles. This not only causes severe context bloat and escalates inference bills, but it also degrades retrieval-augmented generation (RAG) semantic search precision by polluting your vector space with boilerplate noise.&lt;/p&gt;
&lt;p&gt;Here is how to design and build an enterprise-grade, self-hosted extraction microservice using FastAPI, Trafilatura, Readability, and an asynchronous Playwright fallback for SPA rendering.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;1-the-bottleneck-commercial-apis-vs-fragile-scrapers&quot;&gt;1. The Bottleneck: Commercial APIs vs. Fragile Scrapers&lt;/h3&gt;
&lt;p&gt;Most teams start with simple libraries like &lt;code&gt;BeautifulSoup&lt;/code&gt; or &lt;code&gt;newspaper3k&lt;/code&gt;. These quickly break down:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Layout Brittleness&lt;/strong&gt;: Custom CSS selectors degrade when publishers redesign their DOM or randomize class names (e.g., CSS Modules/Tailwind compiles).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Client-Side Rendering (CSR)&lt;/strong&gt;: Single-page applications built on React, Next.js, or Vue return empty &lt;code&gt;&amp;lt;div id=&quot;root&quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;/code&gt; shells to standard HTTP clients.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Commercial SaaS Overkill&lt;/strong&gt;: Hosted extraction APIs charge upwards of $0.002 to $0.01 per page. If your pipeline indexes 200,000 URLs monthly, you are paying hundreds of dollars for what essentially amounts to an HTTP request and an AST traversal.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;To balance latency, compute cost, and reliability, the optimal architecture uses a &lt;strong&gt;tiered extraction waterfall&lt;/strong&gt;:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Tier 1 (Fast Path)&lt;/strong&gt;: High-speed async HTTP fetch + &lt;code&gt;trafilatura&lt;/code&gt;. Latency: ~150-300ms.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Tier 2 (Heuristic Fallback)&lt;/strong&gt;: If Trafilatura fails or yields empty strings, fall back to Mozilla's &lt;code&gt;readability-lxml&lt;/code&gt; algorithm.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Tier 3 (Headless Browser Fallback)&lt;/strong&gt;: If client-side hydration or dynamic script loading is detected, trigger an isolated headless Playwright worker to render the DOM before extraction.&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h3 id=&quot;2-system-architecture&quot;&gt;2. System Architecture&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;[Incoming Request: URL]
          │
          ▼
┌───────────────────┐
│ Redis Cache Check │ ──(Hit)──► Return Markdown &amp;amp; Metadata
└───────────────────┘
          │ (Miss)
          ▼
┌───────────────────┐
│ HTTPX Async Fetch │
└───────────────────┘
          │
    [Static HTML]
          │
          ▼
┌───────────────────┐
│    Trafilatura    │ ──(Success: Length &amp;gt; Threshold)──► Parse Meta &amp;amp; Return
└───────────────────┘
          │ (Failed / Empty)
          ▼
┌───────────────────┐
│ Readability-lxml  │ ──(Success: Length &amp;gt; Threshold)──► Parse Meta &amp;amp; Return
└───────────────────┘
          │ (Failed / SPA Detected)
          ▼
┌───────────────────┐
│ Playwright Worker │ ──(Render DOM)──► Re-extract via Trafilatura
└───────────────────┘
          │
          ▼
[Store in Cache &amp;amp; Return Markdown]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This setup allows 85% of standard web content to pass through the lightweight Python tier without launching Chromium, keeping memory usage stable and infrastructure costs near zero.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;3-implementation-code-amp-core-logic&quot;&gt;3. Implementation: Code &amp;amp; Core Logic&lt;/h3&gt;
&lt;p&gt;Below is the complete implementation of the dual-engine pipeline using FastAPI, Pydantic v2, and async execution.&lt;/p&gt;
&lt;h4 id=&quot;3-1-pydantic-validation-amp-schemas&quot;&gt;3.1 Pydantic Validation &amp;amp; Schemas&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from pydantic import BaseModel, HttpUrl, Field
from typing import Optional, Dict, Any
from datetime import datetime

class ExtractionRequest(BaseModel):
    url: HttpUrl
    force_playwright: bool = Field(default=False, description=&quot;Bypass fast path and force browser rendering&quot;)
    max_chars: Optional[int] = Field(default=None, description=&quot;Truncate body content for token limits&quot;)

class PageMetadata(BaseModel):
    title: Optional[str] = None
    author: Optional[str] = None
    published_date: Optional[str] = None
    site_name: Optional[str] = None
    reading_time_minutes: int = 0
    opengraph: Dict[str, Any] = {}

class ExtractionResponse(BaseModel):
    url: str
    markdown: str
    metadata: PageMetadata
    engine_used: str
    execution_time_ms: float
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;3-2-the-dual-engine-core-service&quot;&gt;3.2 The Dual-Engine Core Service&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import time
import httpx
import trafilatura
from readability import Document
from bs4 import BeautifulSoup
from markdownify import markdownify as md
from playwright.async_api import async_playwright

USER_AGENT = &quot;Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36&quot;

async def fetch_html_fast(url: str) -&amp;gt; str:
    async with httpx.AsyncClient(timeout=10.0, follow_redirects=True) as client:
        response = await client.get(url, headers={&quot;User-Agent&quot;: USER_AGENT})
        response.raise_for_status()
        return response.text

async def fetch_html_playwright(url: str) -&amp;gt; str:
    async with async_playwright() as p:
        browser = await p.chromium.launch(headless=True, args=[&quot;--no-sandbox&quot;, &quot;--disable-dev-shm-usage&quot;])
        context = await browser.new_context(user_agent=USER_AGENT)
        page = await context.new_page()
        await page.goto(url, wait_until=&quot;networkidle&quot;, timeout=20000)
        content = await page.content()
        await browser.close()
        return content

def extract_metadata(html: str) -&amp;gt; PageMetadata:
    soup = BeautifulSoup(html, &quot;lxml&quot;)
    og_data = {}
    for tag in soup.find_all(&quot;meta&quot;):
        prop = tag.get(&quot;property&quot;, tag.get(&quot;name&quot;, &quot;&quot;))
        if prop.startswith(&quot;og:&quot;) or prop.startswith(&quot;twitter:&quot;):
            og_data[prop] = tag.get(&quot;content&quot;, &quot;&quot;)

    title = og_data.get(&quot;og:title&quot;) or (soup.title.string if soup.title else None)
    author = og_data.get(&quot;article:author&quot;) or og_data.get(&quot;twitter:creator&quot;)
    pub_date = og_data.get(&quot;article:published_time&quot;)

    return PageMetadata(
        title=title,
        author=author,
        published_date=pub_date,
        site_name=og_data.get(&quot;og:site_name&quot;),
        opengraph=og_data
    )

def extract_content(html: str, url: str) -&amp;gt; tuple[str, str]:
    # Primary Engine: Trafilatura
    extracted = trafilatura.extract(
        html,
        url=url,
        output_format=&quot;markdown&quot;,
        include_links=True,
        include_images=False,
        favor_recall=False
    )
    if extracted and len(extracted.strip()) &amp;gt; 200:
        return extracted, &quot;trafilatura&quot;

    # Fallback Engine: Readability + Markdownify
    doc = Document(html)
    summary_html = doc.summary()
    markdown_output = md(summary_html, heading_style=&quot;ATX&quot;).strip()
    
    if len(markdown_output) &amp;gt; 100:
        return markdown_output, &quot;readability&quot;

    return &quot;&quot;, &quot;none&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;3-3-the-fastapi-microservice-route&quot;&gt;3.3 The FastAPI Microservice Route&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from fastapi import FastAPI, HTTPException, status

app = FastAPI(title=&quot;Article-to-Markdown Extraction API&quot;, version=&quot;1.0.0&quot;)

@app.post(&quot;/api/v1/extract&quot;, response_model=ExtractionResponse)
async def extract_article(payload: ExtractionRequest):
    start_time = time.perf_counter()
    url_str = str(payload.url)
    engine_used = &quot;trafilatura&quot;
    
    try:
        if payload.force_playwright:
            html = await fetch_html_playwright(url_str)
            markdown, engine_used = extract_content(html, url_str)
            engine_used = f&quot;playwright+{engine_used}&quot;
        else:
            # Tier 1 &amp;amp; 2: Fast HTTP path
            try:
                html = await fetch_html_fast(url_str)
                markdown, engine_used = extract_content(html, url_str)
            except Exception:
                markdown = &quot;&quot;
            
            # Tier 3: SPA / Fallback if empty
            if not markdown or len(markdown.strip()) &amp;lt; 150:
                html = await fetch_html_playwright(url_str)
                markdown, engine_used = extract_content(html, url_str)
                engine_used = f&quot;playwright_fallback+{engine_used}&quot;

        if not markdown:
            raise HTTPException(
                status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
                detail=&quot;Failed to extract meaningful content from the target URL.&quot;
            )

        metadata = extract_metadata(html)
        words = len(markdown.split())
        metadata.reading_time_minutes = max(1, round(words / 200))

        if payload.max_chars and len(markdown) &amp;gt; payload.max_chars:
            markdown = markdown[:payload.max_chars] + &quot;\n\n[Content Truncated]&quot;

        exec_duration = (time.perf_counter() - start_time) * 1000

        return ExtractionResponse(
            url=url_str,
            markdown=markdown,
            metadata=metadata,
            engine_used=engine_used,
            execution_time_ms=round(exec_duration, 2)
        )

    except HTTPException:
        raise
    except Exception as e:
        raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e))
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;4-deployment-caching-amp-concurrency-hardening&quot;&gt;4. Deployment, Caching &amp;amp; Concurrency Hardening&lt;/h3&gt;
&lt;p&gt;When exposing this service in production pipelines (e.g., connecting n8n webhooks or feeding continuous Celery queues), consider these three safeguards:&lt;/p&gt;
&lt;h4 id=&quot;browser-resource-constraints&quot;&gt;Browser Resource Constraints&lt;/h4&gt;
&lt;p&gt;Launching a new Chromium instance via &lt;code&gt;async_playwright&lt;/code&gt; on every request will instantly lead to Linux OOM crashes. In a production build:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Maintain a single long-lived Browser instance.&lt;/li&gt;
&lt;li&gt;Create and destroy lightweight &lt;code&gt;BrowserContext&lt;/code&gt; sessions per request.&lt;/li&gt;
&lt;li&gt;Enforce &lt;code&gt;--disable-dev-shm-usage&lt;/code&gt; and &lt;code&gt;--no-sandbox&lt;/code&gt; flags inside your Docker container.&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 id=&quot;redis-caching-layer&quot;&gt;Redis Caching Layer&lt;/h4&gt;
&lt;p&gt;Articles rarely mutate hourly. Adding an upstream Redis cache using a SHA-256 hash of the normalized canonical URL drastically drops response times to sub-10ms for repeated requests:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import hashlib

def generate_cache_key(url: str) -&amp;gt; str:
    normalized = url.split(&quot;?&quot;)[0].rstrip(&quot;/&quot;).lower()
    return f&quot;extract:{hashlib.sha256(normalized.encode()).hexdigest()}&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;docker-container-configuration&quot;&gt;Docker Container Configuration&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-dockerfile&quot;&gt;FROM python:3.11-slim

ENV PYTHONUNBUFFERED=1 \
    PIP_NO_CACHE_DIR=1 \
    PLAYWRIGHT_BROWSERS_PATH=/ms-playwright

WORKDIR /app
RUN apt-get update &amp;amp;&amp;amp; apt-get install -y --no-install-recommends \
    libxml2-dev libxslt-dev gcc libc-dev curl \
    &amp;amp;&amp;amp; rm -rf /var/lib/apt/lists/*

COPY requirements.txt .
RUN pip install -r requirements.txt
RUN playwright install --with-deps chromium

COPY . .
EXPOSE 8000
CMD [&quot;uvicorn&quot;, &quot;main:app&quot;, &quot;--host&quot;, &quot;0.0.0.0&quot;, &quot;--port&quot;, &quot;8000&quot;, &quot;--workers&quot;, &quot;2&quot;]
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;5-conclusion-amp-ready-to-use-workflow&quot;&gt;5. Conclusion &amp;amp; Ready-to-Use Workflow&lt;/h3&gt;
&lt;p&gt;You can manually wire this FastAPI microservice into your infrastructure following the architectural design and code above. &lt;/p&gt;
&lt;p&gt;If you want the complete, production-ready implementation out of the box—including:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Pre-configured Docker Compose with Redis cache and persistent volume configurations&lt;/li&gt;
&lt;li&gt;Playwright connection-pooling to avoid concurrency memory leaks&lt;/li&gt;
&lt;li&gt;Rate-limiting middleware (slowapi) and health check probes&lt;/li&gt;
&lt;li&gt;Complete test suites with synthetic fixtures and ready-to-import n8n orchestration workflows&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You can grab the complete source repository directly:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Instant Access on Whop:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://whop.com/mr-studio-7faa/article-to-markdown-extraction-api-fastapi-microservice&quot;&gt;Get the Microservice on Whop&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Direct Download on Gumroad:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://mannyverse767.gumroad.com/l/hlzptg&quot;&gt;Download on Gumroad&lt;/a&gt; — Use promo code &lt;strong&gt;&lt;code&gt;EARLYBIRD&lt;/code&gt;&lt;/strong&gt; at checkout for &lt;strong&gt;20% off&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;
</description>
<category>Tutorials</category>
<guid isPermaLink="true">https://coderlegion.com/29821/building-high-throughput-article-markdown-api-for-llm-ingestion-with-fastapi-playwright</guid>
<pubDate>Mon, 05 Oct 2026 08:36:31 +0000</pubDate>
</item>
<item>
<title>Building Smart Health API: A Production-Style REST API with CNN-Based Risk Prediction</title>
<link>https://coderlegion.com/29424/building-smart-health-api-a-production-style-rest-api-with-cnn-based-risk-prediction</link>
<description>&lt;p&gt;&lt;em&gt;By Priyadharshiny J — &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/priyadharshiny13&quot;&gt;GitHub: priyadharshiny13&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;h3 id=&quot;overview&quot;&gt;Overview&lt;/h3&gt;
&lt;p&gt;Smart Health API is a backend platform I built to explore how a real healthcare product might structure its API layer: role-based access for different user types, secure authentication, database migrations that don't break production, and a machine learning model wired into an async pipeline instead of blocking the request thread.&lt;/p&gt;
&lt;p&gt;The result is a FastAPI service that lets Patients, Doctors, and Admins interact with the same system through different permission levels, and that returns CNN-based oral cancer risk scores without making the caller wait on model inference.&lt;/p&gt;
&lt;h3 id=&quot;stack&quot;&gt;Stack&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;API layer:&lt;/strong&gt; FastAPI&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Database:&lt;/strong&gt; MySQL, with SQLAlchemy ORM&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Migrations:&lt;/strong&gt; Alembic&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Auth:&lt;/strong&gt; JWT-based authentication with Role-Based Access Control (RBAC)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ML:&lt;/strong&gt; TensorFlow CNN model for oral cancer risk prediction&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Async processing:&lt;/strong&gt; Celery + Redis&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Infra:&lt;/strong&gt; Docker, deployed toward AWS&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CI/CD:&lt;/strong&gt; GitHub Actions&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;architecture-decisions&quot;&gt;Architecture Decisions&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Why RBAC with three roles instead of a single user type?&lt;/strong&gt;&lt;br&gt;
Healthcare data access isn't uniform — a Patient should only see their own records, a Doctor needs read/write access to their assigned patients, and an Admin needs oversight without clinical permissions. I modeled this directly into the JWT claims and enforced it at the route-dependency level in FastAPI, rather than checking roles inside each endpoint's business logic. This keeps permission logic in one place and out of the individual handlers.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Why Alembic migrations instead of &lt;code&gt;create_all()&lt;/code&gt;?&lt;/strong&gt;&lt;br&gt;
&lt;code&gt;SQLAlchemy.create_all()&lt;/code&gt; is fine for a prototype, but it can't handle schema evolution — adding a column, renaming a table, backfilling data. Alembic gave the project versioned, reversible migrations, which is the same discipline a real production service needs the moment more than one person touches the schema.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Why Celery + Redis for the CNN inference?&lt;/strong&gt;&lt;br&gt;
Running a CNN prediction synchronously inside an HTTP request handler blocks the worker for however long inference takes, which doesn't scale under concurrent load. I moved prediction into a Celery task queue backed by Redis: the API accepts the image, enqueues the job, and returns a task ID immediately. The client polls (or would be pushed a webhook/notification in a fuller version) for the result. This separates the request/response cycle from the compute-heavy ML work.&lt;/p&gt;
&lt;h3 id=&quot;a-real-debugging-story&quot;&gt;A Real Debugging Story&lt;/h3&gt;
&lt;p&gt;Wiring up SQLAlchemy models across multiple modules (&lt;code&gt;User&lt;/code&gt;, &lt;code&gt;Patient&lt;/code&gt;, &lt;code&gt;Doctor&lt;/code&gt;, &lt;code&gt;Prediction&lt;/code&gt;) introduced circular import errors once relationships started referencing each other — &lt;code&gt;Patient&lt;/code&gt; needed &lt;code&gt;User&lt;/code&gt;, &lt;code&gt;User&lt;/code&gt; needed &lt;code&gt;Prediction&lt;/code&gt;, and Python's import system didn't like the cycle. The fix was restructuring model definitions to use string-based relationship references (&lt;code&gt;relationship(&quot;Patient&quot;, back_populates=...)&lt;/code&gt;) instead of direct class imports, which lets SQLAlchemy resolve the relationship lazily at mapper-configuration time rather than at import time.&lt;/p&gt;
&lt;p&gt;Small detail, but it's the kind of thing that only shows up once a schema has enough interrelated tables — and it's a good reminder that ORM relationship design needs the same intentionality as the schema itself.&lt;/p&gt;
&lt;h3 id=&quot;what-s-next&quot;&gt;What's Next&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Add automated tests for the RBAC permission boundaries (currently manually verified)&lt;/li&gt;
&lt;li&gt;Expose Celery task status through a WebSocket instead of polling&lt;/li&gt;
&lt;li&gt;Add rate limiting on the prediction endpoint given its compute cost&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;repo&quot;&gt;Repo&lt;/h3&gt;
&lt;p&gt;Code and setup instructions: &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/priyadharshiny13&quot;&gt;github.com/priyadharshiny13&lt;/a&gt; — see the &lt;code&gt;smart-health-api&lt;/code&gt; repository.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/29424/building-smart-health-api-a-production-style-rest-api-with-cnn-based-risk-prediction</guid>
<pubDate>Thu, 01 Oct 2026 15:25:23 +0000</pubDate>
</item>
<item>
<title>What If Full-Stack Features Were Installable?</title>
<link>https://coderlegion.com/29360/what-if-full-stack-features-were-installable</link>
<description>&lt;p&gt;&lt;em&gt;NextAPI, and an experiment in reusing whole application capabilities instead of code.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Every developer knows the first week of a new project. Before a single line of product code, you wire up authentication, decide how roles work, connect a database, configure Docker and build the shell your pages will live in. Then, once the foundation is in place, you start on the features, and discover you are rebuilding the same team workspace, profile page and admin area you built on the last project.&lt;/p&gt;
&lt;p&gt;Boilerplate solved the foundation. It never solved the features.&lt;/p&gt;
&lt;p&gt;NextAPI is my attempt to solve both. It began as a Next.js and FastAPI starter and has grown into something more ambitious: a framework where a full-stack feature (its pages, its API, its database model, its permissions and its navigation) can be installed, verified, versioned and rolled back like a package.&lt;/p&gt;
&lt;p&gt;This article walks through what that actually means, and why I think it matters.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;part-one-a-foundation-you-can-use-today&quot;&gt;Part One: A Foundation You Can Use Today&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;NextAPI Core&lt;/strong&gt; is a complete full-stack application, ready in minutes. It stands entirely on its own. You never have to touch the module system to get value from it.&lt;/p&gt;
&lt;p&gt;Out of the box, you get:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Capability &lt;/th&gt;&lt;th&gt;What it means in practice&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Encrypted authentication &lt;/td&gt;&lt;td&gt;Credentials-based auth through NextAuth v5. Login and registration payloads are AES-256-CBC encrypted on the client before a signed JWT is ever issued.&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Role-based access control &lt;/td&gt;&lt;td&gt;Admin, user and guest roles embedded in the JWT and enforced on both frontend and backend.&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Machine-to-machine API access&lt;/td&gt;&lt;td&gt;API client tokens (&lt;code&gt;X-API-Key&lt;/code&gt; / &lt;code&gt;X-API-Secret&lt;/code&gt;) kept separate from user sessions, so integrations never borrow a person's login.&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;A modern async backend&lt;/td&gt;&lt;td&gt;FastAPI with Pydantic v2 validation and SQLAlchemy 2.0.&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Two databases, one switch &lt;/td&gt;&lt;td&gt;SQLite by default with zero setup, or MongoDB with a single environment variable.&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Web and native mobile from one codebase &lt;/td&gt;&lt;td&gt;The same frontend ships as Android and iOS apps through Capacitor, authenticating against the same JWT endpoints.&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;A configurable app shell&lt;/td&gt;&lt;td&gt;Sidebar or top navbar, chosen once.&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Production-ready deployment &lt;/td&gt;&lt;td&gt;A Docker Compose stack with frontend, backend and an optional nginx reverse proxy with SSL.&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Even setup has been designed to remove the usual failure points. Running &lt;code&gt;npm run setup&lt;/code&gt; generates real secrets rather than placeholders, and writes the same encryption key to both the frontend and backend configuration. The two most common ways to break authentication by hand, a forgotten placeholder or keys that drift apart, simply cannot happen.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npm run setup
npm run dev
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Frontend on port 3000, backend on port 8000, and you are building your product.&lt;/p&gt;
&lt;p&gt;If you would rather start from a genuinely empty canvas, &lt;code&gt;npm run init:blank&lt;/code&gt; permanently removes the demo pages, so there is no dead code to carry into production.&lt;/p&gt;
&lt;p&gt;From here, the application is yours. Write your own routes, models and components. Install any npm or Python package. Change the architecture. NextAPI is not a hosted platform, and nothing about your application depends on anyone but you.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;part-two-the-problem-package-managers-never-solved&quot;&gt;Part Two: The Problem Package Managers Never Solved&lt;/h3&gt;
&lt;p&gt;The package ecosystem is extraordinary. Need a Python library? Install it. Need a React component? There are thousands.&lt;/p&gt;
&lt;p&gt;But what if you do not need a library? What if you need a &lt;strong&gt;feature&lt;/strong&gt;?&lt;/p&gt;
&lt;p&gt;Consider team management. It is not a component. It is a set of pages for teams, members and invitations; API endpoints behind each of them; database collections to store them; permissions governing who can do what; and navigation entries so users can find them.&lt;/p&gt;
&lt;p&gt;Those pieces describe one capability. Yet the way we reuse software today scatters them across separate packages, separate copy-pastes and, most often, separate rewrites. Every copy starts drifting the moment it lands.&lt;/p&gt;
&lt;p&gt;That led to the question at the heart of NextAPI:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;What if the unit of reuse were the feature rather than the library?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h3 id=&quot;part-three-a-feature-with-a-contract&quot;&gt;Part Three: A Feature With a Contract&lt;/h3&gt;
&lt;p&gt;In NextAPI, a feature is a &lt;strong&gt;module&lt;/strong&gt;, and every module is described by a single manifest. Here is a real one, lightly trimmed:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &quot;id&quot;: &quot;ai-notes&quot;,
  &quot;version&quot;: &quot;1.0.0&quot;,
  &quot;nextapiVersion&quot;: &quot;&amp;gt;=1.0.0 &amp;lt;2.0.0&quot;,
  &quot;requires&quot;: { &quot;modules&quot;: [&quot;profile-page@^1.0.0&quot;] },
  &quot;env&quot;: {
    &quot;backend&quot;: [{ &quot;key&quot;: &quot;ANTHROPIC_API_KEY&quot;, &quot;required&quot;: true, &quot;secret&quot;: true }]
  },
  &quot;dependencies&quot;: {
    &quot;frontend&quot;: { &quot;npm&quot;: { &quot;react-markdown&quot;: &quot;^9.0.0&quot; } },
    &quot;backend&quot;: { &quot;pypi&quot;: { &quot;anthropic&quot;: &quot;^0.40.0&quot; } }
  },
  &quot;frontend&quot;: {
    &quot;routes&quot;: [{ &quot;path&quot;: &quot;/notes&quot;, &quot;protected&quot;: true, &quot;label&quot;: &quot;Notes&quot; }]
  },
  &quot;backend&quot;: {
    &quot;router&quot;: { &quot;module&quot;: &quot;backend/router.py&quot;, &quot;prefix&quot;: &quot;/notes&quot; },
    &quot;collection&quot;: &quot;module_ai_notes&quot;
  },
  &quot;functions&quot;: {
    &quot;uses&quot;: [&quot;auth.get_current_user&quot;, &quot;profile.get_display_name&quot;]
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Read it closely, because this is where the idea becomes concrete. In one file, this module declares:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;the &lt;strong&gt;page&lt;/strong&gt; it adds, that the page requires sign-in, and the label it shows in navigation;&lt;/li&gt;
&lt;li&gt;the &lt;strong&gt;API router&lt;/strong&gt; it contributes and the URL prefix it answers on;&lt;/li&gt;
&lt;li&gt;the &lt;strong&gt;database collection&lt;/strong&gt; it owns;&lt;/li&gt;
&lt;li&gt;the &lt;strong&gt;npm and PyPI packages&lt;/strong&gt; it needs;&lt;/li&gt;
&lt;li&gt;the &lt;strong&gt;environment variables&lt;/strong&gt; it requires, and which are secrets;&lt;/li&gt;
&lt;li&gt;the &lt;strong&gt;other modules&lt;/strong&gt; it depends on, with version ranges;&lt;/li&gt;
&lt;li&gt;the &lt;strong&gt;specific functions&lt;/strong&gt; it calls from those modules;&lt;/li&gt;
&lt;li&gt;and the &lt;strong&gt;range of NextAPI versions&lt;/strong&gt; it is compatible with.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A conventional package gives you code. This manifest gives the framework an understanding of a capability: what it is, what it needs and how it fits into the application around it. Everything else NextAPI does is built on that understanding.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;part-four-two-commands-and-what-really-happens&quot;&gt;Part Four: Two Commands, and What Really Happens&lt;/h3&gt;
&lt;p&gt;Installing a module needs no account:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;nextapi add @alice/team-workspace
nextapi sync
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;add&lt;/code&gt; only records your intent in the project's lockfile. Nothing in your application changes.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;sync&lt;/code&gt; is where the real work happens, and its defining principle is simple: &lt;strong&gt;nothing is written until the whole install has been proven safe.&lt;/strong&gt; Before touching a single file, it:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;resolves dependency order and checks for circular dependencies;&lt;/li&gt;
&lt;li&gt;fetches any modules the new one depends on, automatically;&lt;/li&gt;
&lt;li&gt;verifies that every function a module calls is actually provided by the module it depends on;&lt;/li&gt;
&lt;li&gt;checks for route, backend package and database collection collisions.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;If any check fails, the install is refused up front. An install is never left half-applied.&lt;/p&gt;
&lt;p&gt;Only then does &lt;code&gt;sync&lt;/code&gt; wire the feature into your real codebase:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;your-app
├── app/team/page.tsx                                  new
├── backend/module_alice_team_workspace/router.py      new
├── backend/module_alice_team_workspace/models_mongo.py new
├── config/routes.ts                                   RBAC entry merged
└── .env                                               appended, your values untouched
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The page appears in your sidebar or navbar automatically. The RBAC rules are merged into your configuration. The backend lands as its own isolated package.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;part-five-the-hard-problems-solved-in-the-open&quot;&gt;Part Five: The Hard Problems, Solved in the Open&lt;/h3&gt;
&lt;p&gt;The idea is easy to state. What makes NextAPI worth taking seriously is how it handles the problems that make installable features genuinely difficult.&lt;/p&gt;
&lt;h4 id=&quot;it-joins-your-data-instead-of-duplicating-it&quot;&gt;It joins your data instead of duplicating it&lt;/h4&gt;
&lt;p&gt;Suppose your application already has users, and you install a user-management module that also declares a &lt;code&gt;users&lt;/code&gt; collection. A naive system creates a second, disconnected collection, and you now have two sources of truth about who can log in.&lt;/p&gt;
&lt;p&gt;NextAPI detects the overlap. If the module's fields are compatible with your existing collection, &lt;code&gt;sync&lt;/code&gt; offers to link the module to your real data instead:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;'@alice/user-management' declares a &quot;users&quot; collection compatible with your
existing UserCollection. Link it instead of creating a separate collection? (y/N)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It goes further than matching field names:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Method adoption.&lt;/strong&gt; If the module calls methods your collection class does not have yet, &lt;code&gt;sync&lt;/code&gt; adds the module's implementations to your model. It is purely additive and never overwrites a method your application already relies on.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Real-data checks.&lt;/strong&gt; A schema can match on paper and still not match reality. &lt;code&gt;sync&lt;/code&gt; samples your actual MongoDB documents, and if a field your model declares as required is missing from existing records, it asks before relaxing it:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;'users' collection: 'created_at' is declared required, but missing from
214/1,048 existing documents. Demote 'created_at' to optional in your
host's UserMongo model to match reality? (y/N)
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Remembered decisions.&lt;/strong&gt; Updates to a linked module re-link automatically. If a future version stops matching, it falls back safely and tells you exactly what happened.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;When fields do not match, the module installs against its own collection and you receive a precise, field-by-field report of what did not line up, which is something concrete for the module's author to fix.&lt;/p&gt;
&lt;p&gt;This is the difference between installing code into an empty project and integrating a feature into a living system.&lt;/p&gt;
&lt;h4 id=&quot;modules-depend-on-capabilities-not-just-packages&quot;&gt;Modules depend on capabilities, not just packages&lt;/h4&gt;
&lt;p&gt;Modules can declare the functions they &lt;strong&gt;provide&lt;/strong&gt; and the functions they &lt;strong&gt;use&lt;/strong&gt;. A support-chat module can call &lt;code&gt;profile.get_display_name&lt;/code&gt;, provided by a profile module, and the registry confirms that capability exists before either module installs.&lt;/p&gt;
&lt;p&gt;Package managers resolve graphs of libraries. NextAPI resolves a graph of application capabilities.&lt;/p&gt;
&lt;h4 id=&quot;names-never-collide&quot;&gt;Names never collide&lt;/h4&gt;
&lt;p&gt;Two authors can both build a module with a &lt;code&gt;/profile&lt;/code&gt; page and a &lt;code&gt;profile&lt;/code&gt; collection. At install time, NextAPI automatically namespaces pages, backend code and database storage per module. Authors can write as though their module is the only one that will ever exist.&lt;/p&gt;
&lt;h4 id=&quot;versioned-like-real-software&quot;&gt;Versioned like real software&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Versions follow semver and are &lt;strong&gt;immutable&lt;/strong&gt; once published.&lt;/li&gt;
&lt;li&gt;A bad release can be &lt;strong&gt;yanked&lt;/strong&gt; from new installs without breaking anyone already using it.&lt;/li&gt;
&lt;li&gt;Compatible version ranges are resolved to a single shared version automatically, and genuine conflicts are reported with both requirements named.&lt;/li&gt;
&lt;li&gt;Stateless modules can opt into running &lt;strong&gt;multiple major versions side by side&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Stored data carries its own &lt;code&gt;schemaVersion&lt;/code&gt;. Changing the shape of persisted documents requires publishing a &lt;strong&gt;normalizer&lt;/strong&gt; that upgrades older documents on read, so no previously stored record ever becomes unreadable.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;every-risky-change-is-deliberate-and-reversible&quot;&gt;Every risky change is deliberate and reversible&lt;/h4&gt;
&lt;p&gt;Some modules replace an existing page rather than adding one: a custom login screen, a redesigned landing page. That requires an explicit acknowledgement written into the manifest itself, so it cannot be scripted past a prompt. Before anything is replaced, the original is captured as a full snapshot, and &lt;code&gt;nextapi rollback /login&lt;/code&gt; restores it.&lt;/p&gt;
&lt;p&gt;Files you have edited by hand since the last sync are detected and left alone. &lt;code&gt;nextapi list&lt;/code&gt; flags drift, and &lt;code&gt;nextapi remove&lt;/code&gt; will not discard your changes without &lt;code&gt;--force&lt;/code&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;part-six-it-is-your-code&quot;&gt;Part Six: It Is Your Code&lt;/h3&gt;
&lt;p&gt;None of this turns your application into a black box.&lt;/p&gt;
&lt;p&gt;After &lt;code&gt;sync&lt;/code&gt;, a module's files are committed into your project as ordinary, readable source code, marked in clearly generated regions. You can edit them immediately. There is &lt;strong&gt;no runtime dependency on the registry&lt;/strong&gt;. If the registry disappeared tomorrow, your application would keep running exactly as it does today.&lt;/p&gt;
&lt;p&gt;That was a non-negotiable design goal. The registry distributes capabilities. It never owns them.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;part-seven-building-and-sharing-modules&quot;&gt;Part Seven: Building and Sharing Modules&lt;/h3&gt;
&lt;p&gt;The registry at &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.nextapi.app&quot;&gt;nextapi.app&lt;/a&gt; is where modules are published, discovered and installed. Building for it is designed to be as approachable as using it.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Build in the browser.&lt;/strong&gt; Start from a working template and edit in a full code editor that understands TypeScript, JavaScript, Python and JSON, with errors marked on the exact line.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Build with AI.&lt;/strong&gt; The AI Builder writes an entire module (manifest, backend, page and preview data) from a description. What sets it apart is discipline: it loads design standards before laying out a page, looks up a library's real documentation instead of guessing, lints its own output for accessibility issues such as contrast and reduced motion, and runs the same validation that gates every publish. Its output is identical in form to a hand-written module. The only difference is who typed it.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Publish with confidence.&lt;/strong&gt; &lt;code&gt;nextapi validate&lt;/code&gt; checks the schema, capabilities and file paths locally, and every publish is re-validated on the server. Each module must ship maintainer-facing implementation notes explaining what it assumes about the host application.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Share privately.&lt;/strong&gt; Publish a private module and share it with specific people by username.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Fork with credit.&lt;/strong&gt; Fork any public module to adapt it. The original author is recorded permanently and cannot be removed from the fork.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;part-eight-core-stands-on-its-own&quot;&gt;Part Eight: Core Stands on Its Own&lt;/h3&gt;
&lt;p&gt;This is worth stating plainly, because the registry can make the project appear more complex than it is.&lt;/p&gt;
&lt;p&gt;You can use NextAPI Core purely as a full-stack foundation and ignore the module ecosystem entirely. The registry is a layer on top, not a requirement underneath:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;                    Registry
                       |
             +---------+---------+
             |         |         |
          Module A  Module B  Module C
             |         |         |
             +---------+---------+
                       |
                  NextAPI Core
                       |
              +--------+--------+
              |                 |
           Next.js           FastAPI
              |                 |
              +--------+--------+
                       |
                    Database
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;NextAPI should be useful before the ecosystem exists. The registry should make it more powerful, never necessary.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;what-i-am-actually-building&quot;&gt;What I Am Actually Building&lt;/h3&gt;
&lt;p&gt;I am not building another package manager, and I have no interest in replacing npm or PyPI. They solve a different problem, and they solve it well.&lt;/p&gt;
&lt;p&gt;What interests me is moving one level higher. A library knows what functions it exports. A NextAPI module knows:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&quot;I add these pages, and they require sign-in.&quot;&lt;/li&gt;
&lt;li&gt;&quot;I expose these API routes.&quot;&lt;/li&gt;
&lt;li&gt;&quot;I own this data, and this is the version of its shape.&quot;&lt;/li&gt;
&lt;li&gt;&quot;I need these permissions, packages and secrets.&quot;&lt;/li&gt;
&lt;li&gt;&quot;I depend on these capabilities from other modules.&quot;&lt;/li&gt;
&lt;li&gt;&quot;I belong in the navigation, here.&quot;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Once a framework has that information, it can do far more than copy code into a project. It can reason about how a feature fits into an application: whether it is safe, whether it conflicts, whether it should join existing data, and how to undo it.&lt;/p&gt;
&lt;h3 id=&quot;where-things-stand&quot;&gt;Where Things Stand&lt;/h3&gt;
&lt;p&gt;NextAPI is open source under the MIT license and actively evolving. The foundation is solid, the module system already handles problems most tools never attempt, and plenty of hard work remains: richer migrations, broader host compatibility and a growing library of modules.&lt;/p&gt;
&lt;p&gt;The underlying idea is one I believe is worth pursuing:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;Instead of rebuilding the same full-stack features over and over, what if we could install the capability itself?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;That is what NextAPI has become. Not just a starter kit, and not just a registry, but an attempt to move reusable software one step closer to the application itself.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Try it:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Source code: &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/sup3rus3r/nextapi-core&quot;&gt;github.com/sup3rus3r/nextapi-core&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Module registry and docs: &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.nextapi.app&quot;&gt;nextapi.app&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Support the project: &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/sponsors/sup3rus3r&quot;&gt;GitHub Sponsors&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/29360/what-if-full-stack-features-were-installable</guid>
<pubDate>Thu, 01 Oct 2026 07:36:17 +0000</pubDate>
</item>
<item>
<title>Building a URL Shortener with FastAPI and SQLite — V1 Complete</title>
<link>https://coderlegion.com/28967/building-a-url-shortener-with-fastapi-and-sqlite-v1-complete</link>
<description>&lt;p&gt;After completing my Personal Expense Tracker, I started my fourth project: a &lt;strong&gt;URL Shortener&lt;/strong&gt;. This was also my &lt;strong&gt;first web backend project&lt;/strong&gt;, so the goal was to move from CLI applications into building a small web application and get more practical experience with &lt;strong&gt;FastAPI, SQLite, HTML, CSS, and Jinja2&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;The result is a simple URL Shortener that takes a long URL, generates a short code, stores the mapping in SQLite, and redirects users to the original URL.&lt;/p&gt;
&lt;h3 id=&quot;what-is-a-url-shortener-&quot;&gt;What Is a URL Shortener?&lt;/h3&gt;
&lt;p&gt;A URL shortener converts a long URL into a shorter link.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;https://example.com/some/very/long/path/to/a/page
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;can become something like:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;http://localhost:8000/aB72xK
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When someone opens the shortened URL, the application looks up the short code in the database and redirects them to the original URL.&lt;/p&gt;
&lt;p&gt;So the basic flow is:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Long URL
   ↓
Generate short code
   ↓
Store both in SQLite
   ↓
Return short URL
   ↓
User opens short URL
   ↓
Look up short code
   ↓
Redirect to original URL
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;project-architecture&quot;&gt;Project Architecture&lt;/h3&gt;
&lt;p&gt;I kept the architecture intentionally simple for V1.&lt;/p&gt;
&lt;p&gt;There are two main backend responsibilities:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;FastAPI application&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Handles the web routes, receives the submitted URL, generates the short code, displays the result, and performs the redirect.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;SQLite database layer&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Handles storing and retrieving the relationship between the short code and the original URL.&lt;/p&gt;
&lt;p&gt;The database table contains:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;urls
├── short_code
└── original_url
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;short_code&lt;/code&gt; is the primary key because each shortened URL needs its own unique identifier.&lt;/p&gt;
&lt;h3 id=&quot;moving-from-api-style-responses-to-a-web-interface&quot;&gt;Moving From API-Style Responses to a Web Interface&lt;/h3&gt;
&lt;p&gt;One of the interesting parts of this project was learning that FastAPI doesn't have to be used only for JSON APIs.&lt;/p&gt;
&lt;p&gt;For the first stage of learning FastAPI, I worked with API-style routes and responses.&lt;/p&gt;
&lt;p&gt;For this project, I wanted a simple user-facing interface instead.&lt;/p&gt;
&lt;p&gt;So I used:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;HTML&lt;/strong&gt; for the page structure&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CSS&lt;/strong&gt; for basic styling&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Jinja2&lt;/strong&gt; for server-side HTML rendering&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;FastAPI&lt;/strong&gt; as the backend&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The user submits a URL through the HTML form, FastAPI processes it, and Jinja2 renders the result back into the page.&lt;/p&gt;
&lt;p&gt;I intentionally did not add JavaScript for V1. The goal was to understand the traditional server-rendered web application flow first.&lt;/p&gt;
&lt;h3 id=&quot;generating-the-short-code&quot;&gt;Generating the Short Code&lt;/h3&gt;
&lt;p&gt;For the current version, the application generates a &lt;strong&gt;6-character short code&lt;/strong&gt; using letters and numbers.&lt;/p&gt;
&lt;p&gt;The generated code is then stored together with the original URL.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;short_code:   aB72xK
original_url: https://example.com/some/long/url
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When &lt;code&gt;/aB72xK&lt;/code&gt; is requested, the application searches for that short code and retrieves the original URL.&lt;/p&gt;
&lt;h3 id=&quot;redirecting-the-user&quot;&gt;Redirecting the User&lt;/h3&gt;
&lt;p&gt;The redirect is the part that makes the application actually function as a URL shortener.&lt;/p&gt;
&lt;p&gt;The application receives a request such as:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;/aB72xK
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It looks up &lt;code&gt;aB72xK&lt;/code&gt; in SQLite.&lt;/p&gt;
&lt;p&gt;If the code exists, FastAPI returns a redirect response to the stored original URL.&lt;/p&gt;
&lt;p&gt;If the code doesn't exist, the application returns a not-found response.&lt;/p&gt;
&lt;p&gt;This helped me understand that a shortened URL isn't just a smaller version of the original URL. It is an identifier that points to data stored by the application.&lt;/p&gt;
&lt;h3 id=&quot;keeping-v1-simple&quot;&gt;Keeping V1 Simple&lt;/h3&gt;
&lt;p&gt;One thing I wanted to maintain throughout this project was a limited scope.&lt;/p&gt;
&lt;p&gt;The purpose of V1 was to learn the fundamental workflow, not to build a production-scale URL-shortening service.&lt;/p&gt;
&lt;h4 id=&quot;current-v1-scope&quot;&gt;Current V1 Scope&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;URL shortening&lt;/li&gt;
&lt;li&gt;6-character short code generation&lt;/li&gt;
&lt;li&gt;SQLite storage&lt;/li&gt;
&lt;li&gt;URL redirection&lt;/li&gt;
&lt;li&gt;Simple HTML + CSS interface&lt;/li&gt;
&lt;li&gt;Jinja2 server-side rendering&lt;/li&gt;
&lt;li&gt;Empty-input handling&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That's where I decided to stop V1.&lt;/p&gt;
&lt;p&gt;There are many features that could be added later, such as URL expiration, analytics, custom short codes, URL management, authentication, and more.&lt;/p&gt;
&lt;p&gt;But adding all of them now would move the project away from its original learning goal.&lt;/p&gt;
&lt;h3 id=&quot;what-i-learned&quot;&gt;What I Learned&lt;/h3&gt;
&lt;p&gt;This project helped me connect several concepts that I had previously learned separately.&lt;/p&gt;
&lt;p&gt;I practiced:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Building a web application with FastAPI&lt;/li&gt;
&lt;li&gt;Connecting FastAPI with SQLite&lt;/li&gt;
&lt;li&gt;Designing a simple database structure&lt;/li&gt;
&lt;li&gt;Using a database class to separate database operations&lt;/li&gt;
&lt;li&gt;Generating unique identifiers&lt;/li&gt;
&lt;li&gt;Handling redirects&lt;/li&gt;
&lt;li&gt;Using Jinja2 templates&lt;/li&gt;
&lt;li&gt;Connecting HTML forms with backend routes&lt;/li&gt;
&lt;li&gt;Serving CSS through FastAPI&lt;/li&gt;
&lt;li&gt;Organizing a small web application&lt;/li&gt;
&lt;li&gt;Deploying the application as a web service&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;More importantly, I got a better understanding of how the &lt;strong&gt;frontend, backend, and database communicate with each other&lt;/strong&gt;.&lt;/p&gt;
&lt;h3 id=&quot;project-structure&quot;&gt;Project Structure&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;url-shortener/
├── main.py
├── database.py
├── requirements.txt
├── templates/
│   └── index.html
├── static/
│   └── style.css
├── .gitignore
├── LICENSE
└── README.md
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;what-s-next-&quot;&gt;What's Next?&lt;/h3&gt;
&lt;p&gt;V1 is now complete.&lt;/p&gt;
&lt;p&gt;I want to keep the project available as a small working example of a FastAPI web application rather than continuously adding features without a specific learning goal.&lt;/p&gt;
&lt;p&gt;Future versions could explore more advanced concepts when they become relevant to my learning path.&lt;/p&gt;
&lt;h3 id=&quot;final-thoughts&quot;&gt;Final Thoughts&lt;/h3&gt;
&lt;p&gt;This project was a useful step from building command-line applications toward building web applications.&lt;/p&gt;
&lt;p&gt;My previous projects helped me practice Python, OOP, CRUD operations, SQLite, and application structure. With the URL Shortener, I was able to bring those concepts together with FastAPI and a browser-based interface.&lt;/p&gt;
&lt;p&gt;It's a small application, but it gave me practical experience with the complete basic flow:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Browser → FastAPI → SQLite → FastAPI → Browser&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;And that's exactly what I wanted from Project #4.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Project #4 — URL Shortener: V1 Complete.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Live Demo: &lt;a rel=&quot;nofollow&quot; href=&quot;https://url-shortener-k5ed.onrender.com/&quot;&gt;https://url-shortener-k5ed.onrender.com/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Source Code: &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/xDK0d3r/url-shortener&quot;&gt;https://github.com/xDK0d3r/url-shortener&lt;/a&gt;&lt;/p&gt;
&lt;h4 id=&quot;continue-reading&quot;&gt;Continue Reading&lt;/h4&gt;
&lt;p&gt;I also wrote about this project on &lt;strong&gt;Dev.to&lt;/strong&gt;, where I shared more about my learning experience, what I learned while building my first web backend project, and how this project fits into my overall development journey.&lt;/p&gt;
&lt;p&gt;If you'd like to read that perspective, check it out here:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://dev.to/xdk0d3r/building-my-first-web-backend-project-a-url-shortener-with-fastapi-and-sqlite-327k&quot;&gt;Read the Dev.to article →&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/28967/building-a-url-shortener-with-fastapi-and-sqlite-v1-complete</guid>
<pubDate>Mon, 28 Sep 2026 16:49:18 +0000</pubDate>
</item>
<item>
<title>How I Built and Monetized a Currency Exchange Rate API with FastAPI, Deployed it on Render, and Published it on RapidAPI.</title>
<link>https://coderlegion.com/28516/built-monetized-currency-exchange-rate-with-fastapi-deployed-render-published-rapidapi</link>
<description>&lt;p&gt;&lt;strong&gt;Introduction:&lt;/strong&gt;&lt;br&gt;
In order to build passive income streams, I decided to publish APIs on RapidAPI. The idea is straightforward: build a useful API once, set up freemium pricing tiers, and earn recurring monthly income from subscribers.&lt;br&gt;
In this post we will see how I built a Currency Exchange Rate API using FastAPI and Python, deployed it on Render, and published it on RapidAPI. I'll walk through the entire process including a debugging issue that cost me a few hours and might save you the same frustration.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The Tech Stack&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;FastAPI — chose this over Flask and Express because it auto-generates interactive API documentation at /docs out of the box, which is extremely useful when setting up and testing endpoints before publishing&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Python — straightforward for API work, massive community support&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;ExchangeRate-API — free data source providing live rates for 160+ currencies, no scraping needed&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Render — free tier hosting with simple GitHub deployment&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;RapidAPI — marketplace where developers discover and subscribe to APIs&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;The Three Endpoints&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-http&quot;&gt;# 1. Get latest rates for any base currency
GET /latest?base=USD

# 2. Convert between two currencies
GET /convert?from=USD&amp;amp;to=INR&amp;amp;amount=100

# 3. List all supported currencies
GET /currencies
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here's the core conversion endpoint:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;@app.get(&quot;/convert&quot;)
async def convert_currency(
    from_currency: str = Query(alias=&quot;from&quot;),
    to_currency: str = Query(alias=&quot;to&quot;),
    amount: float = Query(default=1.0)
):
    from_currency = from_currency.upper()
    to_currency = to_currency.upper()

    async with httpx.AsyncClient() as client:
        response = await client.get(
            f&quot;{BASE_URL}/pair/{from_currency}/{to_currency}/{amount}&quot;
        )
        data = response.json()
        return {
            &quot;from&quot;: from_currency,
            &quot;to&quot;: to_currency,
            &quot;amount&quot;: amount,
            &quot;rate&quot;: data[&quot;conversion_rate&quot;],
            &quot;converted&quot;: data[&quot;conversion_result&quot;],
            &quot;date&quot;: data[&quot;time_last_update_utc&quot;]
        }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;The Debugging Problem I faced&lt;/strong&gt;&lt;br&gt;
I originally built the API using Frankfurter (api.frankfurter.app) as the data source — it's free, requires no API key, and is backed by the European Central Bank. Perfect on paper.&lt;br&gt;
The problem: when I tested with curl in the terminal, Frankfurter returned data perfectly. But when FastAPI made the same request using httpx, I kept getting this error:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{&quot;detail&quot;: &quot;Could not fetch rates for base currency: USD&quot;}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After some debugging I ran this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;python -c &quot;import urllib.request; print(urllib.request.urlopen('https://api.frankfurter.dev/latest').read())&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And got back:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot;&gt;urllib.error.HTTPError: HTTP Error 403: Forbidden
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Frankfurter was blocking Python requests because they didn't include a browser-like User-Agent header. The server was rejecting anything that looked like a script.&lt;br&gt;
I tried adding a User-Agent header to httpx but the issue persisted on my network. The clean fix was switching to ExchangeRate-API which has a proper free tier, returns consistent JSON, and works reliably with Python requests.&lt;br&gt;
If you hit a 403 from a data source that works fine in the browser but fails in Python — check your User-Agent header first, and if that doesn't work, switch to a more developer-friendly data source.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Deploying on Render&lt;/strong&gt;&lt;br&gt;
Render's free tier is enough to get started. The setup takes about 5 minutes:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Push your code to GitHub&lt;/li&gt;
&lt;li&gt;Connect the repo to Render → New Web Service&lt;/li&gt;
&lt;li&gt;Set the start command: uvicorn main:app --host 0.0.0.0 --port 8000&lt;/li&gt;
&lt;li&gt;Add your API key as an environment variable&lt;/li&gt;
&lt;li&gt;Deploy&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;One important note — store your API key as an environment variable on Render, not hardcoded in your code. Use python-dotenv locally and os.getenv() to read it:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import os
from dotenv import load_dotenv
load_dotenv()
EXCHANGE_RATE_API_KEY = os.getenv(&quot;EXCHANGE_RATE_API_KEY&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The free tier has one limitation — cold starts. After 15 minutes of inactivity Render spins down your service and the first request takes 30-60 seconds to wake up. Upgrade to the $7/month starter plan once you have paying subscribers — your first subscriber covers the cost.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Publishing on RapidAPI&lt;/strong&gt;&lt;br&gt;
Once your API is live on Render, publishing on RapidAPI takes about 20 minutes:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Go to rapidapi.com/provider → Add New API&lt;/li&gt;
&lt;li&gt;Add your Render URL as the Base URL&lt;/li&gt;
&lt;li&gt;Add each endpoint with its query parameters&lt;/li&gt;
&lt;li&gt;Set up freemium pricing tiers&lt;/li&gt;
&lt;li&gt;Write a README and submit for review&lt;br&gt;
RapidAPI review takes 24-48 hours. Once approved your API is discoverable by thousands of developers searching the marketplace.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;Closing&lt;/strong&gt;&lt;br&gt;
The full API is live on RapidAPI — &lt;a rel=&quot;nofollow&quot; href=&quot;https://rapidapi.com/dhritibaruah20/api/currency-exchange-rate-api9&quot;&gt;https://rapidapi.com/dhritibaruah20/api/currency-exchange-rate-api9&lt;/a&gt;. Free tier available with 100 requests/month.&lt;br&gt;
I'm also building more APIs as part of a broader strategy to generate passive income as a developer. &lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/28516/built-monetized-currency-exchange-rate-with-fastapi-deployed-render-published-rapidapi</guid>
<pubDate>Thu, 24 Sep 2026 16:03:18 +0000</pubDate>
</item>
<item>
<title>Project LifeOps (Part 4): Executive Word (.docx) Reporting Engine and Multi-Sheet Excel (.xlsx)</title>
<link>https://coderlegion.com/27643/project-lifeops-part-4-executive-word-docx-reporting-engine-and-multi-sheet-excel-xlsx</link>
<description>&lt;p&gt;In previous installments, we designed the &lt;a rel=&quot;nofollow&quot; href=&quot;https://datalaria.com/en/posts/app-lifeops_part1_arquitectura_backend/&quot;&gt;FastAPI backend and Supabase database architecture&lt;/a&gt;, built the &lt;a rel=&quot;nofollow&quot; href=&quot;https://datalaria.com/en/posts/app-lifeops_part2_frontend_dashboard/&quot;&gt;Glassmorphism React client&lt;/a&gt;, and engineered the &lt;a rel=&quot;nofollow&quot; href=&quot;https://datalaria.com/en/posts/app-lifeops_part3_modulos_kanban/&quot;&gt;interactive fitness, reading, cinema, and Kanban modules&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Yet in software engineering, one principle remains non-negotiable: &lt;strong&gt;data sovereignty&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Most subscription-based commercial apps commit the same calculated mistake: they allow users to enter data effortlessly for free, but when you want to export your records, you are met with obscure JSON dumps, truncated exports, or paywalls. If the company pivots or discontinues the service, your life telemetry is trapped in a closed silo.&lt;/p&gt;
&lt;p&gt;When designing &lt;strong&gt;LifeOps&lt;/strong&gt;, I established a golden rule: &lt;strong&gt;no recorded data would ever be held hostage&lt;/strong&gt;. At any given moment, the user must be able to generate a beautifully styled, executive &lt;strong&gt;Word document (&lt;code&gt;.docx&lt;/code&gt;)&lt;/strong&gt; ready for printing or client presentation, a multi-sheet audit workbook in &lt;strong&gt;Excel (&lt;code&gt;.xlsx&lt;/code&gt;)&lt;/strong&gt; with dedicated tabs, and raw &lt;strong&gt;CSV backups with UTF-8 BOM&lt;/strong&gt; ready for direct ingestion in PowerBI or spreadsheet tools without manual encoding fixes.&lt;/p&gt;
&lt;p&gt;Best of all: implemented under a &lt;strong&gt;Zero-Disk in-memory architecture&lt;/strong&gt; without saving temporary files to server disks. Let's see how it was engineered! 🚀&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;[!TIP]&lt;br&gt;
&lt;strong&gt;Test the live app&lt;/strong&gt;: You can generate and download your own reports in the production build of LifeOps at &lt;a rel=&quot;nofollow&quot; href=&quot;https://datalaria.com/apps/lifeops/&quot;&gt;https://datalaria.com/apps/lifeops/&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h4 id=&quot;-lifeops-series-roadmap&quot;&gt;🗺️ LifeOps Series Roadmap&lt;/h4&gt;
&lt;p&gt;To understand how every architectural layer fits together, this series spans 5 structured installments:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;🟢 &lt;strong&gt;Part 1&lt;/strong&gt;: &lt;a rel=&quot;nofollow&quot; href=&quot;https://datalaria.com/en/posts/app-lifeops_part1_arquitectura_backend/&quot;&gt;Personal Operating System Architecture and FastAPI + Supabase Backend&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;🟢 &lt;strong&gt;Part 2&lt;/strong&gt;: &lt;a rel=&quot;nofollow&quot; href=&quot;https://datalaria.com/en/posts/app-lifeops_part2_frontend_dashboard/&quot;&gt;React Frontend with Glassmorphism, 360° Dashboard, and Design System&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;🟢 &lt;strong&gt;Part 3&lt;/strong&gt;: &lt;a rel=&quot;nofollow&quot; href=&quot;https://datalaria.com/en/posts/app-lifeops_part3_modulos_kanban/&quot;&gt;Core Interactive Modules: Fitness, Library, Cinema, and Professional Kanban Board&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;🟢 &lt;strong&gt;Part 4 (This article)&lt;/strong&gt;: Executive Word (.docx) Reporting Engine and Multi-Sheet Excel (.xlsx) Export&lt;/li&gt;
&lt;li&gt;🟢 &lt;strong&gt;Part 5&lt;/strong&gt;: &lt;a rel=&quot;nofollow&quot; href=&quot;https://datalaria.com/en/posts/app-lifeops_part5_deploy_mobile_pwa/&quot;&gt;24/7 Zero-Cost Cloud Deployment ($0/month), Mobile UX, and PWA&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h4 id=&quot;1-zero-disk-architecture-in-memory-ram-generation-with-io-bytesio-&quot;&gt;1. Zero-Disk Architecture: In-Memory RAM Generation with &lt;code&gt;io.BytesIO&lt;/code&gt; 🧠⚡&lt;/h4&gt;
&lt;p&gt;Many online tutorials suggest a hazardous pattern for backend file downloads:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Write a temporary file to disk (e.g. &lt;code&gt;/tmp/report_123.docx&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Populate the document.&lt;/li&gt;
&lt;li&gt;Serve it over HTTP.&lt;/li&gt;
&lt;li&gt;Schedule a background cronjob or script to delete it later.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;In ephemeral cloud containers or serverless tiers (such as Render.com's free instances), &lt;strong&gt;this pattern is a recipe for failure&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Ephemeral storage is strictly constrained; multiple concurrent downloads will rapidly exhaust container disk space (&lt;em&gt;Out of Disk Space&lt;/em&gt; crash).&lt;/li&gt;
&lt;li&gt;If an unhandled exception occurs mid-execution, orphaned temporary files remain uncleaned.&lt;/li&gt;
&lt;li&gt;Parallel requests can suffer race conditions or filename collisions if collision prevention is not meticulously handled.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In LifeOps, we built a &lt;strong&gt;Zero-Disk&lt;/strong&gt; pipeline:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;┌─────────────────────────────────────────────────────────────┐
│ 1. Frontend Request: POST /api/v1/reports/generate          │
└──────────────────────────────┬──────────────────────────────┘
                               │
                               ▼
┌─────────────────────────────────────────────────────────────┐
│ 2. FastAPI: Optimized relational query in Supabase          │
│    activities(*, workouts, books, films) + tasks + projects │
└──────────────────────────────┬──────────────────────────────┘
                               │
                               ▼
┌─────────────────────────────────────────────────────────────┐
│ 3. In-Memory Engine: python-docx / openpyxl                 │
│    doc.save(buffer) where buffer = io.BytesIO()             │
│    buffer.seek(0)                                           │
└──────────────────────────────┬──────────────────────────────┘
                               │
                               ▼
┌─────────────────────────────────────────────────────────────┐
│ 4. Direct Streaming HTTP Response:                          │
│    Response(content=buffer.getvalue(), media_type=docx)     │
│    Header: Content-Disposition: attachment; filename=...    │
└─────────────────────────────────────────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The document is composed directly in memory RAM, buffered inside an &lt;code&gt;io.BytesIO&lt;/code&gt; object, streamed to the client browser, and automatically reclaimed by Python's garbage collector once the response cycle ends. &lt;strong&gt;Zero disk I/O, zero file collision hazards, and maximum throughput.&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;2-executive-word-docx-generation-with-python-docx-&quot;&gt;2. Executive Word (.docx) Generation with &lt;code&gt;python-docx&lt;/code&gt; 📄✨&lt;/h4&gt;
&lt;p&gt;For compiling Microsoft Word documents in Python, &lt;code&gt;python-docx&lt;/code&gt; is the industry standard. However, default documents often appear rudimentary, featuring generic fonts and tables lacking inner cell padding.&lt;/p&gt;
&lt;p&gt;To achieve an executive-grade aesthetic, I developed utility helpers that directly manipulate the underlying OpenXML elements (&lt;code&gt;docx.oxml&lt;/code&gt;):&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from docx import Document
from docx.shared import Inches, Pt, RGBColor
from docx.oxml import parse_xml
from docx.oxml.ns import nsdecls

# LifeOps Brand Palette
COLOR_PRIMARY_HEX = &quot;0B0F17&quot;    # Dark Obsidian
COLOR_EMERALD_HEX = &quot;10B981&quot;    # Emerald Accent
COLOR_BG_LIGHT_HEX = &quot;F8FAFC&quot;   # Soft Table Fill

def set_cell_background(cell, fill_hex: str):
    &quot;&quot;&quot;Applies a solid background color to a Word table cell.&quot;&quot;&quot;
    tcPr = cell._tc.get_or_add_tcPr()
    shd = parse_xml(f'&amp;lt;w:shd {nsdecls(&quot;w&quot;)} w:fill=&quot;{fill_hex}&quot;/&amp;gt;')
    tcPr.append(shd)

def set_cell_margins(cell, top=100, bottom=100, left=150, right=150):
    &quot;&quot;&quot;Sets inner cell padding (in twips, 20 twips = 1 pt).&quot;&quot;&quot;
    tcPr = cell._tc.get_or_add_tcPr()
    tcMar = parse_xml(
        f'&amp;lt;w:tcMar {nsdecls(&quot;w&quot;)}&amp;gt;'
        f'&amp;lt;w:top w:w=&quot;{top}&quot; w:type=&quot;dxa&quot;/&amp;gt;'
        f'&amp;lt;w:bottom w:w=&quot;{bottom}&quot; w:type=&quot;dxa&quot;/&amp;gt;'
        f'&amp;lt;w:left w:w=&quot;{left}&quot; w:type=&quot;dxa&quot;/&amp;gt;'
        f'&amp;lt;w:right w:w=&quot;{right}&quot; w:type=&quot;dxa&quot;/&amp;gt;'
        f'&amp;lt;/w:tcMar&amp;gt;'
    )
    tcPr.append(tcMar)
&lt;/code&gt;&lt;/pre&gt;
&lt;h5 id=&quot;2-1-available-report-templates&quot;&gt;2.1. Available Report Templates&lt;/h5&gt;
&lt;p&gt;The reporting service supports three purpose-built templates:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Monthly Integral Summary (&lt;code&gt;monthly_summary&lt;/code&gt;)&lt;/strong&gt;: The complete 360° dossier. Aggregates monthly fitness distance, reading progress, cinema log, and active project deliverables.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Fitness Performance Dossier (&lt;code&gt;sport_performance&lt;/code&gt;)&lt;/strong&gt;: Built for athletic analysis: total volume in kilometers, average paces, heart rate distributions, and Personal Best (PB) records.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Project Portfolio Status (&lt;code&gt;project_status&lt;/code&gt;)&lt;/strong&gt;: Engineered for professional management: deliverable statuses, overdue deadlines, and the complete chronological progress log extracted from the &lt;code&gt;JSONB&lt;/code&gt; comments column.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&quot;https://datalaria.com/en/posts/app-lifeops_part4_informes_word_excel/lifeops_word_templates_en.png&quot; alt=&quot;Executive Word (.docx) report templates catalog with selector and metric breakdown&quot;&gt;&lt;/p&gt;
&lt;h5 id=&quot;2-2-in-memory-assembly&quot;&gt;2.2. In-Memory Assembly&lt;/h5&gt;
&lt;p&gt;The entrypoint constructs the document and streams it directly to the buffer:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import io

def generate_docx_report(template_type: str, user_email: str, date_from, date_to, data: dict) -&amp;gt; io.BytesIO:
    doc = Document()

    # Uniform executive page margins (2 cm / 0.8 inches)
    for section in doc.sections:
        section.top_margin = Inches(0.8)
        section.bottom_margin = Inches(0.8)
        section.left_margin = Inches(0.8)
        section.right_margin = Inches(0.8)

    if template_type == &quot;sport_performance&quot;:
        _build_sport_report(doc, user_email, date_from, date_to, data)
    elif template_type == &quot;project_status&quot;:
        _build_project_report(doc, user_email, date_from, date_to, data)
    else:
        _build_monthly_integral_report(doc, user_email, date_from, date_to, data)

    buffer = io.BytesIO()
    doc.save(buffer)
    buffer.seek(0)
    return buffer
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h4 id=&quot;3-multi-sheet-excel-xlsx-workbooks-with-openpyxl-&quot;&gt;3. Multi-Sheet Excel (.xlsx) Workbooks with &lt;code&gt;openpyxl&lt;/code&gt; 📊📗&lt;/h4&gt;
&lt;p&gt;Word dossiers provide polished executive summaries, but for quantitative analysis, pivot tables, or PowerBI models, the premier format is &lt;strong&gt;Excel (&lt;code&gt;.xlsx&lt;/code&gt;)&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Using &lt;code&gt;openpyxl&lt;/code&gt;, we compile an audit workbook consolidating the user's entire cloud schema across &lt;strong&gt;5 formatted thematic worksheets&lt;/strong&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from openpyxl import Workbook
from openpyxl.styles import Font, PatternFill, Alignment, Border, Side
from openpyxl.utils import get_column_letter

def export_full_excel(user_id: str) -&amp;gt; io.BytesIO:
    wb = Workbook()
    wb.remove(wb.active)  # Drop default empty sheet

    # Corporate Styling
    header_fill = PatternFill(start_color=&quot;1E293B&quot;, end_color=&quot;1E293B&quot;, fill_type=&quot;solid&quot;)
    header_font = Font(name=&quot;Calibri&quot;, size=11, bold=True, color=&quot;FFFFFF&quot;)
    data_font = Font(name=&quot;Calibri&quot;, size=10)
    thin_border = Border(
        left=Side(style=&quot;thin&quot;, color=&quot;E2E8F0&quot;),
        right=Side(style=&quot;thin&quot;, color=&quot;E2E8F0&quot;),
        top=Side(style=&quot;thin&quot;, color=&quot;E2E8F0&quot;),
        bottom=Side(style=&quot;thin&quot;, color=&quot;E2E8F0&quot;),
    )

    sheets_config = [
        (&quot;🏃 Sport &amp;amp; Fitness&quot;, _fetch_sport_data),
        (&quot;📚 Book Library&quot;, _fetch_books_data),
        (&quot;🎬 Cinema &amp;amp; TV&quot;, _fetch_films_data),
        (&quot;📋 Tasks Kanban&quot;, _fetch_tasks_data),
        (&quot;💼 Project Portfolio&quot;, _fetch_projects_data),
    ]

    for title, fetcher in sheets_config:
        ws = wb.create_sheet(title=title)
        ws.views.sheetView[0].showGridLines = True  # Gridlines always visible

        data = fetcher(user_id)
        if data:
            headers = [h.replace(&quot;_&quot;, &quot; &quot;).upper() for h in data[0].keys()]
            ws.append(headers)

            # Style Header Row
            for cell in ws[1]:
                cell.fill = header_fill
                cell.font = header_font
                cell.alignment = Alignment(horizontal=&quot;center&quot;, vertical=&quot;center&quot;)
            ws.row_dimensions[1].height = 24

            # Populate Rows with Cell Borders
            for row_idx, item in enumerate(data, start=2):
                ws.append(list(item.values()))
                for col_idx in range(1, len(headers) + 1):
                    c = ws.cell(row=row_idx, column=col_idx)
                    c.font = data_font
                    c.border = thin_border

            # Dynamic auto-fitting column widths
            for col in ws.columns:
                max_len = max(len(str(cell.value or &quot;&quot;)) for cell in col)
                col_letter = get_column_letter(col[0].column)
                ws.column_dimensions[col_letter].width = max(max_len + 4, 12)

    buffer = io.BytesIO()
    wb.save(buffer)
    buffer.seek(0)
    return buffer
&lt;/code&gt;&lt;/pre&gt;
&lt;h5 id=&quot;professional-touches-&quot;&gt;Professional Touches:&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;showGridLines = True&lt;/code&gt;&lt;/strong&gt;: By default, Excel hides gridlines on worksheets featuring background styling. Forcing this flag ensures crisp readability.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Smart Auto-Fit Columns&lt;/strong&gt;: Automatically calculates the longest string in each column and adds a 4-character safety buffer, eliminating truncated headers or numerical &lt;code&gt;###&lt;/code&gt; overflow errors.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://datalaria.com/en/posts/app-lifeops_part4_informes_word_excel/lifeops_excel_master_en.png&quot; alt=&quot;Multi-sheet Master Excel export (.xlsx) consolidating all modules into thematic tabs&quot;&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;4-csv-backups-the-secret-of-utf-8-bom-ufeff-&quot;&gt;4. CSV Backups: The Secret of UTF-8 BOM (&lt;code&gt;\ufeff&lt;/code&gt;) 🛡️&lt;/h4&gt;
&lt;p&gt;For data engineers requiring raw tabular feeds for Python scripts or command-line pipelines, LifeOps exposes entity-level CSV downloads.&lt;/p&gt;
&lt;p&gt;However, a notorious challenge exists: &lt;strong&gt;Microsoft Excel on Windows mishandles standard UTF-8 CSVs&lt;/strong&gt;, converting accented characters and international letters (such as &lt;em&gt;&quot;Kilómetros&quot;&lt;/em&gt; or &lt;em&gt;&quot;Diseño&quot;&lt;/em&gt;) into garbled symbols (&lt;em&gt;mojibake&lt;/em&gt;), while frequently failing to split columns properly.&lt;/p&gt;
&lt;p&gt;The elegant technical solution is prepending the &lt;strong&gt;UTF-8 Byte Order Mark (BOM) (&lt;code&gt;\ufeff&lt;/code&gt;)&lt;/strong&gt; and utilizing semicolon (&lt;code&gt;;&lt;/code&gt;) delimiters:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import io
import csv

def export_entity_csv(entity: str, user_id: str) -&amp;gt; bytes:
    data = fetcher(user_id)
    output = io.StringIO()

    # 1. Prepend UTF-8 BOM so Excel opens accents natively
    output.write(&quot;\ufeff&quot;)

    if data:
        writer = csv.DictWriter(output, fieldnames=list(data[0].keys()), delimiter=&quot;;&quot;, quoting=csv.QUOTE_MINIMAL)
        writer.writeheader()
        for row in data:
            writer.writerow(row)

    return output.getvalue().encode(&quot;utf-8&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When users double-click the resulting file, &lt;strong&gt;Excel opens it perfectly without encoding dialogs&lt;/strong&gt;, preserving accented strings and clean column separation.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://datalaria.com/en/posts/app-lifeops_part4_informes_word_excel/lifeops_csv_export_en.png&quot; alt=&quot;CSV data export modules powered by UTF-8 BOM encoding and semicolon delimiter&quot;&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;5-server-hardening-anti-dos-rate-limiting-with-slowapi-&quot;&gt;5. Server Hardening: Anti-DoS Rate Limiting with &lt;code&gt;slowapi&lt;/code&gt; ⏱️&lt;/h4&gt;
&lt;p&gt;Generating &lt;code&gt;.docx&lt;/code&gt; documents and &lt;code&gt;.xlsx&lt;/code&gt; workbooks requires significant CPU cycles and transient memory allocation. An automated script or abusive client sending 50 parallel requests could easily overwhelm Render's free container memory limits.&lt;/p&gt;
&lt;p&gt;To protect uptime, we enforced &lt;strong&gt;IP and user-level rate limits&lt;/strong&gt; with &lt;code&gt;slowapi&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from slowapi import Limiter
from slowapi.util import get_remote_address

limiter = Limiter(key_func=get_remote_address)

# Strict rate limit for intensive Word (.docx) and Excel (.xlsx) compiles
@router.post(&quot;/generate&quot;)
@limiter.limit(&quot;10/minute&quot;)
def generate_report(request: Request, req: GenerateReportRequest, user = Depends(get_current_user)):
    ...

# Rate limit for lightweight CSV exports
@router.get(&quot;/export/csv&quot;)
@limiter.limit(&quot;20/minute&quot;)
def export_csv(request: Request, entity: str, user = Depends(get_current_user)):
    ...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If a client exceeds the threshold, FastAPI instantly responds with an &lt;strong&gt;HTTP 429 Too Many Requests&lt;/strong&gt;, shielding backend memory for other users.&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;6-frontend-center-reports-amp-export-ui-reportspage-jsx-&quot;&gt;6. Frontend Center: Reports &amp;amp; Export UI (&lt;code&gt;ReportsPage.jsx&lt;/code&gt;) 💻&lt;/h4&gt;
&lt;p&gt;In our React client, the &lt;code&gt;/reports&lt;/code&gt; route provides an intuitive command center:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Interactive Template Cards&lt;/strong&gt;: Clear previews explaining the target audience of each report.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Period Selectors&lt;/strong&gt;: Instant shortcuts for &lt;em&gt;Current Month&lt;/em&gt;, &lt;em&gt;Previous Month&lt;/em&gt;, or custom date ranges.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Native Streaming Downloads&lt;/strong&gt;: The browser handles streaming binary payloads via &lt;code&gt;Blob&lt;/code&gt; APIs.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://datalaria.com/en/posts/app-lifeops_part4_informes_word_excel/lifeops_reports_center_en.png&quot; alt=&quot;LifeOps Reports and Data Export Center with active account *Emails are not allowed*&quot;&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;const response = await fetch(`${API_BASE_URL}/api/v1/reports/generate`, {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': `Bearer ${token}`
  },
  body: JSON.stringify(requestPayload)
});

const blob = await response.blob();
const downloadUrl = window.URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = downloadUrl;
link.download = filename;
link.click();
window.URL.revokeObjectURL(downloadUrl);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://datalaria.com/en/posts/app-lifeops_part4_informes_word_excel/lifeops_reports_history_en.png&quot; alt=&quot;Generated reports history table tracking dates, item volumes, and document types&quot;&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;conclusion-amp-what-s-next-&quot;&gt;Conclusion &amp;amp; What's Next 🎯&lt;/h4&gt;
&lt;p&gt;With our reporting engine completed, LifeOps elevates from a web dashboard into an &lt;strong&gt;executive asset generator and portable data hub&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;We have our cloud architecture, our dark-mode interface, our daily tracking modules, and our data export engine. Only one final milestone remains to conclude the series: &lt;strong&gt;production cloud deployment and mobile engineering&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;In &lt;strong&gt;Part 5&lt;/strong&gt; (the final installment of this series), we will cover:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;24/7 Zero-Cost Cloud Deployment ($0/month)&lt;/strong&gt; on Render.com and Netlify, with proxy rewrites and automated SSL.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Ergonomic Mobile UX&lt;/strong&gt;: Sticky Bottom Navigation Bar, touch-friendly Off-Canvas Drawer, and floating modales using &lt;code&gt;React.createPortal&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Progressive Web App (PWA)&lt;/strong&gt;: Direct home-screen installation without app store friction.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h4 id=&quot;references-amp-useful-links-&quot;&gt;References &amp;amp; Useful Links 🔗&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;🚀 &lt;strong&gt;Production Application&lt;/strong&gt;: Try the reports download center at &lt;a rel=&quot;nofollow&quot; href=&quot;https://datalaria.com/apps/lifeops/&quot;&gt;datalaria.com/apps/lifeops&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;🌐 &lt;strong&gt;Production Swagger API&lt;/strong&gt;: Reporting and export endpoints at &lt;a rel=&quot;nofollow&quot; href=&quot;https://lifeops-api.onrender.com/docs&quot;&gt;lifeops-api.onrender.com/docs&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;📄 &lt;strong&gt;python-docx&lt;/strong&gt;: Official documentation at &lt;a rel=&quot;nofollow&quot; href=&quot;https://python-docx.readthedocs.io/&quot;&gt;python-docx.readthedocs.io&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;📊 &lt;strong&gt;openpyxl&lt;/strong&gt;: Excel spreadsheet manipulation guide at &lt;a rel=&quot;nofollow&quot; href=&quot;https://openpyxl.readthedocs.io/&quot;&gt;openpyxl.readthedocs.io&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;🛡️ &lt;strong&gt;SlowAPI&lt;/strong&gt;: Rate limiting for FastAPI and Starlette at &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/laurentS/slowapi&quot;&gt;github.com/laurentS/slowapi&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;See you in the final installment! Do you export your personal app data to Excel or Word? Share your workflow in the comments below. 👇&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/27643/project-lifeops-part-4-executive-word-docx-reporting-engine-and-multi-sheet-excel-xlsx</guid>
<pubDate>Fri, 18 Sep 2026 14:06:04 +0000</pubDate>
</item>
<item>
<title>Project LifeOps (Part 3): Core Interactive Modules: Fitness, Library, Cinema, and Professional Kanba</title>
<link>https://coderlegion.com/27227/project-lifeops-part-core-interactive-modules-fitness-library-cinema-professional-kanba</link>
<description>&lt;p&gt;In earlier installments, we engineered the foundational backbone of &lt;strong&gt;LifeOps&lt;/strong&gt;: in &lt;a rel=&quot;nofollow&quot; href=&quot;https://datalaria.com/en/posts/app-lifeops_part1_arquitectura_backend/&quot;&gt;Part 1&lt;/a&gt; we designed the polymorphic PostgreSQL schema and modular FastAPI backend, and in &lt;a rel=&quot;nofollow&quot; href=&quot;https://datalaria.com/en/posts/app-lifeops_part2_frontend_dashboard/&quot;&gt;Part 2&lt;/a&gt; we brought the &lt;em&gt;Glassmorphism Dark Mode&lt;/em&gt; design system and global 360° Dashboard to life.&lt;/p&gt;
&lt;p&gt;Now comes the most defining phase of development: &lt;strong&gt;the day-to-day functional modules&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;A personal operating system fails if it forces users into rigid input forms or scatters daily data across disconnected views. The challenge was articulating three completely disparate life areas under unified design and performance standards:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;🏃 &lt;strong&gt;Health &amp;amp; Physical Fitness&lt;/strong&gt;: Workout logging across sports with dynamic metrics (distance volume, calories, pace calculations, and Personal Bests).&lt;/li&gt;
&lt;li&gt;📚 &lt;strong&gt;Intellectual Culture &amp;amp; Media&lt;/strong&gt;: Book tracking with interactive reading progress bars and an entertainment catalog organized by streaming platform.&lt;/li&gt;
&lt;li&gt;💼 &lt;strong&gt;Professional Execution&lt;/strong&gt;: An agile 4-column Kanban board mapped to projects, equipped with a &lt;strong&gt;chronological progress log powered by PostgreSQL JSONB&lt;/strong&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;&lt;p&gt;[!TIP]&lt;br&gt;
&lt;strong&gt;Test the live app&lt;/strong&gt;: You can explore the production build of LifeOps directly at &lt;a rel=&quot;nofollow&quot; href=&quot;https://datalaria.com/apps/lifeops/&quot;&gt;https://datalaria.com/apps/lifeops/&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h4 id=&quot;-lifeops-series-roadmap-1&quot;&gt;🗺️ LifeOps Series Roadmap&lt;/h4&gt;
&lt;p&gt;To understand how every architectural layer fits together, this series spans 5 structured installments:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;🟢 &lt;strong&gt;Part 1&lt;/strong&gt;: &lt;a rel=&quot;nofollow&quot; href=&quot;https://datalaria.com/en/posts/app-lifeops_part1_arquitectura_backend/&quot;&gt;Personal Operating System Architecture and FastAPI + Supabase Backend&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;🟢 &lt;strong&gt;Part 2&lt;/strong&gt;: &lt;a rel=&quot;nofollow&quot; href=&quot;https://datalaria.com/en/posts/app-lifeops_part2_frontend_dashboard/&quot;&gt;React Frontend with Glassmorphism, 360° Dashboard, and Design System&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;🟢 &lt;strong&gt;Part 3 (This article)&lt;/strong&gt;: Core Interactive Modules: Fitness, Library, Cinema, and Professional Kanban Board&lt;/li&gt;
&lt;li&gt;⚪ &lt;strong&gt;Part 4&lt;/strong&gt;: &lt;a rel=&quot;nofollow&quot; href=&quot;https://datalaria.com/en/posts/app-lifeops_part4_informes_word_excel/&quot;&gt;In-Memory Word Dossiers (.docx) &amp;amp; Multi-Sheet Excel Engine (.xlsx)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;⚪ &lt;strong&gt;Part 5&lt;/strong&gt;: &lt;a rel=&quot;nofollow&quot; href=&quot;https://datalaria.com/en/posts/app-lifeops_part5_deploy_mobile_pwa/&quot;&gt;24/7 Zero-Cost Cloud Deployment ($0/month), Mobile UX, and PWA&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h4 id=&quot;1-the-ux-dilemma-dual-view-mode-inspiring-cards-vs-synthesized-table-&quot;&gt;1. The UX Dilemma: Dual View Mode (Inspiring Cards vs. Synthesized Table) 🎴📊&lt;/h4&gt;
&lt;p&gt;When building data-intensive dashboards, designers face a classic user experience tradeoff:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;The Grid / Cards View&lt;/strong&gt;: Visually engaging, spacious, and inspiring. It highlights book covers, movie posters, and workout chips at a glance.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The Data Table View&lt;/strong&gt;: Irreplaceable when auditing timestamps, comparing heart rates, or scanning through an entire month of logs without endless vertical scrolling.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Instead of forcing a single opinionated view, I built a &lt;strong&gt;persistent dual-mode toggle stored in &lt;code&gt;localStorage&lt;/code&gt;&lt;/strong&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;const [viewMode, setViewMode] = useState(() =&amp;gt; {
  return localStorage.getItem('lifeops_view_sport') || 'grid';
});

const handleViewChange = (mode) =&amp;gt; {
  setViewMode(mode);
  localStorage.setItem('lifeops_view_sport', mode);
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In the header of each module, two unobtrusive buttons with &lt;code&gt;LayoutGrid&lt;/code&gt; and &lt;code&gt;Table&lt;/code&gt; icons toggle the presentation mode instantly. Thanks to React and Vite's build architecture, view switches execute in sub-milliseconds without triggering redundant server roundtrips.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://datalaria.com/en/posts/app-lifeops_part3_modulos_kanban/lifeops_sport_table_en.png&quot; alt=&quot;Dual view mode in LifeOps: Synthesized data table with key metrics, personal bests, and instant sorting&quot;&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;2-fitness-amp-performance-module-sportmodule-jsx-&quot;&gt;2. Fitness &amp;amp; Performance Module (&lt;code&gt;SportModule.jsx&lt;/code&gt;) 🏃💨&lt;/h4&gt;
&lt;p&gt;The fitness module caters to multi-sport athletes practicing running, road cycling, gym strength sessions, and swimming.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://datalaria.com/en/posts/app-lifeops_part3_modulos_kanban/lifeops_sport_performance_en.png&quot; alt=&quot;Fitness and Performance Module featuring aggregate KPI summary widgets and detailed workout cards&quot;&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;┌────────────────────────────────────────────────────────────────────────┐
│  🏃 Sport &amp;amp; Fitness                    [ + Log Workout ]       [⊞|≡]   │
├─────────────────┬─────────────────┬─────────────────┬──────────────────┤
│ TOTAL DISTANCE  │ ACTIVE TIME     │ CALORIES        │ PERSONAL BESTS   │
│   142.5 km      │   18.4 hours    │   12,450 kcal   │    4 PBs 🏅      │
├─────────────────┴─────────────────┴─────────────────┴──────────────────┤
│                                                                        │
│ ┌───────────────────────────┐         ┌──────────────────────────────┐ │
│ │ 🏃 RUNNING         🏅 PB  │         │ 🚴 CYCLING                   │ │
│ │ Sunday Long Run           │         │ Mountain Pass Century Ride   │ │
│ │ 2026-09-10                │         │ 2026-09-08                   │ │
│ │ [📍 21.1 km] [⏱️ 1h 48m]  │         │ [📍 65.0 km] [⏱️ 2h 45m]     │ │
│ │ [🔥 1,420 kcal] [❤️ 152bpm]│        │ [🔥 1,890 kcal] [⛰️ +950m]    │ │
│ └───────────────────────────┘         └──────────────────────────────┘ │
└────────────────────────────────────────────────────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;
&lt;h5 id=&quot;2-1-aggregated-high-level-kpis&quot;&gt;2.1. Aggregated High-Level KPIs&lt;/h5&gt;
&lt;p&gt;Before rendering individual activity items, we compute cumulative totals using in-memory array reducers:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;const totalKm = workouts.reduce((sum, act) =&amp;gt; sum + (act.workout?.distance_km || 0), 0);
const totalMinutes = workouts.reduce((sum, act) =&amp;gt; sum + (act.duration_minutes || 0), 0);
const totalCalories = workouts.reduce((sum, act) =&amp;gt; sum + (act.workout?.calories || 0), 0);
const pbCount = workouts.filter((act) =&amp;gt; act.workout?.personal_best).length;
&lt;/code&gt;&lt;/pre&gt;
&lt;h5 id=&quot;2-2-segregated-fastapi-payload&quot;&gt;2.2. Segregated FastAPI Payload&lt;/h5&gt;
&lt;p&gt;When an athlete records an exercise, the client dispatches a structured contract that FastAPI parses into the master table &lt;code&gt;lifeops.activities&lt;/code&gt; and the child table &lt;code&gt;lifeops.workouts&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;const payload = {
  activity: {
    activity_type: 'sport',
    title: formData.title,
    date: formData.date,
    duration_minutes: parseInt(formData.duration_minutes),
    description: formData.notes,
  },
  workout: {
    workout_type: formData.workout_type, // 'running' | 'cycling' | 'gym'
    distance_km: parseFloat(formData.distance_km) || null,
    calories: parseInt(formData.calories) || null,
    avg_heart_rate: parseInt(formData.avg_heart_rate) || null,
    elevation_m: parseInt(formData.elevation_m) || null,
    personal_best: formData.personal_best,
    notes: formData.notes,
  },
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This ensures shared attributes (dates, general durations, notes) remain globally indexable for the 360° Dashboard, while biometric attributes (elevation gain, heart rate, personal bests) reside in their specialized child table with cascading foreign key guarantees.&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;3-culture-amp-media-tracking-books-amp-cinema-&quot;&gt;3. Culture &amp;amp; Media Tracking: Books &amp;amp; Cinema 📚🎬&lt;/h4&gt;
&lt;p&gt;For cultural tracking, the goal was leaving behind fragmented mobile notes in favor of an enriched visual library.&lt;/p&gt;
&lt;h5 id=&quot;3-1-interactive-reading-progress-booksmodule-jsx-&quot;&gt;3.1. Interactive Reading Progress (&lt;code&gt;BooksModule.jsx&lt;/code&gt;)&lt;/h5&gt;
&lt;p&gt;On each book card, we calculate page completion percentages in real time:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;const percentage = b.pages_total &amp;gt; 0 
  ? Math.min(100, Math.round((b.pages_read / b.pages_total) * 100)) 
  : 0;

&amp;lt;div className=&quot;book-progress-wrapper&quot;&amp;gt;
  &amp;lt;div className=&quot;progress-info&quot;&amp;gt;
    &amp;lt;span&amp;gt;{b.pages_read} / {b.pages_total} pages&amp;lt;/span&amp;gt;
    &amp;lt;span className=&quot;percentage-tag&quot;&amp;gt;{percentage}%&amp;lt;/span&amp;gt;
  &amp;lt;/div&amp;gt;
  &amp;lt;div className=&quot;progress-bar-bg&quot;&amp;gt;
    &amp;lt;div 
      className=&quot;progress-bar-fill&quot; 
      style={{ 
        width: `${percentage}%`,
        background: percentage === 100 ? 'var(--accent-emerald)' : 'var(--grad-primary)'
      }} 
    /&amp;gt;
  &amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A status filter organizes titles into &lt;code&gt;reading&lt;/code&gt;, &lt;code&gt;completed&lt;/code&gt;, and &lt;code&gt;wishlist&lt;/code&gt;, paired with an interactive 5-star rating system using &lt;code&gt;lucide-react&lt;/code&gt; icons.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://datalaria.com/en/posts/app-lifeops_part3_modulos_kanban/lifeops_books_progress_en.png&quot; alt=&quot;Reading Library module with dynamic progress tracking percentages and status badges&quot;&gt;&lt;/p&gt;
&lt;h5 id=&quot;3-2-streaming-media-catalog-filmsmodule-jsx-&quot;&gt;3.2. Streaming Media Catalog (&lt;code&gt;FilmsModule.jsx&lt;/code&gt;)&lt;/h5&gt;
&lt;p&gt;The cinema module tracks movies, TV series, and documentaries cataloged by streaming provider (&lt;em&gt;Netflix, HBO Max, Prime Video, Disney+, Cinema&lt;/em&gt;). Each entry logs release year, director, rating, and personal review notes, answering: &lt;em&gt;&quot;What were the best films I watched this past quarter?&quot;&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://datalaria.com/en/posts/app-lifeops_part3_modulos_kanban/lifeops_films_catalog_en.png&quot; alt=&quot;Cinema and TV series catalog organized by streaming provider with ratings&quot;&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;4-professional-execution-the-kanban-board-kanbanboard-jsx-&quot;&gt;4. Professional Execution: The Kanban Board (&lt;code&gt;KanbanBoard.jsx&lt;/code&gt;) 📋&lt;/h4&gt;
&lt;p&gt;For professional tasks, I implemented a full Kanban board featuring 4 columns reflecting agile task lifecycles:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;📝 &lt;strong&gt;To Do (&lt;code&gt;todo&lt;/code&gt;)&lt;/strong&gt;: Backlog tasks queued for upcoming execution.&lt;/li&gt;
&lt;li&gt;⚡ &lt;strong&gt;In Progress (&lt;code&gt;in_progress&lt;/code&gt;)&lt;/strong&gt;: Active work during the current sprint.&lt;/li&gt;
&lt;li&gt;🔍 &lt;strong&gt;In Review (&lt;code&gt;review&lt;/code&gt;)&lt;/strong&gt;: Tasks pending review, testing, or third-party feedback.&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;Done (&lt;code&gt;done&lt;/code&gt;)&lt;/strong&gt;: Completed deliverables.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&quot;https://datalaria.com/en/posts/app-lifeops_part3_modulos_kanban/lifeops_kanban_board_en.png&quot; alt=&quot;Professional 4-column Kanban board with project tags, urgent priority chips, and progress badges&quot;&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;┌──────────────────┬──────────────────┬──────────────────┬──────────────────┐
│ 📝 TO DO (3)     │ ⚡ IN PROGRESS (2)│ 🔍 IN REVIEW (1) │ ✅ DONE (8)      │
├──────────────────┼──────────────────┼──────────────────┼──────────────────┤
│ ┌──────────────┐ │ ┌──────────────┐ │ ┌──────────────┐ │ ┌──────────────┐ │
│ │ Refactor API │ │ │ Frontend UI  │ │ │ E2E Test Run │ │ │ DB Migration │ │
│ │ 🚨 Critical  │ │ │ 🟡 Medium    │ │ │ 🟢 Low       │ │ │ 2026-09-02   │ │
│ │ 📅 Sep 18    │ │ │ 💬 3 notes   │ │ │ 📅 Today     │ │ │ 💬 5 notes   │ │
│ │ [→ Move]     │ │ │ [←]      [→] │ │ │ [←]      [→] │ │ │ [← Move]     │ │
│ └──────────────┘ │ └──────────────┘ │ └──────────────┘ │ └──────────────┘ │
└──────────────────┴──────────────────┴──────────────────┴──────────────────┘
&lt;/code&gt;&lt;/pre&gt;
&lt;h5 id=&quot;4-1-optimistic-ui-updates-for-tactile-fluidity&quot;&gt;4.1. Optimistic UI Updates for Tactile Fluidity&lt;/h5&gt;
&lt;p&gt;When moving a task from &lt;code&gt;in_progress&lt;/code&gt; to &lt;code&gt;review&lt;/code&gt;, waiting for the entire roundtrip (browser → Render API → Supabase → response) introduces an undesirable delay of several hundred milliseconds.&lt;/p&gt;
&lt;p&gt;To deliver an instantaneous tactile feel, we execute an &lt;strong&gt;optimistic state update&lt;/strong&gt; in React:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;const handleMoveTask = async (taskId, currentStatus, direction) =&amp;gt; {
  const statusOrder = ['todo', 'in_progress', 'review', 'done'];
  const currentIndex = statusOrder.indexOf(currentStatus);
  const targetIndex = direction === 'next' ? currentIndex + 1 : currentIndex - 1;

  if (targetIndex &amp;lt; 0 || targetIndex &amp;gt;= statusOrder.length) return;
  const targetStatus = statusOrder[targetIndex];

  // 1. Instantaneous optimistic update
&lt;/code&gt;&lt;/pre&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/27227/project-lifeops-part-core-interactive-modules-fitness-library-cinema-professional-kanba</guid>
<pubDate>Tue, 15 Sep 2026 06:12:43 +0000</pubDate>
</item>
<item>
<title>Why I Don't Put Tenant-Specific Validation in My FastAPI Endpoints</title>
<link>https://coderlegion.com/26690/why-i-dont-put-tenant-specific-validation-in-my-fastapi-endpoints</link>
<description>&lt;p&gt;Multi-tenant applications have a way of turning simple endpoints into surprisingly complicated pieces of code.&lt;/p&gt;
&lt;p&gt;At first, tenant-specific validation seems harmless.&lt;/p&gt;
&lt;p&gt;You have two tenants with different limits, so you write something like:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;if tenant == &quot;acme&quot;:
    max_quantity = 100
elif tenant == &quot;globex&quot;:
    max_quantity = 10
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It works.&lt;/p&gt;
&lt;p&gt;The problem isn't the first &lt;code&gt;if&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The problem is what happens after there are ten more rules.&lt;/p&gt;
&lt;h3 id=&quot;when-the-endpoint-starts-knowing-too-much&quot;&gt;When the endpoint starts knowing too much&lt;/h3&gt;
&lt;p&gt;I prefer an endpoint to answer one main question:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;What should the application do with this request?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I don't want the route handler to also be responsible for figuring out:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;which tenant made the request&lt;/li&gt;
&lt;li&gt;whether that tenant is active&lt;/li&gt;
&lt;li&gt;what limits the tenant has&lt;/li&gt;
&lt;li&gt;which validation rules apply&lt;/li&gt;
&lt;li&gt;how those validation failures should be returned&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;None of those things are really the endpoint's job.&lt;/p&gt;
&lt;p&gt;They are context and validation concerns.&lt;/p&gt;
&lt;p&gt;Once those responsibilities start accumulating inside a route, even a small endpoint can become difficult to reason about.&lt;/p&gt;
&lt;h3 id=&quot;fastapi-dependencies-are-a-natural-boundary&quot;&gt;FastAPI dependencies are a natural boundary&lt;/h3&gt;
&lt;p&gt;FastAPI's dependency injection provides a useful place to resolve tenant information.&lt;/p&gt;
&lt;p&gt;For example, a request can contain:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-http&quot;&gt;X-Tenant-ID: globex
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A dependency can turn that header into a strongly typed &lt;code&gt;TenantConfig&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Now the rest of the request-processing pipeline doesn't need to repeatedly parse the header or look up the tenant.&lt;/p&gt;
&lt;p&gt;But that still leaves an interesting problem.&lt;/p&gt;
&lt;p&gt;The request body doesn't contain the information we need for every validation rule.&lt;/p&gt;
&lt;h3 id=&quot;some-validation-needs-context&quot;&gt;Some validation needs context&lt;/h3&gt;
&lt;p&gt;Consider a simple Pydantic model:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;class OrderCreate(BaseModel):
    product_id: str = Field(..., min_length=1)
    quantity: int = Field(..., gt=0)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The model can determine that &lt;code&gt;quantity=0&lt;/code&gt; is invalid without knowing anything about the tenant.&lt;/p&gt;
&lt;p&gt;But suppose:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;acme   → maximum quantity = 100
globex → maximum quantity = 10
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now the validity of the same request depends on information outside the request body.&lt;/p&gt;
&lt;p&gt;That's where Pydantic v2's validation context becomes useful.&lt;/p&gt;
&lt;p&gt;Instead of hard-coding tenant names into the model, the application can pass the resolved tenant into validation:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;OrderCreate.model_validate(
    payload,
    context={&quot;tenant&quot;: tenant},
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The validator can access that information through &lt;code&gt;ValidationInfo&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The important part for me isn't the specific API. It's the separation of responsibilities:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;FastAPI dependency
        ↓
Resolve tenant
        ↓
TenantConfig
        ↓
Pydantic validation
        ↓
Validated request
        ↓
Endpoint
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The endpoint doesn't need to know how any of that happened.&lt;/p&gt;
&lt;h3 id=&quot;but-there-s-a-trade-off&quot;&gt;But there's a trade-off&lt;/h3&gt;
&lt;p&gt;I don't think this pattern is automatically the right answer for every FastAPI application.&lt;/p&gt;
&lt;p&gt;To make tenant information available before Pydantic validation, the example intentionally receives the request body as raw data and calls &lt;code&gt;model_validate()&lt;/code&gt; itself.&lt;/p&gt;
&lt;p&gt;That gives us the context we need, but we give up some of FastAPI's automatic request-body handling.&lt;/p&gt;
&lt;p&gt;One noticeable consequence is OpenAPI documentation.&lt;/p&gt;
&lt;p&gt;With a normal Pydantic request model, FastAPI can automatically generate a detailed request schema. With the raw-body approach, the generated request schema is less descriptive.&lt;/p&gt;
&lt;p&gt;That's a real trade-off.&lt;/p&gt;
&lt;p&gt;If automatically generated API documentation were the most important requirement, I'd investigate a different integration.&lt;/p&gt;
&lt;h3 id=&quot;what-i-actually-care-about&quot;&gt;What I actually care about&lt;/h3&gt;
&lt;p&gt;The goal isn't to eliminate every &lt;code&gt;if&lt;/code&gt; statement from an application.&lt;/p&gt;
&lt;p&gt;It's to keep responsibilities somewhere they make sense.&lt;/p&gt;
&lt;p&gt;I'd rather have an endpoint that looks like:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;def create_order(
    ctx: TenantOrderContext = Depends(get_validated_order),
) -&amp;gt; OrderResponse:
    ...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;than one that starts doing all of this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;def create_order(request, tenant):
    # resolve tenant
    # check tenant status
    # determine limits
    # validate request
    # handle validation failures
    # process order
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The second approach isn't inherently broken.&lt;/p&gt;
&lt;p&gt;It's just carrying more responsibility than the endpoint needs to.&lt;/p&gt;
&lt;h3 id=&quot;the-broader-lesson&quot;&gt;The broader lesson&lt;/h3&gt;
&lt;p&gt;The interesting part of this exercise wasn't really multi-tenancy.&lt;/p&gt;
&lt;p&gt;It was thinking about &lt;strong&gt;where validation gets its information from&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Some rules depend only on the object being validated.&lt;/p&gt;
&lt;p&gt;Others depend on the context surrounding that object.&lt;/p&gt;
&lt;p&gt;When validation depends on external context, putting everything inside the route handler is an easy solution. But it isn't necessarily the cleanest boundary.&lt;/p&gt;
&lt;p&gt;FastAPI dependencies and Pydantic v2 validation context give you another option.&lt;/p&gt;
&lt;p&gt;I built a small working example of this approach with FastAPI, Pydantic v2, and pytest, including tests for tenant-specific limits, invalid requests, inactive tenants, unknown tenants, and malformed payloads.&lt;/p&gt;
&lt;p&gt;The complete implementation is available on GitHub.&lt;br&gt;
{% embed &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/MaybeSomeone-arc18/fastapi-multitenant-validation&quot;&gt;https://github.com/MaybeSomeone-arc18/fastapi-multitenant-validation&lt;/a&gt; %}&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/26690/why-i-dont-put-tenant-specific-validation-in-my-fastapi-endpoints</guid>
<pubDate>Thu, 10 Sep 2026 02:52:06 +0000</pubDate>
</item>
<item>
<title>Building a Real-Time Dashboard with FastAPI, WebSockets, and MySQL</title>
<link>https://coderlegion.com/26578/building-a-real-time-dashboard-with-fastapi-websockets-and-mysql</link>
<description>&lt;p&gt;I recently worked on a lightweight real-time dashboard built with &lt;strong&gt;FastAPI&lt;/strong&gt;, &lt;strong&gt;WebSockets&lt;/strong&gt;, and &lt;strong&gt;aiomysql&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;The main goal was simple: retrieve live data from a MySQL database and push updates to the frontend in real time without relying on continuous polling.&lt;/p&gt;
&lt;p&gt;This project provided a practical opportunity to explore asynchronous programming, WebSocket communication, and database integration using Python.&lt;/p&gt;
&lt;h3 id=&quot;project-overview&quot;&gt;Project Overview&lt;/h3&gt;
&lt;p&gt;The application uses a &lt;strong&gt;FastAPI backend&lt;/strong&gt; to communicate with a MySQL database and a &lt;strong&gt;WebSocket endpoint&lt;/strong&gt; to deliver database updates to connected clients.&lt;/p&gt;
&lt;p&gt;The overall architecture can be summarized as:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;MySQL Database
      │
      ▼
   aiomysql
      │
      ▼
FastAPI Backend
      │
      ▼
  WebSocket
      │
      ▼
Frontend Dashboard
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The WebSocket connection allows the server to push new data directly to the client, making the dashboard capable of displaying updates in real time.&lt;/p&gt;
&lt;h3 id=&quot;what-this-project-demonstrates&quot;&gt;What This Project Demonstrates&lt;/h3&gt;
&lt;p&gt;The project covers several important backend development concepts:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Setting up Python and the required dependencies&lt;/li&gt;
&lt;li&gt;Building an asynchronous FastAPI backend&lt;/li&gt;
&lt;li&gt;Connecting to MySQL using &lt;code&gt;aiomysql&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Implementing a WebSocket endpoint&lt;/li&gt;
&lt;li&gt;Retrieving database records asynchronously&lt;/li&gt;
&lt;li&gt;Sending live data to connected clients&lt;/li&gt;
&lt;li&gt;Running the application with Uvicorn&lt;/li&gt;
&lt;li&gt;Structuring a simple real-time backend application&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;why-use-websockets-&quot;&gt;Why Use WebSockets?&lt;/h3&gt;
&lt;p&gt;Traditional REST APIs generally follow a request/response model.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Client → Request → Server
Client ← Response ← Server
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If the frontend needs updated information continuously, it may have to repeatedly send requests to the server.&lt;/p&gt;
&lt;p&gt;This is commonly known as &lt;strong&gt;polling&lt;/strong&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Client → Request
Server → Response

Client → Request
Server → Response

Client → Request
Server → Response
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With WebSockets, the client establishes a persistent connection with the server:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Client ⇄ WebSocket Connection ⇄ Server
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The server can then push new information to the client whenever it becomes available.&lt;/p&gt;
&lt;p&gt;This makes WebSockets particularly useful for applications where data needs to be delivered quickly and continuously.&lt;/p&gt;
&lt;h3 id=&quot;potential-use-cases&quot;&gt;Potential Use Cases&lt;/h3&gt;
&lt;p&gt;This architecture can be useful for:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Real-time dashboards&lt;/li&gt;
&lt;li&gt;Monitoring systems&lt;/li&gt;
&lt;li&gt;Live administration panels&lt;/li&gt;
&lt;li&gt;Student management systems&lt;/li&gt;
&lt;li&gt;Record management applications&lt;/li&gt;
&lt;li&gt;System status monitoring&lt;/li&gt;
&lt;li&gt;Live data visualization&lt;/li&gt;
&lt;li&gt;Notification systems&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;documentation-and-source-code&quot;&gt;Documentation and Source Code&lt;/h3&gt;
&lt;p&gt;I created a complete guide covering the setup and implementation process, including:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Python installation&lt;/li&gt;
&lt;li&gt;Required package installation&lt;/li&gt;
&lt;li&gt;Project configuration&lt;/li&gt;
&lt;li&gt;Server startup&lt;/li&gt;
&lt;li&gt;Complete &lt;code&gt;main.py&lt;/code&gt; implementation&lt;/li&gt;
&lt;li&gt;FastAPI and WebSocket configuration&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The GitHub repository also includes the documentation and source code for reference.&lt;/p&gt;
&lt;h4 id=&quot;github-repository&quot;&gt;GitHub Repository&lt;/h4&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/NithilaLD/fastapi-websocket-realtime-dashboard&quot;&gt;FastAPI WebSocket Real-Time Dashboard - GitHub Repository&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The repository contains:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Complete Guide (PDF)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Source Code (&lt;code&gt;main.py&lt;/code&gt;)&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;key-technologies&quot;&gt;Key Technologies&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Technology &lt;/th&gt;&lt;th&gt;Purpose&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Python     &lt;/td&gt;&lt;td&gt;Backend programming&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;FastAPI    &lt;/td&gt;&lt;td&gt;Asynchronous web framework&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;WebSockets &lt;/td&gt;&lt;td&gt;Real-time client-server communication&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;aiomysql   &lt;/td&gt;&lt;td&gt;Asynchronous MySQL connectivity&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;MySQL      &lt;/td&gt;&lt;td&gt;Database&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Uvicorn    &lt;/td&gt;&lt;td&gt;ASGI server&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id=&quot;final-thoughts-1&quot;&gt;Final Thoughts&lt;/h3&gt;
&lt;p&gt;This project was a useful exercise in combining &lt;strong&gt;asynchronous programming, database connectivity, and real-time communication&lt;/strong&gt; into a practical backend application.&lt;/p&gt;
&lt;p&gt;FastAPI and WebSockets provide a straightforward foundation for building applications where data needs to move between the server and client with minimal delay.&lt;/p&gt;
&lt;p&gt;I look forward to extending this project with features such as authentication, multiple connected clients, automatic database change detection, improved error handling, and a more advanced frontend dashboard.&lt;/p&gt;
&lt;p&gt;If you are working with FastAPI or real-time applications, I'd be interested to hear about your projects and approaches.&lt;/p&gt;
&lt;h3 id=&quot;also-published-on&quot;&gt;Also Published On&lt;/h3&gt;
&lt;p&gt;This article is also available on the following platforms:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;LinkedIn:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.linkedin.com/pulse/building-real-time-fastapi-dashboard-websockets-async-liyanarachchi-nvf7c/&quot;&gt;Read on LinkedIn&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Medium:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://dulannithilaliyanarachchi.medium.com/building-a-real-time-fastapi-dashboard-with-websockets-and-async-mysql-344d86522a0e&quot;&gt;Read on Medium&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;AWS:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://builder.aws.com/content/3Ij3xiZllF1UvwYpk5OCYx52UfK/building-a-real-time-fastapi-dashboard-with-websockets-and-async-mysql&quot;&gt;Read on AWS&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;DEV Community:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://dev.to/dulannithilaliyanarachchi/building-a-real-time-dashboard-with-fastapi-websockets-and-mysql-1494&quot;&gt;Read on DEV Community&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/26578/building-a-real-time-dashboard-with-fastapi-websockets-and-mysql</guid>
<pubDate>Wed, 09 Sep 2026 08:42:41 +0000</pubDate>
</item>
<item>
<title>Coding Period Has Started(WEEK-1)</title>
<link>https://coderlegion.com/26521/coding-period-has-started-week-1</link>
<description>&lt;p&gt;&lt;strong&gt;Progress I have made in this week:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Created a ArtifactManager object which hooks into the OHIFViewer and&lt;br&gt;
gets access to the cornerstone tools.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Created a FindSimilarArtifacts tool which gets the coordinates from&lt;br&gt;
the rectangle ROI and performs the next steps.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Drafted the EventListener which handles asynchronous response&lt;br&gt;
payloads with the FastAPI backend.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Things for next week:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Extracting DICOM Instance UID from the Orthanc viewer state to&lt;br&gt;
properly route images to the AI model.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Creating a JSON payload of the exact ROI coordinates and routing it&lt;br&gt;
to the backend.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Drafting a Microservice Architecture for the FastAPI server.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/26521/coding-period-has-started-week-1</guid>
<pubDate>Tue, 08 Sep 2026 21:14:31 +0000</pubDate>
</item>
<item>
<title>FastAPI Authentication with UserHarbor</title>
<link>https://coderlegion.com/26420/fastapi-authentication-with-userharbor</link>
<description>&lt;p&gt;This tutorial shows how to connect &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/userharbor/userharbor&quot;&gt;UserHarbor&lt;/a&gt; directly to a &lt;a rel=&quot;nofollow&quot; href=&quot;https://fastapi.tiangolo.com/&quot;&gt;FastAPI&lt;/a&gt; application and build a complete authentication flow step by step.&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/userharbor/userharbor&quot;&gt;UserHarbor&lt;/a&gt; is a framework-agnostic Python library for user accounts, authentication, sessions, password recovery, and roles and permissions. It keeps those security rules in the application layer while leaving HTTP routing, persistence, and email delivery to integrations. For more information about UserHarbor and its design, see the &lt;a rel=&quot;nofollow&quot; href=&quot;https://userharbor.github.io&quot;&gt;official documentation&lt;/a&gt;.&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; For most FastAPI applications, prefer the ready-made &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/userharbor/userharbor-fastapi&quot;&gt;&lt;code&gt;userharbor-fastapi&lt;/code&gt;&lt;/a&gt; adapter. It provides the router, dependencies, schemas, and HTTP error mapping shown in this tutorial.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This tutorial builds the same ideas step by step.&lt;/p&gt;
&lt;p&gt;You will create a small &lt;a rel=&quot;nofollow&quot; href=&quot;https://fastapi.tiangolo.com/&quot;&gt;FastAPI&lt;/a&gt; application that uses &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/userharbor/userharbor&quot;&gt;UserHarbor&lt;/a&gt; directly for:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;user registration&lt;/li&gt;
&lt;li&gt;email verification&lt;/li&gt;
&lt;li&gt;login&lt;/li&gt;
&lt;li&gt;bearer session authentication&lt;/li&gt;
&lt;li&gt;current-user dependencies&lt;/li&gt;
&lt;li&gt;password reset&lt;/li&gt;
&lt;li&gt;password change&lt;/li&gt;
&lt;li&gt;account deletion&lt;/li&gt;
&lt;li&gt;logout&lt;/li&gt;
&lt;li&gt;role and permission checks&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The goal is to show how the pieces fit together. &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/userharbor/userharbor&quot;&gt;UserHarbor&lt;/a&gt; owns the account rules, password hashing, token generation, token hashing, session validation, and role checks. &lt;a rel=&quot;nofollow&quot; href=&quot;https://fastapi.tiangolo.com/&quot;&gt;FastAPI&lt;/a&gt; owns HTTP routing, request parsing, response models, and dependency injection.&lt;/p&gt;
&lt;h3 id=&quot;install-dependencies&quot;&gt;Install dependencies&lt;/h3&gt;
&lt;p&gt;Create a virtual environment, then install FastAPI, UserHarbor, and a storage adapter:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;pip install &quot;fastapi[standard]&quot;
pip install userharbor
pip install userharbor-sqlalchemy
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This tutorial uses the SQLAlchemy store and a small console email sender. In a real application, use your normal database configuration and an email sender such as &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/userharbor/userharbor-smtp&quot;&gt;&lt;code&gt;userharbor-smtp&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&quot;create-main-py&quot;&gt;Create &lt;code&gt;main.py&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;Start with the imports and application setup:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from typing import Any

from fastapi import Depends, FastAPI, HTTPException, status
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
from pydantic import BaseModel
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from userharbor import UserHarbor
from userharbor.exceptions import (
    InvalidCredentialsError,
    InvalidEmailError,
    InvalidPasswordResetTokenError,
    InvalidPermissionError,
    InvalidRoleError,
    InvalidSessionTokenError,
    InvalidUsernameError,
    InvalidVerificationTokenError,
    PermissionDeniedError,
    UnknownPermissionError,
    UnknownRoleError,
    UnverifiedUserError,
    UserHarborError,
    WeakPasswordError,
)
from userharbor_sqlalchemy import SQLAlchemyUserStore


app = FastAPI()
bearer = HTTPBearer(auto_error=False)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;HTTPBearer(auto_error=False)&lt;/code&gt; lets your code decide exactly how to turn missing or invalid credentials into HTTP responses.&lt;/p&gt;
&lt;h3 id=&quot;add-an-email-sender&quot;&gt;Add an email sender&lt;/h3&gt;
&lt;p&gt;UserHarbor calls an &lt;code&gt;EmailSender&lt;/code&gt; when it needs to send verification tokens, password reset tokens, and account notifications.&lt;/p&gt;
&lt;p&gt;For the tutorial, print those messages to the terminal:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;class ConsoleEmailSender:
    def send_verification(
        self,
        username: str,
        email: str,
        verification_token: str,
    ) -&amp;gt; None:
        print(f&quot;Verify {username} at {email}: {verification_token}&quot;)

    def send_password_reset(
        self,
        username: str,
        email: str,
        reset_token: str,
    ) -&amp;gt; None:
        print(f&quot;Reset password for {username} at {email}: {reset_token}&quot;)

    def send_email_verified(self, username: str, email: str) -&amp;gt; None:
        print(f&quot;Email verified for {username} at {email}&quot;)

    def send_password_changed(self, username: str, email: str) -&amp;gt; None:
        print(f&quot;Password changed for {username} at {email}&quot;)

    def send_account_deleted(self, username: str, email: str) -&amp;gt; None:
        print(f&quot;Account deleted for {username} at {email}&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In production, replace this with an implementation that sends real email.&lt;/p&gt;
&lt;h3 id=&quot;configure-userharbor&quot;&gt;Configure UserHarbor&lt;/h3&gt;
&lt;p&gt;Create the SQLAlchemy store, create the tables, and pass the store and email sender to &lt;code&gt;UserHarbor&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;engine = create_engine(&quot;sqlite:///users.db&quot;)
SessionLocal = sessionmaker(bind=engine)

store = SQLAlchemyUserStore(SessionLocal)
store.metadata.create_all(engine)

harbor = UserHarbor(
    secret_key=&quot;change-this-secret-key&quot;,
    store=store,
    email_sender=ConsoleEmailSender(),
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The FastAPI application never handles password hashing or token hashing. UserHarbor does that before data reaches the store.&lt;/p&gt;
&lt;h3 id=&quot;define-request-and-response-models&quot;&gt;Define request and response models&lt;/h3&gt;
&lt;p&gt;Use Pydantic models for the HTTP layer:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;class RegisterRequest(BaseModel):
    username: str
    email: str
    password: str


class LoginRequest(BaseModel):
    username: str
    password: str


class VerifyEmailRequest(BaseModel):
    token: str


class PasswordResetRequest(BaseModel):
    email: str


class PasswordResetConfirmRequest(BaseModel):
    token: str
    new_password: str


class ChangePasswordRequest(BaseModel):
    old_password: str
    new_password: str


class DeleteAccountRequest(BaseModel):
    password: str


class TokenResponse(BaseModel):
    access_token: str
    token_type: str = &quot;bearer&quot;


class PublicUser(BaseModel):
    username: str
    email: str
    verified: bool
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;These models are not part of UserHarbor. They are your HTTP contract.&lt;/p&gt;
&lt;h3 id=&quot;map-userharbor-errors-to-http-errors&quot;&gt;Map UserHarbor errors to HTTP errors&lt;/h3&gt;
&lt;p&gt;UserHarbor raises domain exceptions. Your web layer should translate them into HTTP responses:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;ERROR_CODES: dict[type[UserHarborError], str] = {
    InvalidCredentialsError: &quot;invalid_credentials&quot;,
    InvalidEmailError: &quot;invalid_email&quot;,
    InvalidPasswordResetTokenError: &quot;invalid_password_reset_token&quot;,
    InvalidPermissionError: &quot;invalid_permission&quot;,
    InvalidRoleError: &quot;invalid_role&quot;,
    InvalidSessionTokenError: &quot;invalid_session_token&quot;,
    InvalidUsernameError: &quot;invalid_username&quot;,
    InvalidVerificationTokenError: &quot;invalid_verification_token&quot;,
    PermissionDeniedError: &quot;permission_denied&quot;,
    UnknownPermissionError: &quot;unknown_permission&quot;,
    UnknownRoleError: &quot;unknown_role&quot;,
    UnverifiedUserError: &quot;unverified_user&quot;,
    WeakPasswordError: &quot;weak_password&quot;,
}


def error_code(error: UserHarborError) -&amp;gt; str:
    for error_type, code in ERROR_CODES.items():
        if isinstance(error, error_type):
            return code
    return &quot;userharbor_error&quot;


def status_code(error: UserHarborError) -&amp;gt; int:
    if isinstance(error, (InvalidCredentialsError, InvalidSessionTokenError)):
        return status.HTTP_401_UNAUTHORIZED
    if isinstance(error, (PermissionDeniedError, UnverifiedUserError)):
        return status.HTTP_403_FORBIDDEN
    if isinstance(error, (UnknownPermissionError, UnknownRoleError)):
        return status.HTTP_404_NOT_FOUND
    if isinstance(error, InvalidUsernameError) and &quot;already exists&quot; in str(error):
        return status.HTTP_409_CONFLICT
    return status.HTTP_400_BAD_REQUEST


def to_http_error(error: UserHarborError) -&amp;gt; HTTPException:
    return HTTPException(
        status_code=status_code(error),
        detail={
            &quot;detail&quot;: str(error) or error.__class__.__name__,
            &quot;code&quot;: error_code(error),
        },
        headers={&quot;WWW-Authenticate&quot;: &quot;Bearer&quot;}
        if isinstance(error, InvalidSessionTokenError)
        else None,
    )
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This keeps authentication failures, authorization failures, and validation errors distinct.&lt;/p&gt;
&lt;h3 id=&quot;register-users&quot;&gt;Register users&lt;/h3&gt;
&lt;p&gt;The registration endpoint parses JSON, calls &lt;code&gt;harbor.register()&lt;/code&gt;, and returns a neutral response:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;@app.post(&quot;/auth/register&quot;, status_code=status.HTTP_201_CREATED)
def register(request: RegisterRequest) -&amp;gt; dict[str, str]:
    try:
        harbor.register(
            username=request.username,
            email=request.email,
            password=request.password,
        )
    except UserHarborError as error:
        raise to_http_error(error) from error
    return {&quot;detail&quot;: &quot;User registered&quot;}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;UserHarbor validates the username, email, and password. It creates the user, stores only the password hash, stores only the verification token hash, and calls the email sender with the raw verification token.&lt;/p&gt;
&lt;h3 id=&quot;verify-email-addresses&quot;&gt;Verify email addresses&lt;/h3&gt;
&lt;p&gt;Copy the verification token printed by &lt;code&gt;ConsoleEmailSender&lt;/code&gt; and send it to this endpoint:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;@app.post(&quot;/auth/verify-email&quot;)
def verify_email(request: VerifyEmailRequest) -&amp;gt; dict[str, str]:
    try:
        harbor.verify_email(request.token)
    except UserHarborError as error:
        raise to_http_error(error) from error
    return {&quot;detail&quot;: &quot;Email verified&quot;}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After verification, the user can log in.&lt;/p&gt;
&lt;h3 id=&quot;login-and-return-a-bearer-token&quot;&gt;Login and return a bearer token&lt;/h3&gt;
&lt;p&gt;Call &lt;code&gt;harbor.login()&lt;/code&gt; and return the session token in a bearer-compatible response:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;@app.post(&quot;/auth/login&quot;, response_model=TokenResponse)
def login(request: LoginRequest) -&amp;gt; TokenResponse:
    try:
        session_token = harbor.login(request.username, request.password)
    except UserHarborError as error:
        raise to_http_error(error) from error
    return TokenResponse(access_token=session_token)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The raw session token is returned to the client once. UserHarbor stores a hash of it through the configured store.&lt;/p&gt;
&lt;p&gt;Clients should send the token on later requests:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-http&quot;&gt;Authorization: Bearer &amp;lt;session-token&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;create-current-user-dependencies&quot;&gt;Create current-user dependencies&lt;/h3&gt;
&lt;p&gt;Now create the dependency that reads the bearer token and asks UserHarbor for the current user:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;def get_session_token(
    credentials: HTTPAuthorizationCredentials | None = Depends(bearer),
) -&amp;gt; str:
    if credentials is None:
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail=&quot;Missing bearer token&quot;,
            headers={&quot;WWW-Authenticate&quot;: &quot;Bearer&quot;},
        )
    return credentials.credentials


def get_current_user(token: str = Depends(get_session_token)) -&amp;gt; Any:
    try:
        return harbor.get_current_user(token)
    except UserHarborError as error:
        raise to_http_error(error) from error
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is the main connection between FastAPI security dependencies and UserHarbor session validation.&lt;/p&gt;
&lt;h3 id=&quot;protect-a-route&quot;&gt;Protect a route&lt;/h3&gt;
&lt;p&gt;Use &lt;code&gt;Depends(get_current_user)&lt;/code&gt; in any route that requires authentication:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;def serialize_user(user: Any) -&amp;gt; PublicUser:
    return PublicUser(
        username=user.username,
        email=user.email,
        verified=user.verified,
    )


@app.get(&quot;/auth/me&quot;, response_model=PublicUser)
def me(user: Any = Depends(get_current_user)) -&amp;gt; PublicUser:
    return serialize_user(user)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If the token is missing, invalid, expired, or points to a deleted user, UserHarbor rejects it.&lt;/p&gt;
&lt;h3 id=&quot;logout&quot;&gt;Logout&lt;/h3&gt;
&lt;p&gt;Logout removes the current session token:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;@app.post(&quot;/auth/logout&quot;)
def logout(token: str = Depends(get_session_token)) -&amp;gt; dict[str, str]:
    try:
        harbor.logout(token)
    except UserHarborError as error:
        raise to_http_error(error) from error
    return {&quot;detail&quot;: &quot;Logged out&quot;}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To remove all sessions for the current user:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;@app.post(&quot;/auth/logout-all&quot;)
def logout_all(token: str = Depends(get_session_token)) -&amp;gt; dict[str, str]:
    try:
        harbor.logout_all(token)
    except UserHarborError as error:
        raise to_http_error(error) from error
    return {&quot;detail&quot;: &quot;Logged out from all sessions&quot;}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;password-reset&quot;&gt;Password reset&lt;/h3&gt;
&lt;p&gt;Password reset is split into two routes. The first route sends a reset token if the email belongs to a user:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;@app.post(&quot;/auth/password-reset/request&quot;)
def request_password_reset(request: PasswordResetRequest) -&amp;gt; dict[str, str]:
    try:
        harbor.send_password_reset(request.email)
    except UserHarborError as error:
        raise to_http_error(error) from error
    return {&quot;detail&quot;: &quot;Password reset email sent&quot;}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;UserHarbor returns neutrally for unknown email addresses, helping reduce email enumeration.&lt;/p&gt;
&lt;p&gt;The second route accepts the reset token and the new password:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;@app.post(&quot;/auth/password-reset/confirm&quot;)
def confirm_password_reset(request: PasswordResetConfirmRequest) -&amp;gt; dict[str, str]:
    try:
        harbor.reset_password(
            new_password=request.new_password,
            reset_token=request.token,
        )
    except UserHarborError as error:
        raise to_http_error(error) from error
    return {&quot;detail&quot;: &quot;Password reset&quot;}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After a successful reset, UserHarbor removes all sessions for that user.&lt;/p&gt;
&lt;h3 id=&quot;password-change&quot;&gt;Password change&lt;/h3&gt;
&lt;p&gt;Password change requires an existing session and the current password:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;@app.post(&quot;/auth/password/change&quot;)
def change_password(
    request: ChangePasswordRequest,
    token: str = Depends(get_session_token),
) -&amp;gt; dict[str, str]:
    try:
        harbor.change_password(
            old_password=request.old_password,
            new_password=request.new_password,
            session_token=token,
        )
    except UserHarborError as error:
        raise to_http_error(error) from error
    return {&quot;detail&quot;: &quot;Password changed&quot;}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;UserHarbor invalidates all sessions after a successful password change.&lt;/p&gt;
&lt;h3 id=&quot;delete-accounts&quot;&gt;Delete accounts&lt;/h3&gt;
&lt;p&gt;Account deletion should require both a valid session and the current password:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;@app.delete(&quot;/auth/account&quot;)
def delete_account(
    request: DeleteAccountRequest,
    token: str = Depends(get_session_token),
) -&amp;gt; dict[str, str]:
    try:
        harbor.delete_account(
            password=request.password,
            session_token=token,
        )
    except UserHarborError as error:
        raise to_http_error(error) from error
    return {&quot;detail&quot;: &quot;Account deleted&quot;}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;UserHarbor removes all sessions before deleting the user and then calls the email sender with an account-deleted notification.&lt;/p&gt;
&lt;h3 id=&quot;add-role-and-permission-dependencies&quot;&gt;Add role and permission dependencies&lt;/h3&gt;
&lt;p&gt;Create a dependency factory for permissions:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;def require_permission(permission: str):
    def dependency(token: str = Depends(get_session_token)) -&amp;gt; Any:
        try:
            return harbor.require_permission(token, permission)
        except UserHarborError as error:
            raise to_http_error(error) from error

    return dependency
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Use it on protected routes:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;@app.get(&quot;/admin/users&quot;)
def list_users(
    user: Any = Depends(require_permission(&quot;users.read&quot;)),
) -&amp;gt; dict[str, str]:
    return {&quot;detail&quot;: f&quot;{user.username} can read users&quot;}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Before this can succeed, the permission and role must exist and the user must have the role:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;harbor.roles.create(&quot;admin&quot;)
harbor.permissions.create(&quot;users.read&quot;)
harbor.roles.grant_permission(&quot;admin&quot;, &quot;users.read&quot;)
harbor.grant_role(&quot;jane&quot;, &quot;admin&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In a real application, put that setup in migrations, admin commands, or an internal admin interface.&lt;/p&gt;
&lt;h3 id=&quot;production-hardening-checklist&quot;&gt;Production hardening checklist&lt;/h3&gt;
&lt;p&gt;This tutorial keeps everything in one file so the flow is easy to inspect. For a real application:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;load &lt;code&gt;secret_key&lt;/code&gt;, database URLs, and SMTP credentials from environment variables or a secret manager&lt;/li&gt;
&lt;li&gt;use a long random &lt;code&gt;secret_key&lt;/code&gt; and rotate it carefully, because it is used for token hashing&lt;/li&gt;
&lt;li&gt;serve the API only over HTTPS&lt;/li&gt;
&lt;li&gt;replace &lt;code&gt;ConsoleEmailSender&lt;/code&gt; with a real email sender&lt;/li&gt;
&lt;li&gt;use migrations instead of &lt;code&gt;store.metadata.create_all(engine)&lt;/code&gt; at runtime&lt;/li&gt;
&lt;li&gt;decide where roles and permissions are created, such as migrations or admin commands&lt;/li&gt;
&lt;li&gt;keep bearer tokens out of logs&lt;/li&gt;
&lt;li&gt;configure CORS explicitly if a browser frontend calls the API&lt;/li&gt;
&lt;li&gt;add rate limiting around login, password reset, and verification resend&lt;/li&gt;
&lt;li&gt;return neutral responses for account discovery-sensitive flows&lt;/li&gt;
&lt;li&gt;write tests for successful flows and for each mapped error response&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;run-the-app&quot;&gt;Run the app&lt;/h3&gt;
&lt;p&gt;Run the application:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;fastapi dev main.py
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Open the interactive docs:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;http://127.0.0.1:8000/docs
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then try this flow:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Call &lt;code&gt;POST /auth/register&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Copy the verification token printed in the terminal&lt;/li&gt;
&lt;li&gt;Call &lt;code&gt;POST /auth/verify-email&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Call &lt;code&gt;POST /auth/login&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Copy the returned &lt;code&gt;access_token&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Send it as &lt;code&gt;Authorization: Bearer &amp;lt;token&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Call &lt;code&gt;GET /auth/me&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;what-you-built&quot;&gt;What you built&lt;/h3&gt;
&lt;p&gt;You now have a FastAPI application that uses UserHarbor directly.&lt;/p&gt;
&lt;p&gt;FastAPI handles:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;routing&lt;/li&gt;
&lt;li&gt;request parsing&lt;/li&gt;
&lt;li&gt;response models&lt;/li&gt;
&lt;li&gt;OpenAPI documentation&lt;/li&gt;
&lt;li&gt;dependency injection&lt;/li&gt;
&lt;li&gt;HTTP status codes&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;UserHarbor handles:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;account validation&lt;/li&gt;
&lt;li&gt;password hashing&lt;/li&gt;
&lt;li&gt;token generation&lt;/li&gt;
&lt;li&gt;token hashing before storage&lt;/li&gt;
&lt;li&gt;email verification&lt;/li&gt;
&lt;li&gt;session validation&lt;/li&gt;
&lt;li&gt;password reset and password change flows&lt;/li&gt;
&lt;li&gt;role and permission checks&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This is the same boundary used by the &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/userharbor/userharbor-fastapi&quot;&gt;&lt;code&gt;userharbor-fastapi&lt;/code&gt;&lt;/a&gt; adapter. The adapter simply packages this wiring so applications do not have to repeat it.&lt;/p&gt;
</description>
<category>Tutorials</category>
<guid isPermaLink="true">https://coderlegion.com/26420/fastapi-authentication-with-userharbor</guid>
<pubDate>Mon, 07 Sep 2026 20:38:47 +0000</pubDate>
</item>
<item>
<title>Building Role-Based Student, Teacher, and Admin Experiences in EchoEd</title>
<link>https://coderlegion.com/25470/building-role-based-student-teacher-and-admin-experiences-in-echoed</link>
<description>&lt;p&gt;A role-based product should involve more than hiding navigation links.&lt;/p&gt;
&lt;p&gt;  Students, teachers, and platform administrators have different goals, data needs, privacy constraints, and consequences attached to their actions. EchoEd’s recent UI/UX work separates these roles into three&lt;br&gt;
  canonical product areas:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;/learn for students&lt;/li&gt;
&lt;li&gt;/teach for teachers&lt;/li&gt;
&lt;li&gt;/admin for platform administrators&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;  The attached demo videos show each experience in its current form.&lt;/p&gt;
&lt;p&gt;  &lt;strong&gt;Student Learn&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;{% embed &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.youtube.com/watch?v=cch47qSHPHY&quot;&gt;https://www.youtube.com/watch?v=cch47qSHPHY&lt;/a&gt; %}&lt;/p&gt;
&lt;p&gt;  The Student experience prioritizes continuation over navigation complexity.&lt;/p&gt;
&lt;p&gt;  The /learn home composes existing course, progress, badge, certificate, and learner-product APIs to show:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The next governed learning action&lt;/li&gt;
&lt;li&gt;Current courses and progress&lt;/li&gt;
&lt;li&gt;Available learning&lt;/li&gt;
&lt;li&gt;Progress summaries&lt;/li&gt;
&lt;li&gt;Badges and certificates&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;  Course overviews use a reusable curriculum component for units and lessons. Lesson entry still goes through the existing start-course and progress APIs, so the UI cannot bypass backend sequencing rules.&lt;/p&gt;
&lt;p&gt;  Final assessment submissions use a shared confirmation dialog. Lesson completion exposes saving and failure states rather than optimistically assuming persistence.&lt;/p&gt;
&lt;p&gt;  No backend contracts were changed for the Student phase.&lt;/p&gt;
&lt;p&gt;  &lt;strong&gt;Teacher Teach&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;{% embed &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.youtube.com/watch?v=t3wBziH96N4&quot;&gt;https://www.youtube.com/watch?v=t3wBziH96N4&lt;/a&gt; %}&lt;/p&gt;
&lt;p&gt;  The Teacher experience introduces a canonical route family:&lt;/p&gt;
&lt;p&gt;  /teach&lt;br&gt;
  /teach/classes&lt;br&gt;
  /teach/classes/:id&lt;br&gt;
  /teach/curriculum&lt;br&gt;
  /teach/courses/:courseId/preview&lt;br&gt;
  /teach/assignments&lt;br&gt;
  /teach/learners/:learnerId&lt;/p&gt;
&lt;p&gt;  The Teacher home focuses on classes, learner progress, and curriculum. Class detail composes existing section APIs into overview, roster, assignment, progress, and discussion views.&lt;/p&gt;
&lt;p&gt;  Learner detail is scoped through class-roster context. Assignment creation uses confirmation and preserves form values after API failures.&lt;/p&gt;
&lt;p&gt;  The UI does not expose persistent feedback, manual review, or destructive discussion moderation because those workflows do not yet have sufficiently scoped backend contracts.&lt;/p&gt;
&lt;p&gt;  &lt;strong&gt;Platform Admin&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;{% embed &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.youtube.com/watch?v=Gj_T4a8uiiw&quot;&gt;https://www.youtube.com/watch?v=Gj_T4a8uiiw&lt;/a&gt; %}&lt;/p&gt;
&lt;p&gt;  The Admin experience uses canonical routes for overview, users, organizations, courses, badges, and reports.&lt;/p&gt;
&lt;p&gt;  Important implementation decisions include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;User lists omit unnecessary fields.&lt;/li&gt;
&lt;li&gt;Role mutations wait for API confirmation before updating the UI.&lt;/li&gt;
&lt;li&gt;Self-role changes and self-deletion are blocked in the interface.&lt;/li&gt;
&lt;li&gt;Destructive actions use exact consequence language.&lt;/li&gt;
&lt;li&gt;Organization visibility remains constrained by current membership-scoped APIs.&lt;/li&gt;
&lt;li&gt;Course oversight does not grant Content Studio authoring.&lt;/li&gt;
&lt;li&gt;Super Admin capabilities are based on verified authorization, not role-name assumptions.&lt;/li&gt;
&lt;li&gt;Unsupported moderation and lifecycle controls remain absent.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;  During seeded browser testing, the user-detail flow exposed a backend issue: FastAPI accepted the user ID as a string while SQLAlchemy compared it with a UUID column.&lt;/p&gt;
&lt;p&gt;  The repair typed detail and update parameters as uuid.UUID. We also moved the static /api/users/students route before /api/users/{user_id} so the dynamic route could not shadow it.&lt;/p&gt;
&lt;p&gt;  Regression tests now cover both behaviors.&lt;/p&gt;
&lt;p&gt;  &lt;strong&gt;Shared UX and Accessibility&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;  All three areas reuse the same production foundations:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Semantic design tokens&lt;/li&gt;
&lt;li&gt;Role-aware shell navigation&lt;/li&gt;
&lt;li&gt;Route guards&lt;/li&gt;
&lt;li&gt;Shared loading and state components&lt;/li&gt;
&lt;li&gt;Confirmation dialogs with focus trapping and restoration&lt;/li&gt;
&lt;li&gt;Keyboard-operable controls&lt;/li&gt;
&lt;li&gt;Visible focus indicators&lt;/li&gt;
&lt;li&gt;Reduced-motion behavior&lt;/li&gt;
&lt;li&gt;Semantic desktop tables&lt;/li&gt;
&lt;li&gt;Stacked mobile record layouts&lt;/li&gt;
&lt;li&gt;Legacy deep-link compatibility&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;  The frontend improves presentation and workflow clarity, but backend authorization remains authoritative.&lt;/p&gt;
&lt;p&gt;  &lt;strong&gt;Current Verification&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;  Frontend unit tests: 245 passing&lt;br&gt;
  Backend tests: 224 passing&lt;br&gt;
  Seeded Playwright scenarios: 3 passing&lt;br&gt;
  Production build: passing&lt;br&gt;
  Strict OpenSpec validation: passing&lt;/p&gt;
&lt;p&gt;  The Playwright suite currently covers the seeded Student workflow, the Platform Admin workflow, and rejection of an unauthorized Student deep link into Admin.&lt;/p&gt;
&lt;p&gt;  &lt;strong&gt;Known Gaps&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;  Several workflows need stronger backend contracts before the UI can safely support them:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Persistent teacher feedback&lt;/li&gt;
&lt;li&gt;Scoped teacher review&lt;/li&gt;
&lt;li&gt;Authenticated discussion moderation&lt;/li&gt;
&lt;li&gt;Platform-wide organization oversight&lt;/li&gt;
&lt;li&gt;Account suspension and restoration&lt;/li&gt;
&lt;li&gt;Administrative audit events&lt;/li&gt;
&lt;li&gt;Badge lifecycle management&lt;/li&gt;
&lt;li&gt;Explicit Super Admin permission inheritance&lt;/li&gt;
&lt;li&gt;Privacy-minimized Admin user response schemas&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;  These are documented gaps, not frontend placeholders.&lt;/p&gt;
&lt;p&gt;  &lt;strong&gt;Community Review&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;  I would value feedback from people working across education and software:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Do the boundaries between Learn, Teach, and Admin match real responsibilities?&lt;/li&gt;
&lt;li&gt;Which Teacher information should be visible at class level versus learner level?&lt;/li&gt;
&lt;li&gt;What information should a platform administrator never see in a broad user list?&lt;/li&gt;
&lt;li&gt;Which workflows should receive deeper screen-reader and keyboard testing?&lt;/li&gt;
&lt;li&gt;Which backend gap would you prioritize before expanding the next UI phase?&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;  The Student, Teacher, and Platform Admin production phases are complete for EchoEd’s currently supported APIs.&lt;/p&gt;
&lt;p&gt;  The full UI/UX overhaul is still underway. Content Studio, organization-admin self-service, Community, parent, and viewer experiences remain outside these completed phases.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/25470/building-role-based-student-teacher-and-admin-experiences-in-echoed</guid>
<pubDate>Wed, 26 Aug 2026 21:28:49 +0000</pubDate>
</item>
<item>
<title>Building UserHarbor: Framework-Agnostic User Management for Python</title>
<link>https://coderlegion.com/24763/building-userharbor-framework-agnostic-user-management-for-python</link>
<description>&lt;p&gt;User management is one of those problems that rarely feels difficult enough to deserve much attention.&lt;/p&gt;
&lt;p&gt;Until you implement it for the fifth time.&lt;/p&gt;
&lt;p&gt;Registration, login, sessions, email verification, password resets, password changes, account deletion, roles, permissions — none of these features are particularly unusual. But almost every application needs some combination of them, and the implementation often ends up tightly coupled to whatever framework, ORM, or infrastructure the project happened to use at the time.&lt;/p&gt;
&lt;p&gt;That was the problem that led me to build &lt;strong&gt;UserHarbor&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;UserHarbor is a framework-agnostic Python library for user account management. The goal is not to build another web framework or a complete identity platform. Instead, it provides a small domain-level API for common account operations while leaving HTTP, databases and email delivery to separate integrations.&lt;/p&gt;
&lt;p&gt;Since I first wrote about the project, the interesting part has increasingly become not just the authentication API itself, but the boundary between the core and its integrations.&lt;/p&gt;
&lt;h3 id=&quot;the-problem-i-wanted-to-solve&quot;&gt;The problem I wanted to solve&lt;/h3&gt;
&lt;p&gt;Imagine building two applications.&lt;/p&gt;
&lt;p&gt;One uses:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;FastAPI&lt;/li&gt;
&lt;li&gt;SQLAlchemy&lt;/li&gt;
&lt;li&gt;PostgreSQL&lt;/li&gt;
&lt;li&gt;SMTP&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Another uses:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Flask&lt;/li&gt;
&lt;li&gt;MongoDB&lt;/li&gt;
&lt;li&gt;an external email API&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The user-management rules are mostly the same.&lt;/p&gt;
&lt;p&gt;A password still needs to be validated and hashed. A verification token still needs to expire. Password reset tokens still need to be protected. Sessions need to be created and invalidated. Roles and permissions need to be checked.&lt;/p&gt;
&lt;p&gt;But in many libraries these rules are mixed together with database models, HTTP handlers or framework-specific abstractions.&lt;/p&gt;
&lt;p&gt;I wanted the opposite.&lt;/p&gt;
&lt;p&gt;The core should know &lt;strong&gt;what should happen&lt;/strong&gt;, but not necessarily &lt;strong&gt;how the application stores or transports the data&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;That leads to an architecture that looks roughly like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Application / Framework
        │
        ▼
    UserHarbor
        │
        ├── UserStore
        │       └── database / ORM / custom backend
        │
        └── EmailSender
                └── SMTP / API / custom provider
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The core owns things such as registration, validation, password hashing, token generation, token hashing, session handling and authorization rules.&lt;/p&gt;
&lt;p&gt;The adapters own infrastructure.&lt;/p&gt;
&lt;h3 id=&quot;a-small-domain-level-api&quot;&gt;A small domain-level API&lt;/h3&gt;
&lt;p&gt;UserHarbor currently handles the common account lifecycle:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;user registration&lt;/li&gt;
&lt;li&gt;email verification&lt;/li&gt;
&lt;li&gt;login&lt;/li&gt;
&lt;li&gt;sessions&lt;/li&gt;
&lt;li&gt;logout from one or all sessions&lt;/li&gt;
&lt;li&gt;password change&lt;/li&gt;
&lt;li&gt;password reset&lt;/li&gt;
&lt;li&gt;account deletion&lt;/li&gt;
&lt;li&gt;roles and permissions&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It deliberately does not expose HTTP endpoints itself.&lt;/p&gt;
&lt;p&gt;That means code using the core can look like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;user = harbor.register(
    username=&quot;jane&quot;,
    email=&quot;*Emails are not allowed*&quot;,
    password=&quot;StrongPassword123!&quot;,
)

harbor.verify_email(verification_token)

session_token = harbor.login(
    username=&quot;jane&quot;,
    password=&quot;StrongPassword123!&quot;,
)

current_user = harbor.get_current_user(session_token)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The same &lt;code&gt;UserHarbor&lt;/code&gt; instance can be used from FastAPI, Flask, Django, a CLI application or something that does not expose HTTP at all.&lt;/p&gt;
&lt;p&gt;Authorization follows the same idea:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;harbor.roles.create(&quot;admin&quot;)
harbor.permissions.create(&quot;users.delete&quot;)

harbor.roles.grant_permission(
    &quot;admin&quot;,
    &quot;users.delete&quot;,
)

harbor.grant_role(&quot;jane&quot;, &quot;admin&quot;)

if harbor.has_permission(session_token, &quot;users.delete&quot;):
    delete_user()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Or, if access should be enforced:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;user = harbor.require_permission(
    session_token,
    &quot;users.delete&quot;,
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;UserHarbor implements simple role-based access control, but leaves application-specific authorization policy outside the core. It intentionally does not try to become a general policy engine.&lt;/p&gt;
&lt;h3 id=&quot;framework-agnostic-does-not-mean-framework-unfriendly&quot;&gt;Framework-agnostic does not mean framework-unfriendly&lt;/h3&gt;
&lt;p&gt;One thing I wanted to avoid was making framework independence come at the cost of developer experience.&lt;/p&gt;
&lt;p&gt;For example, there is an official &lt;code&gt;userharbor-fastapi&lt;/code&gt; integration.&lt;/p&gt;
&lt;p&gt;Instead of manually writing authentication routes and dependencies, a FastAPI application can configure UserHarbor and attach the adapter:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from fastapi import FastAPI
from userharbor import UserHarbor
from userharbor_fastapi import UserHarborFastAPI

harbor = UserHarbor(
    secret_key=&quot;your-secret-key&quot;,
    store=store,
    email_sender=email_sender,
)

auth = UserHarborFastAPI(harbor)

app = FastAPI()
app.include_router(
    auth.router,
    prefix=&quot;/auth&quot;,
    tags=[&quot;auth&quot;],
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The adapter provides the framework-specific layer: routers, request schemas, bearer authentication dependencies, error mapping and helpers for requiring roles or permissions.&lt;/p&gt;
&lt;p&gt;The important part is that FastAPI still does not leak into the UserHarbor core.&lt;/p&gt;
&lt;p&gt;You can replace the web framework without replacing the account-management logic.&lt;/p&gt;
&lt;h3 id=&quot;the-harder-problem-what-does-it-mean-to-implement-userstore-&quot;&gt;The harder problem: what does it mean to implement &lt;code&gt;UserStore&lt;/code&gt;?&lt;/h3&gt;
&lt;p&gt;Originally, separating persistence behind a &lt;code&gt;UserStore&lt;/code&gt; interface seemed like the obvious solution.&lt;/p&gt;
&lt;p&gt;Define an interface, implement the methods, and now SQLAlchemy, MongoDB, Redis or anything else can provide storage.&lt;/p&gt;
&lt;p&gt;But there is a subtle problem.&lt;/p&gt;
&lt;p&gt;Matching method signatures does not mean two storage implementations behave the same way.&lt;/p&gt;
&lt;p&gt;Consider a password reset token.&lt;/p&gt;
&lt;p&gt;Should creating a new token remove an older token?&lt;/p&gt;
&lt;p&gt;What happens when a user is deleted?&lt;/p&gt;
&lt;p&gt;Should their sessions disappear automatically?&lt;/p&gt;
&lt;p&gt;What should happen if a transaction fails halfway through a password change?&lt;/p&gt;
&lt;p&gt;Should deleting something that no longer exists raise an error?&lt;/p&gt;
&lt;p&gt;These behaviors are part of the storage contract even though Python's type system cannot express them.&lt;/p&gt;
&lt;p&gt;This became one of the most important changes in UserHarbor 0.7.0.&lt;/p&gt;
&lt;h3 id=&quot;turning-the-adapter-contract-into-executable-tests&quot;&gt;Turning the adapter contract into executable tests&lt;/h3&gt;
&lt;p&gt;UserHarbor now ships a reusable contract test suite for &lt;code&gt;UserStore&lt;/code&gt; implementations.&lt;/p&gt;
&lt;p&gt;An adapter can import the complete suite:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# tests/test_user_store_contract.py

from userharbor.testing.user_store_contract import *
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then it only needs to provide a clean store:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import pytest


@pytest.fixture
def user_store():
    store = create_user_store()

    try:
        yield store
    finally:
        dispose_user_store(store)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The same tests can then run against SQLAlchemy, an in-memory implementation, or a completely different persistence backend.&lt;/p&gt;
&lt;p&gt;Version 0.7.0 introduced &lt;strong&gt;57 reusable contract tests&lt;/strong&gt; covering users, password hashes, verification tokens, password reset tokens, sessions, roles, permissions, relationships and transaction behavior.&lt;/p&gt;
&lt;p&gt;This changes the meaning of an adapter.&lt;/p&gt;
&lt;p&gt;A compatible &lt;code&gt;UserStore&lt;/code&gt; no longer just claims to implement an interface. It can demonstrate that it follows the behavioral semantics expected by the core.&lt;/p&gt;
&lt;p&gt;For example, the contract specifies that:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;usernames and email addresses are unique&lt;/li&gt;
&lt;li&gt;user creation and the initial verification token are atomic&lt;/li&gt;
&lt;li&gt;a new verification token replaces the previous one&lt;/li&gt;
&lt;li&gt;a new password reset token replaces the previous one&lt;/li&gt;
&lt;li&gt;deleting a user removes their sessions and related tokens&lt;/li&gt;
&lt;li&gt;repeated relationship assignments are idempotent&lt;/li&gt;
&lt;li&gt;deleting roles and permissions removes their assignments&lt;/li&gt;
&lt;li&gt;successful transactions commit&lt;/li&gt;
&lt;li&gt;failed transactions roll back&lt;/li&gt;
&lt;li&gt;nested transactions participate in the outer transaction&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These details are easy to overlook when implementing another backend, and they are exactly the type of differences that can produce authentication bugs which only appear much later.&lt;/p&gt;
&lt;p&gt;For me, this was an important step in the architecture: the abstraction is now described not only by Python protocols and documentation, but also by executable behavior.&lt;/p&gt;
&lt;h3 id=&quot;a-template-for-building-new-storage-adapters&quot;&gt;A template for building new storage adapters&lt;/h3&gt;
&lt;p&gt;To make that process easier, I also created &lt;code&gt;userharbor-inmemory&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;It is a minimal in-memory &lt;code&gt;UserStore&lt;/code&gt; implementation that passes the complete storage contract.&lt;/p&gt;
&lt;p&gt;It serves two purposes.&lt;/p&gt;
&lt;p&gt;First, it is useful for tests and examples where a real database is unnecessary.&lt;/p&gt;
&lt;p&gt;Second, the repository itself can be used as a template for creating new storage integrations. A developer can start with a working implementation and passing contract tests, replace the in-memory backend incrementally, and continuously verify that the new adapter still behaves correctly.&lt;/p&gt;
&lt;p&gt;The idea is that creating something like a future database adapter should mostly become:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;working UserStore template
        │
        ▼
replace persistence implementation
        │
        ▼
run shared contract tests
        │
        ▼
add backend-specific tests
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;instead of reverse-engineering the expected behavior from the core implementation.&lt;/p&gt;
&lt;h3 id=&quot;keeping-security-behavior-inside-the-core&quot;&gt;Keeping security behavior inside the core&lt;/h3&gt;
&lt;p&gt;Another important architectural boundary is that storage adapters never receive raw tokens for persistence.&lt;/p&gt;
&lt;p&gt;UserHarbor generates the raw verification, reset and session tokens, but hashes them before passing them to &lt;code&gt;UserStore&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The raw token is given only to the part of the application that needs it — for example, to an email sender or to the user after login.&lt;/p&gt;
&lt;p&gt;The database stores the hash.&lt;/p&gt;
&lt;p&gt;The core also owns behavior such as token expiration and session validation.&lt;/p&gt;
&lt;p&gt;Sensitive account-discovery flows are designed to return neutral responses where appropriate. For example, requesting a password reset for an unknown email address does not reveal whether that account exists.&lt;/p&gt;
&lt;p&gt;There are also account lifecycle notifications for events such as:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;successful email verification&lt;/li&gt;
&lt;li&gt;password changes&lt;/li&gt;
&lt;li&gt;password resets&lt;/li&gt;
&lt;li&gt;account deletion&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The &lt;code&gt;EmailSender&lt;/code&gt; interface decides how those messages are delivered, but it does not decide when the operation is valid. That decision remains in the core.&lt;/p&gt;
&lt;h3 id=&quot;sqlalchemy-without-owning-your-application&quot;&gt;SQLAlchemy without owning your application&lt;/h3&gt;
&lt;p&gt;The official SQLAlchemy adapter is useful out of the box, but one design requirement was that adopting UserHarbor should not force an application to adopt a completely separate user model.&lt;/p&gt;
&lt;p&gt;By default, the adapter can manage its own user table.&lt;/p&gt;
&lt;p&gt;But applications that already have a SQLAlchemy model can provide it instead:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;store = SQLAlchemyUserStore(
    SessionLocal,
    user_model=AppUser,
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Applications can also map their model to a richer public user object instead of being limited to UserHarbor's minimal representation.&lt;/p&gt;
&lt;p&gt;The documentation now also covers using the adapter with Alembic migrations rather than relying on &lt;code&gt;metadata.create_all()&lt;/code&gt; at application startup.&lt;/p&gt;
&lt;p&gt;That distinction matters because examples should be easy to run, but real applications need a sensible path toward managing schema changes properly.&lt;/p&gt;
&lt;h3 id=&quot;installation&quot;&gt;Installation&lt;/h3&gt;
&lt;p&gt;The core can be installed on its own:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;pip install userharbor
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Or with selected official integrations:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;pip install &quot;userharbor[sqlalchemy,smtp,fastapi]&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For experimenting with the complete official stack:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;pip install &quot;userharbor[all]&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The main official integrations currently include SQLAlchemy storage, SMTP email delivery and FastAPI support.&lt;/p&gt;
&lt;h3 id=&quot;what-i-deliberately-do-not-want-userharbor-to-become&quot;&gt;What I deliberately do not want UserHarbor to become&lt;/h3&gt;
&lt;p&gt;Feature creep is an easy trap for this kind of project.&lt;/p&gt;
&lt;p&gt;Once you have authentication, it is tempting to add OAuth. Then social login. Then MFA. Organizations. Teams. ACLs. Resource ownership. Admin panels. A policy language.&lt;/p&gt;
&lt;p&gt;Eventually the &quot;small authentication library&quot; becomes an application framework.&lt;/p&gt;
&lt;p&gt;That is specifically what I am trying to avoid.&lt;/p&gt;
&lt;p&gt;The core should remain focused on common account-management primitives.&lt;/p&gt;
&lt;p&gt;More specialized functionality can exist as integrations or separate libraries if there is demand for it, but it should not make the basic package more complicated for everyone else.&lt;/p&gt;
&lt;p&gt;One of the design principles of UserHarbor is therefore deliberately boring:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;stability is more important than feature count.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Once the public API stabilizes, I would rather spend development effort on security, reliability, compatibility and performance than continuously expand the scope of the core.&lt;/p&gt;
&lt;h3 id=&quot;current-status&quot;&gt;Current status&lt;/h3&gt;
&lt;p&gt;UserHarbor is currently at version &lt;strong&gt;0.7.0&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;The project is still young, and I do not consider the API stable or the library ready for production use yet.&lt;/p&gt;
&lt;p&gt;At this stage, the project is as much about validating the architecture as adding functionality.&lt;/p&gt;
&lt;p&gt;The areas I am particularly interested in getting feedback on are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;the boundary between the core and integrations&lt;/li&gt;
&lt;li&gt;the &lt;code&gt;UserStore&lt;/code&gt; contract&lt;/li&gt;
&lt;li&gt;the contract-testing approach&lt;/li&gt;
&lt;li&gt;the FastAPI integration&lt;/li&gt;
&lt;li&gt;custom storage backends&lt;/li&gt;
&lt;li&gt;the shape of the public API&lt;/li&gt;
&lt;li&gt;what should — and should not — belong in the core&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;There are plenty of good framework-specific authentication libraries in Python.&lt;/p&gt;
&lt;p&gt;UserHarbor is exploring a slightly different question:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Can the account-management domain itself be reusable across frameworks and infrastructure, while integrations remain small, replaceable and independently testable?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;So far, I think that boundary is turning out to be the most interesting part of the project.&lt;/p&gt;
&lt;h3 id=&quot;links&quot;&gt;Links&lt;/h3&gt;
&lt;p&gt;Documentation:&lt;br&gt;
&lt;a rel=&quot;nofollow&quot; href=&quot;https://userharbor.github.io&quot;&gt;https://userharbor.github.io&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Repository:&lt;br&gt;
&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/userharbor/userharbor&quot;&gt;https://github.com/userharbor/userharbor&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;FastAPI integration:&lt;br&gt;
&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/userharbor/userharbor-fastapi&quot;&gt;https://github.com/userharbor/userharbor-fastapi&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;SQLAlchemy adapter:&lt;br&gt;
&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/userharbor/userharbor-sqlalchemy&quot;&gt;https://github.com/userharbor/userharbor-sqlalchemy&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;SMTP adapter:&lt;br&gt;
&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/userharbor/userharbor-smtp&quot;&gt;https://github.com/userharbor/userharbor-smtp&lt;/a&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/24763/building-userharbor-framework-agnostic-user-management-for-python</guid>
<pubDate>Mon, 17 Aug 2026 12:31:04 +0000</pubDate>
</item>
<item>
<title>Looking for contributors to help debug an AI-powered Network Traffic Analyzer (FastAPI + Scapy)</title>
<link>https://coderlegion.com/22884/looking-for-contributors-to-help-debug-an-powered-network-traffic-analyzer-fastapi-scapy</link>
<description>&lt;p&gt;Hi everyone,&lt;/p&gt;
&lt;p&gt;I've been building &lt;strong&gt;NetGuard&lt;/strong&gt;, an open-source AI-powered Network Traffic Analyzer and Active Defense Platform built with &lt;strong&gt;FastAPI&lt;/strong&gt;, &lt;strong&gt;Scapy&lt;/strong&gt;, &lt;strong&gt;SQLite&lt;/strong&gt;, and a &lt;strong&gt;Random Forest&lt;/strong&gt; model trained on the NSL-KDD dataset.&lt;/p&gt;
&lt;p&gt;The project currently includes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Live packet capture&lt;/li&gt;
&lt;li&gt;Rule-based IDS&lt;/li&gt;
&lt;li&gt;Traffic visualization dashboard&lt;/li&gt;
&lt;li&gt;AI traffic classification&lt;/li&gt;
&lt;li&gt;Automatic IP blocking using iptables&lt;/li&gt;
&lt;li&gt;Threat remediation recommendations&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Most of the architecture is working, but I'm currently stuck on a few integration issues.&lt;/p&gt;
&lt;p&gt;The biggest one:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The orchestrator successfully receives &lt;strong&gt;HTTP 200 OK&lt;/strong&gt; from the classifier, but &lt;code&gt;ai_result&lt;/code&gt; is always &lt;code&gt;null&lt;/code&gt;. I suspect a payload mismatch or response parsing issue inside &lt;code&gt;call_classifier()&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;There are also a couple of other issues involving SQLite alert persistence and PDF report generation.&lt;/p&gt;
&lt;p&gt;I've documented everything in the README, including reproduction steps and setup instructions.&lt;/p&gt;
&lt;p&gt;GitHub:&lt;br&gt;
&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/GulrezQayyum/netguard&quot;&gt;https://github.com/GulrezQayyum/netguard&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;If anyone enjoys debugging FastAPI, async Python, networking, or ML integrations, I'd really appreciate your feedback or pull requests.&lt;/p&gt;
&lt;p&gt;Thanks!&lt;/p&gt;
</description>
<category>Discussions</category>
<guid isPermaLink="true">https://coderlegion.com/22884/looking-for-contributors-to-help-debug-an-powered-network-traffic-analyzer-fastapi-scapy</guid>
<pubDate>Fri, 17 Jul 2026 12:35:07 +0000</pubDate>
</item>
<item>
<title>I Build a blog API with Redis - Here's every problem I Hit</title>
<link>https://coderlegion.com/22689/i-build-a-blog-api-with-redis-heres-every-problem-i-hit</link>
<description>&lt;p&gt;I build a blog API with FastAPI + Redis + MySQL.&lt;br&gt;
Three cache problems almost killed my app.&lt;br&gt;
If you also face these problems!&lt;br&gt;
Here's how I fixed each one.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;1-cache-penetration&quot;&gt;1. Cache Penetration&lt;/h2&gt;
&lt;h3 id=&quot;what-s-cache-penetration-&quot;&gt;What's Cache Penetration?&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Cache Penetration&lt;/strong&gt; refers to a scenario where the requested data exists neither in cache nor in the database,causing every request to hit the database directly,thereby increasing the database load.&lt;/p&gt;
&lt;h3 id=&quot;how-to-solve-&quot;&gt;How to solve?&lt;/h3&gt;
&lt;h4 id=&quot;1-caching-null-values&quot;&gt;1.Caching null values&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;Steps:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;When a queried key exists in neither the cache nor the database,the result (such as &lt;code&gt;null&lt;/code&gt; or an empty value) is cached with a short TTL.&lt;/li&gt;
&lt;li&gt;This way,subsequent identical requests can be served directly from the cache,avoiding frequent database access.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Example:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;...
redis = get_redis()
data = redis.get(cache_key)

# First, check the cache. If the cache entry exists, check whether it is __NULL__
if data is not None:
    if data == &quot;__NULL__&quot;:
        return {&quot;data&quot;: None, &quot;source&quot;: &quot;cache&quot;}
    return {&quot;data&quot;: data, &quot;source&quot;: &quot;cache&quot;}

# Second, cache miss, query database
value = query_database(cache_key)

# Third, cache the result whether it exists in DB or not
if value is not None:
    redis.setex(cache_key, 60, value)          # cache real data
else:
    redis.setex(cache_key, 10, &quot;__NULL__&quot;)     # cache null sentinel (short TTL)

return value

&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2 id=&quot;2-cache-breakdown&quot;&gt;2.Cache breakdown&lt;/h2&gt;
&lt;h3 id=&quot;what-s-cache-breakdown-&quot;&gt;What's Cache breakdown?&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Cache breakdown&lt;/strong&gt;occurs when a popular data entry expires in the cache while a massive number of concurrent requests are hitting that same data at the exact same time.All those requests penetrate straight through to the database,putting enormous pressure on it and potentially causing system performance issues.&lt;/p&gt;
&lt;h3 id=&quot;how-to-solve--1&quot;&gt;How to solve?&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Lock mechanism:&lt;/strong&gt;When the cache expires,use a locking mechanism to ensure that only one thread can access the database and update the cache.The other threads wait util the cache is rebuilt before reading the data.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Example:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# Generate a lock key based on the given key
lock_key = f&quot;mutex:{key}&quot;

# Use SETNX operation to acquire the lock. If the key does not exist, set it with an expiration time of 300 seconds.
# This prevents concurrent access.
lock_acquired = redis_client.set(lock_key, &quot;1&quot;, nx=True, ex=300)

if lock_acquired:
    try:
        # Successfully acquired the lock, query the database
        value = db_query_func(key)
        if value is not None:
            # Write the result to the cache with an expiration time of 3600 seconds
            redis_client.setex(key, 3600, value)
        return value
    finally:
        # Release the lock
        redis_client.delete(lock_key)
else:
    # Failed to acquire the lock, sleep for 0.1 seconds and then retry
    time.sleep(0.1)
    return get_data_with_mutex(key, redis_client, db_query_func)

&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2 id=&quot;3-cache-avalanche&quot;&gt;3.Cache avalanche&lt;/h2&gt;
&lt;h3 id=&quot;what-s-cache-avalanche-&quot;&gt;What's cache avalanche?&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Cache avalance&lt;/strong&gt;refers to a situation where a large amount of cached data expires at the same time or the cache service goes down. As a result, all requests are directly sent to the database, causing a sudden surge in the database's pressure and even leading to its downtime.&lt;/p&gt;
&lt;h3 id=&quot;how-to-solve--2&quot;&gt;How to solve?&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Randomized TTL&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The core idea is to avoid a large number of keys expiring simultaneously.&lt;/li&gt;
&lt;li&gt;When setting the expiration time for the cache, add a random value.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Example:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import random

expire_time = time + random.randint(0,300)
redis.set(key,value,ex=expire_time)
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;what-about-you-&quot;&gt;What about you?&lt;/h3&gt;
&lt;p&gt;Have you run into any of these cache problems in your projects? &lt;br&gt;
Or do you use a different strategy? Let me know in the comments &lt;/p&gt;
&lt;p&gt;If this was helpful, follow me for more backend deep dives.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/22689/i-build-a-blog-api-with-redis-heres-every-problem-i-hit</guid>
<pubDate>Wed, 15 Jul 2026 00:51:31 +0000</pubDate>
</item>
<item>
<title>Hardening Two Multi Tenant SaaS APIs:</title>
<link>https://coderlegion.com/19779/hardening-two-multi-tenant-saas-apis</link>
<description>&lt;h2 id=&quot;hardening-two-multi-tenant-saas-apis&quot;&gt;Hardening Two Multi Tenant SaaS APIs&lt;/h2&gt;
&lt;h3 id=&quot;what-we-found-what-we-fixed-and-what-changed&quot;&gt;What We Found, What We Fixed, and What Changed&lt;/h3&gt;
&lt;p&gt;Security hardening is not glamorous work.&lt;/p&gt;
&lt;p&gt;Most of it is careful reading, careful verification, uncomfortable edge cases, and refusing to trust assumptions that have quietly become part of the system.&lt;/p&gt;
&lt;p&gt;Recently, I completed a remediation pass across two multi tenant SaaS products: Site2CRM and Made4Founders. Both products had grown into real platforms with authenticated dashboards, public webhooks, billing flows, CRM integrations, OAuth style connections, file uploads, background jobs, and customer owned data.&lt;/p&gt;
&lt;p&gt;That kind of product has a wide attack surface.&lt;/p&gt;
&lt;p&gt;The work started with security reports and ended with two hardened branches, 24 total commits, more than 140 new regression tests, database migrations, centralized security utilities, audit logging, startup gates, and CI checks that now block the same classes of mistakes from coming back.&lt;/p&gt;
&lt;p&gt;This article is a breakdown of what we found, how we fixed it, and the engineering lessons that came out of the process.&lt;/p&gt;
&lt;h3 id=&quot;the-goal-was-not-just-to-close-findings&quot;&gt;The Goal Was Not Just to Close Findings&lt;/h3&gt;
&lt;p&gt;A security report usually arrives as a list of issues.&lt;/p&gt;
&lt;p&gt;That can make the work feel transactional.&lt;/p&gt;
&lt;p&gt;Fix this endpoint.&lt;/p&gt;
&lt;p&gt;Add this check.&lt;/p&gt;
&lt;p&gt;Reject this payload.&lt;/p&gt;
&lt;p&gt;Patch this route.&lt;/p&gt;
&lt;p&gt;That is necessary, but it is not enough.&lt;/p&gt;
&lt;p&gt;The real question is:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What class of mistake allowed this bug to exist?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;That question changed the remediation strategy.&lt;/p&gt;
&lt;p&gt;For every finding, the goal became clear:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Fix the specific issue.&lt;/li&gt;
&lt;li&gt;Add a regression test for that finding.&lt;/li&gt;
&lt;li&gt;Centralize the security pattern where possible.&lt;/li&gt;
&lt;li&gt;Add a guard so the same mistake is harder to reintroduce.&lt;/li&gt;
&lt;li&gt;Document any deployment or data migration steps clearly.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;That approach turned the work from a cleanup pass into a hardening pass.&lt;/p&gt;
&lt;h3 id=&quot;finding-1-tenant-data-must-always-be-scoped-by-tenant&quot;&gt;Finding 1: Tenant Data Must Always Be Scoped by Tenant&lt;/h3&gt;
&lt;p&gt;In a multi tenant application, the most important security rule is simple:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;A customer should only be able to access data that belongs to their organization.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;That rule sounds obvious, but enforcing it consistently is where systems get tested.&lt;/p&gt;
&lt;p&gt;A risky query often looks harmless:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;lead = db.query(Lead).filter(Lead.id == lead_id).first()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The problem is that &lt;code&gt;lead_id&lt;/code&gt; alone is not a tenant boundary.&lt;/p&gt;
&lt;p&gt;In a multi tenant system, the query needs to prove both identity and ownership:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;lead = (
    db.query(Lead)
    .filter(
        Lead.id == lead_id,
        Lead.organization_id == current_user.organization_id,
    )
    .first()
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The same principle applies to updates, deletes, dashboard feeds, background jobs, calendar items, configuration records, and integration data.&lt;/p&gt;
&lt;p&gt;In one case, a calendar feed was using a join path that could leak data across tenants. In another, a vault configuration model had rows that were not safely attached to an organization. Both issues were fixed by making organization ownership explicit, adding migrations where needed, and creating regression coverage around the exact failure modes.&lt;/p&gt;
&lt;p&gt;The broader lesson was this:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Tenant isolation should not depend on developer memory.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;It should be a pattern the codebase makes easy and the test suite actively defends.&lt;/p&gt;
&lt;h3 id=&quot;finding-2-organization-admins-are-not-platform-admins&quot;&gt;Finding 2: Organization Admins Are Not Platform Admins&lt;/h3&gt;
&lt;p&gt;One of the clearest authorization lessons came from a global resource protected by a tenant level role check.&lt;/p&gt;
&lt;p&gt;Most SaaS products have organization roles like:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;OWNER
ADMIN
USER
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Those roles are useful inside a customer account. An organization owner should be able to manage users, settings, forms, integrations, and billing details for that organization.&lt;/p&gt;
&lt;p&gt;But tenant authority is not platform authority.&lt;/p&gt;
&lt;p&gt;A customer &lt;code&gt;OWNER&lt;/code&gt; should not be able to manage global platform resources, issue marketplace codes, access internal tools, or make changes that affect other organizations.&lt;/p&gt;
&lt;p&gt;The risky pattern was conceptually simple:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;if current_user.role not in [&quot;OWNER&quot;, &quot;ADMIN&quot;]:
    raise HTTPException(status_code=403)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That check answers the wrong question.&lt;/p&gt;
&lt;p&gt;It asks:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Is this user powerful inside their own organization?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;For global platform actions, the application needs to ask a different question:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Is this user actually trusted to operate the platform itself?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The fix was to introduce a real platform staff boundary and move global operations behind that boundary:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;require_platform_staff(current_user)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This was not just a one line fix. Tests were added to prove that tenant owners and tenant admins could not access platform scoped functionality. A static guard was also added so future global table routes cannot quietly be protected only by tenant roles.&lt;/p&gt;
&lt;p&gt;The lesson:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Never reuse customer roles for platform administration.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;They represent different trust models.&lt;/p&gt;
&lt;h3 id=&quot;finding-3-public-webhooks-are-public-not-trusted&quot;&gt;Finding 3: Public Webhooks Are Public, Not Trusted&lt;/h3&gt;
&lt;p&gt;Public webhook endpoints need to be reachable by external providers.&lt;/p&gt;
&lt;p&gt;That does not mean they should trust incoming requests.&lt;/p&gt;
&lt;p&gt;Several webhook surfaces needed stronger sender verification. The risk was not that the endpoints existed. The risk was that state changing payloads could be processed without proving they came from the provider they claimed to represent.&lt;/p&gt;
&lt;p&gt;The corrected model is straightforward:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Read the raw request body.&lt;/li&gt;
&lt;li&gt;Verify the provider signature.&lt;/li&gt;
&lt;li&gt;Reject missing or invalid signatures.&lt;/li&gt;
&lt;li&gt;Parse the payload only after verification.&lt;/li&gt;
&lt;li&gt;Apply idempotency where relevant.&lt;/li&gt;
&lt;li&gt;Mutate state.&lt;/li&gt;
&lt;li&gt;Return success.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This mattered for provider events such as inbound messaging events, email bounce and complaint notifications, and billing lifecycle events.&lt;/p&gt;
&lt;p&gt;Billing webhooks received extra attention because they can change plan state. A forged billing event can potentially activate, cancel, downgrade, or otherwise alter customer access.&lt;/p&gt;
&lt;p&gt;The fix was to make verification mandatory. If the provider webhook secret or webhook ID is missing in production, the app should fail closed. If the signature is invalid, the route should reject the request instead of logging the error and continuing.&lt;/p&gt;
&lt;p&gt;The lesson:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;A webhook endpoint can be public without being trusted.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Reachability and trust are separate things.&lt;/p&gt;
&lt;h3 id=&quot;finding-4-external-install-identifiers-are-not-authentication&quot;&gt;Finding 4: External Install Identifiers Are Not Authentication&lt;/h3&gt;
&lt;p&gt;One integration trusted a client supplied install identifier as if it were a credential.&lt;/p&gt;
&lt;p&gt;That is dangerous.&lt;/p&gt;
&lt;p&gt;Install IDs, instance IDs, account IDs, and external resource IDs are identifiers. They are not proof that the caller owns the installation.&lt;/p&gt;
&lt;p&gt;The hardened approach was to require a signed provider token, verify it server side, and only then extract the canonical installation identity from the verified payload.&lt;/p&gt;
&lt;p&gt;The corrected flow became clear:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Receive signed provider instance token.&lt;/li&gt;
&lt;li&gt;Verify the token using the provider app secret.&lt;/li&gt;
&lt;li&gt;Extract the canonical instance ID from the verified payload.&lt;/li&gt;
&lt;li&gt;Resolve the install record from that verified identity.&lt;/li&gt;
&lt;li&gt;Refuse cross organization rebinding unless ownership is proven.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This fixed both the authentication issue and the related install rebinding risk.&lt;/p&gt;
&lt;p&gt;The lesson:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Do not authenticate integrations with bare IDs.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Use provider signed assertions, server side verification, and explicit ownership rules.&lt;/p&gt;
&lt;h3 id=&quot;finding-5-user-controlled-urls-need-ssrf-protection&quot;&gt;Finding 5: User Controlled URLs Need SSRF Protection&lt;/h3&gt;
&lt;p&gt;Several features across the two products involved server side requests to external URLs.&lt;/p&gt;
&lt;p&gt;That pattern is common:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Outbound webhooks
RSS imports
Social integrations
Callback URLs
Remote media fetches
Health checks
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The security risk is SSRF, or Server Side Request Forgery.&lt;/p&gt;
&lt;p&gt;SSRF occurs when a user can influence a URL that the server requests. Without guardrails, an attacker may be able to point the server at internal infrastructure.&lt;/p&gt;
&lt;p&gt;Examples include localhost, private network addresses, cloud metadata services, link local addresses, and internal admin panels.&lt;/p&gt;
&lt;p&gt;The fix was to centralize outbound fetching through a safe fetch utility.&lt;/p&gt;
&lt;p&gt;That utility blocks dangerous destinations, applies timeouts, restricts redirects, limits response size, and prevents internal response bodies from being reflected back to users.&lt;/p&gt;
&lt;p&gt;The important part was centralization.&lt;/p&gt;
&lt;p&gt;Instead of asking every route to remember SSRF rules, risky outbound requests now go through one safer path.&lt;/p&gt;
&lt;p&gt;The lesson:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Any feature that lets a user provide a URL should be treated as a network boundary.&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id=&quot;finding-6-cryptography-should-not-have-production-fallbacks&quot;&gt;Finding 6: Cryptography Should Not Have Production Fallbacks&lt;/h3&gt;
&lt;p&gt;Development conveniences can become production vulnerabilities if they are allowed to survive past local use.&lt;/p&gt;
&lt;p&gt;The hardening pass removed weak secret fallbacks and hardcoded encryption key fallbacks. Token encryption was versioned, and a re encryption path was added so legacy stored values could be migrated safely.&lt;/p&gt;
&lt;p&gt;The improved model included several important rules:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;No hardcoded production fallback keys.&lt;/li&gt;
&lt;li&gt;An explicit application encryption key requirement.&lt;/li&gt;
&lt;li&gt;A versioned encrypted token format.&lt;/li&gt;
&lt;li&gt;A migration script for legacy tokens.&lt;/li&gt;
&lt;li&gt;A production flag to reject legacy plaintext tokens after migration.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The lesson:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Cryptographic failure should be loud.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;If the key is missing in production, the app should not quietly invent one.&lt;/p&gt;
&lt;h3 id=&quot;finding-7-file-uploads-should-stay-boring&quot;&gt;Finding 7: File Uploads Should Stay Boring&lt;/h3&gt;
&lt;p&gt;File uploads are easy to underestimate.&lt;/p&gt;
&lt;p&gt;For brand logos and media uploads, the safest approach was to restrict formats and improve storage behavior.&lt;/p&gt;
&lt;p&gt;The hardening changes included rejecting risky file types where they were unnecessary, using high entropy media names, enforcing body size limits, handling chunked uploads safely, and avoiding active content in logo uploads.&lt;/p&gt;
&lt;p&gt;One practical example was SVG.&lt;/p&gt;
&lt;p&gt;SVG can be useful, but it can also contain active content. If served from the wrong origin or with weak headers, it can become a stored XSS risk.&lt;/p&gt;
&lt;p&gt;XSS means Cross Site Scripting. It occurs when attacker controlled content executes JavaScript in a trusted browser context.&lt;/p&gt;
&lt;p&gt;For a logo upload feature, SVG was not worth the additional risk.&lt;/p&gt;
&lt;p&gt;The lesson:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;If a product only needs images, do not accept formats that behave like documents or code.&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id=&quot;finding-8-security-relevant-configuration-should-fail-closed&quot;&gt;Finding 8: Security Relevant Configuration Should Fail Closed&lt;/h3&gt;
&lt;p&gt;Some risks were not about code paths. They were about missing configuration.&lt;/p&gt;
&lt;p&gt;Examples included missing webhook secrets, missing encryption keys, missing CAPTCHA secrets, missing Redis for rate limiting, weak application secret keys, and insecure production cookie settings.&lt;/p&gt;
&lt;p&gt;The fix was to add production startup gates.&lt;/p&gt;
&lt;p&gt;In development, flexible configuration is helpful.&lt;/p&gt;
&lt;p&gt;In production, missing security configuration should stop the app from starting.&lt;/p&gt;
&lt;p&gt;A startup gate turns a hidden runtime weakness into an obvious deployment failure.&lt;/p&gt;
&lt;p&gt;The lesson:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Failing to boot is better than booting insecurely.&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id=&quot;finding-9-rate-limiting-should-not-silently-degrade&quot;&gt;Finding 9: Rate Limiting Should Not Silently Degrade&lt;/h3&gt;
&lt;p&gt;Rate limiting is often treated as a nice to have, but for authentication and abuse prevention it is a security control.&lt;/p&gt;
&lt;p&gt;If Redis is unavailable and the system silently falls back to per process memory, limits become weaker under multiple workers.&lt;/p&gt;
&lt;p&gt;For example, a limit of 10 attempts may effectively become 40 attempts across four workers.&lt;/p&gt;
&lt;p&gt;The production behavior was hardened so that security sensitive rate limiting depends on a real shared backend.&lt;/p&gt;
&lt;p&gt;The lesson:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;A degraded security control should be visible, not silent.&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id=&quot;finding-10-regression-tests-are-part-of-the-fix&quot;&gt;Finding 10: Regression Tests Are Part of the Fix&lt;/h3&gt;
&lt;p&gt;Every meaningful finding received a named regression test.&lt;/p&gt;
&lt;p&gt;That mattered.&lt;/p&gt;
&lt;p&gt;A test named after a security issue tells future maintainers why the behavior exists. It also prevents a fix from being accidentally removed during a refactor.&lt;/p&gt;
&lt;p&gt;Examples of the test coverage included:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Tenant data cannot be accessed across organizations.&lt;/li&gt;
&lt;li&gt;Platform scoped routes require platform staff.&lt;/li&gt;
&lt;li&gt;Unsigned webhooks are rejected.&lt;/li&gt;
&lt;li&gt;Invalid webhook signatures are rejected.&lt;/li&gt;
&lt;li&gt;Unsafe webhook URLs are rejected.&lt;/li&gt;
&lt;li&gt;SVG logo uploads are rejected.&lt;/li&gt;
&lt;li&gt;Weak production secrets fail startup.&lt;/li&gt;
&lt;li&gt;Legacy plaintext tokens can be rejected after migration.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The lesson:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;If a security issue was important enough to fix, it is important enough to test.&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id=&quot;finding-11-static-guards-catch-what-tests-miss&quot;&gt;Finding 11: Static Guards Catch What Tests Miss&lt;/h3&gt;
&lt;p&gt;Tests are excellent for specific behavior.&lt;/p&gt;
&lt;p&gt;Static lint is better for broad architectural patterns.&lt;/p&gt;
&lt;p&gt;Both repositories now have security lint guards for risky patterns:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Tenant owned queries without organization scope.&lt;/li&gt;
&lt;li&gt;Raw outbound requests using user controlled URLs.&lt;/li&gt;
&lt;li&gt;State changing public routes without auth or signature classification.&lt;/li&gt;
&lt;li&gt;Global platform routes protected only by tenant roles.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The linter supports a baseline, which is important for mature codebases.&lt;/p&gt;
&lt;p&gt;A baseline allows known accepted cases to remain documented while CI fails only on new violations. That keeps the guard practical instead of noisy.&lt;/p&gt;
&lt;p&gt;The preferred workflow is simple:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Fix the violation.&lt;/li&gt;
&lt;li&gt;Add a clearly marked exception only when intentional.&lt;/li&gt;
&lt;li&gt;Update the baseline only when the exception is reviewed and accepted.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The lesson:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The best time to add a guard is right after fixing the bug class.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;That is when the pattern is fresh, the risk is understood, and the team knows what should never happen again.&lt;/p&gt;
&lt;h3 id=&quot;finding-12-some-fixes-require-operational-discipline&quot;&gt;Finding 12: Some Fixes Require Operational Discipline&lt;/h3&gt;
&lt;p&gt;Not every security fix ends with a commit.&lt;/p&gt;
&lt;p&gt;Some work has to happen during deployment.&lt;/p&gt;
&lt;p&gt;Examples include coordinating breaking integration changes, setting required production secrets, running database migrations, inspecting quarantined records, re encrypting stored tokens, enabling flags that reject legacy formats, scrubbing sensitive files from Git history, and running full test suites before release.&lt;/p&gt;
&lt;p&gt;These steps were intentionally left as human controlled actions because they affect production data, integration behavior, and deployment timing.&lt;/p&gt;
&lt;p&gt;That is part of responsible hardening.&lt;/p&gt;
&lt;p&gt;The code can be ready before production is ready.&lt;/p&gt;
&lt;p&gt;The lesson:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;A remediation branch is not deployable until the operational checklist is complete.&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id=&quot;what-changed-by-the-end&quot;&gt;What Changed by the End&lt;/h3&gt;
&lt;p&gt;Across both products, the hardening pass produced:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;24 total commits.&lt;/li&gt;
&lt;li&gt;More than 140 new regression tests.&lt;/li&gt;
&lt;li&gt;Database migrations.&lt;/li&gt;
&lt;li&gt;Centralized webhook verification.&lt;/li&gt;
&lt;li&gt;Centralized SSRF safe fetch behavior.&lt;/li&gt;
&lt;li&gt;Fail closed production startup gates.&lt;/li&gt;
&lt;li&gt;Platform staff authorization.&lt;/li&gt;
&lt;li&gt;Audit logging for sensitive actions.&lt;/li&gt;
&lt;li&gt;Versioned token encryption.&lt;/li&gt;
&lt;li&gt;Tenant isolation checks.&lt;/li&gt;
&lt;li&gt;Security lint guards enforced in CI.&lt;/li&gt;
&lt;li&gt;Main branches left untouched for review.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;More importantly, the products now have better security shape.&lt;/p&gt;
&lt;p&gt;The fixes were not just scattered patches. They became reusable boundaries.&lt;/p&gt;
&lt;p&gt;Tenant data access now has stronger conventions.&lt;/p&gt;
&lt;p&gt;Platform operations now have a separate trust model.&lt;/p&gt;
&lt;p&gt;Webhook verification now follows a consistent pattern.&lt;/p&gt;
&lt;p&gt;Outbound URL fetching now has a safer path.&lt;/p&gt;
&lt;p&gt;Production misconfiguration now fails early.&lt;/p&gt;
&lt;p&gt;Dangerous patterns now have CI visibility.&lt;/p&gt;
&lt;h3 id=&quot;final-takeaway&quot;&gt;Final Takeaway&lt;/h3&gt;
&lt;p&gt;The most valuable security work is not only fixing what was found.&lt;/p&gt;
&lt;p&gt;It is asking what the finding reveals about the system.&lt;/p&gt;
&lt;p&gt;A good remediation process should answer several questions:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;What failed?&lt;/li&gt;
&lt;li&gt;Where else could it fail?&lt;/li&gt;
&lt;li&gt;What is the correct shared pattern?&lt;/li&gt;
&lt;li&gt;How do we test the fix?&lt;/li&gt;
&lt;li&gt;How do we prevent the class from returning?&lt;/li&gt;
&lt;li&gt;What must be true before deployment?&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;That mindset turns a security report into a stronger codebase.&lt;/p&gt;
&lt;p&gt;Patching closes issues.&lt;/p&gt;
&lt;p&gt;Hardening changes the system.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/19779/hardening-two-multi-tenant-saas-apis</guid>
<pubDate>Fri, 05 Jun 2026 18:24:16 +0000</pubDate>
</item>
<item>
<title>Building an AI Product Backend From Scratch: FastAPI, Postgres, pgvector, Stripe, and What I'd Do Di</title>
<link>https://coderlegion.com/18181/building-an-ai-product-backend-from-scratch-fastapi-postgres-pgvector-stripe-and-what-id</link>
<description>&lt;p&gt;A couple of years ago I was hired as the solo backend engineer to build the production backend for an AI product company from scratch. No legacy code, no existing schema, no team to defer to — just a frontend team waiting on an API and a product team waiting on inference. This post is about the architecture decisions I made, what worked, and what I'd do differently if I were starting today.&lt;br&gt;
The shape of the problem&lt;br&gt;
The product needed five things working together:&lt;/p&gt;
&lt;p&gt;A typed API for the frontend&lt;br&gt;
LLM inference with reasonable UX (streaming, cost controls, retries)&lt;br&gt;
Vector search over user-supplied content&lt;br&gt;
Authentication and user management&lt;br&gt;
Subscription billing&lt;/p&gt;
&lt;p&gt;Standard stuff individually. The interesting part was keeping the operational footprint small enough that one person could run the whole thing without it falling over.&lt;br&gt;
Why FastAPI + Postgres + pgvector&lt;br&gt;
The temptation when building an AI product is to reach for a Lambda-and-microservices architecture from day one. I went the other direction: one FastAPI service, one Postgres database, pgvector for embeddings inside that same Postgres.&lt;br&gt;
The reasoning:&lt;/p&gt;
&lt;p&gt;One database, one source of truth. User data, subscription state, and vector embeddings live in the same Postgres. No sync issues between Postgres and a separate vector DB. Backups are one operation. Migrations are one tool (Alembic).&lt;br&gt;
FastAPI's typing pays for itself. Pydantic schemas become the contract with the frontend. OpenAPI generation is free. Async support means LLM calls and external APIs don't block the event loop.&lt;br&gt;
One service is faster to ship and easier to debug. When something breaks at 2am, you don't have to figure out which of seven services owns the bug.&lt;/p&gt;
&lt;p&gt;The cost: at very high scale (millions of users, billions of embeddings), pgvector and a monolithic FastAPI service would need rethinking. For an early-stage product, this was the right tradeoff.&lt;br&gt;
LLM integration: the part people underestimate&lt;br&gt;
Connecting to an LLM API is one line of code. Making it production-grade is most of the engineering work. The pieces:&lt;/p&gt;
&lt;p&gt;Streaming responses. Users will not wait 15 seconds for a wall of text. Server-sent events from FastAPI through to the frontend, with proper backpressure handling.&lt;br&gt;
Token accounting per user. Every request logs prompt tokens, completion tokens, and cost. Without this you have no idea who's burning your budget.&lt;br&gt;
Soft caps + fallback models. Hitting a per-user daily limit doesn't return an error — it switches to a cheaper model and continues. Users get a worse experience, not a broken one.&lt;br&gt;
Retry with backoff that respects rate limits. Upstream providers throttle. Naive retry loops make it worse. Exponential backoff plus reading the Retry-After header is the minimum.&lt;br&gt;
Prompt templates separated from application code. Templates live in their own module, versioned, with the application code only choosing which one to invoke. Changes to wording don't require touching business logic.&lt;/p&gt;
&lt;p&gt;Vector search inside Postgres&lt;br&gt;
pgvector is one of the more underrated decisions you can make. It's a Postgres extension that adds a vector type and similarity-search operators. You get nearest-neighbor queries with the same SQL you use for everything else, the same transaction guarantees, and the same backup story.&lt;br&gt;
The gotchas worth knowing:&lt;/p&gt;
&lt;p&gt;Indexes matter a lot. A naive sequential scan over a few hundred thousand vectors will be slow. IVFFlat or HNSW indexes are not optional past a certain table size.&lt;br&gt;
Chunking is where retrieval quality lives. Embedding a 50-page document as one vector is useless. Chunk strategy (size, overlap, semantic boundaries) affects results more than the embedding model choice.&lt;br&gt;
Re-embedding is expensive. When you change embedding models or chunking strategy, you re-embed everything. Design the ingestion pipeline so this isn't catastrophic.&lt;/p&gt;
&lt;p&gt;Subscription billing: webhooks are the product&lt;br&gt;
Stripe handles the payment UI and the regulatory complexity. Your job is to receive its webhooks and turn them into entitlements in your database.&lt;br&gt;
The non-negotiables:&lt;/p&gt;
&lt;p&gt;Webhooks must be idempotent. Stripe will deliver the same event twice. If your subscription state changes twice, you've granted access twice or revoked it incorrectly.&lt;br&gt;
Webhook signature verification on every request. Without it, anyone can POST a fake &quot;subscription.created&quot; event to your endpoint.&lt;br&gt;
Entitlements as a separate layer. Don't check Stripe's subscription status in every API handler. Maintain a local entitlements table updated by webhooks; check that. It's faster, more reliable, and survives Stripe outages.&lt;br&gt;
Reconciliation jobs. Webhooks fail. A nightly job that pulls the truth from Stripe and reconciles your local state catches the gaps before users notice.&lt;/p&gt;
&lt;p&gt;Auth: boring is correct&lt;br&gt;
Email/password with hashed credentials, OAuth for social sign-in, JWT for sessions with refresh tokens, role-based access control on routes. None of this is novel and none of it should be. The mistake people make on AI products is wanting auth to be clever. It shouldn't be. Pick well-tested libraries and move on.&lt;br&gt;
What I'd do differently today&lt;br&gt;
A few things have changed in the 2-3 years since:&lt;/p&gt;
&lt;p&gt;Structured outputs. Most LLM providers now support enforced JSON schema responses. When I built this, you parsed JSON out of completions and prayed. Today I'd use structured outputs everywhere they're supported and delete a lot of validation code.&lt;br&gt;
Streaming standards. The streaming UX patterns are much more mature now. Vercel AI SDK and similar libraries handle a lot of what I wrote by hand.&lt;br&gt;
Embedding model choices. The embedding landscape moved fast. I'd benchmark a few current options against the actual retrieval task instead of defaulting to OpenAI embeddings.&lt;br&gt;
Observability from day one. I added logging and error reporting, but I'd build proper LLM-aware observability (token usage, latency percentiles, prompt versions, retrieval quality) into the request path from the first commit.&lt;/p&gt;
&lt;p&gt;Takeaways&lt;br&gt;
If you're building an AI product backend:&lt;/p&gt;
&lt;p&gt;One service and one database will take you further than you think&lt;br&gt;
Most of the engineering work is around the LLM call, not the LLM call itself&lt;br&gt;
pgvector is a serious option if you're already on Postgres&lt;br&gt;
Webhook idempotency and entitlement layers will save you from a class of bugs that are hard to debug after the fact&lt;/p&gt;
&lt;p&gt;Happy to dig into any of these in the comments — especially if you've made different architectural choices and have war stories to share.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/18181/building-an-ai-product-backend-from-scratch-fastapi-postgres-pgvector-stripe-and-what-id</guid>
<pubDate>Mon, 18 May 2026 02:16:31 +0000</pubDate>
</item>
<item>
<title>The Executive Interface: Translating AI Telemetry into C-Level Decisions</title>
<link>https://coderlegion.com/18142/the-executive-interface-translating-ai-telemetry-into-c-level-decisions</link>
<description>&lt;p&gt;The most highly engineered artificial intelligence backend fails to secure financial viability if upper management lacks the mechanism to visualize its direct impact on profitability. Applied &lt;strong&gt;Operations Engineering&lt;/strong&gt; does not terminate inherently within a relational database architecture; the structural cycle invariably concludes on the decision-maker’s screen.&lt;/p&gt;
&lt;p&gt;The asynchronous orchestration model implemented in previous segments (CrewAI piloting an LLM securely against deterministic SQL Supabase arrays) yields robust operational execution. This concluding segment reinforces the deployment by exposing the mathematical core toward a managerial interface deliberately calibrated exclusively to communicate &lt;em&gt;Return on Investment (ROI)&lt;/em&gt; calculations and industrial downtime mitigation.&lt;/p&gt;
&lt;h4 id=&quot;the-standardized-data-contract-formulating-the-analytical-api&quot;&gt;The Standardized Data Contract: Formulating the Analytical API&lt;/h4&gt;
&lt;p&gt;In order to rigorously decouple the cognitive analysis load originating from the underlying agent engines, balancing execution layers evenly across the network, we introduce a simulated Read-Replica structure atop the FastAPI environment (&lt;code&gt;dashboard_api.py&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;This REST terminal strictly exposes mathematical telemetry archived implicitly by our evaluating Agent resolving inbound PDNs:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;@app.get(&quot;/api/v1/dashboard/risk-metrics&quot;)
def get_risk_metrics():
    # Enforces a static JSON contract for Frontend ingestion
    return {
        &quot;total_risk_eur&quot;: 15450.00,
        &quot;active_alerts&quot;: 2,
        &quot;affected_skus&quot;: [
            {
                &quot;sku&quot;: &quot;DRONE-X1&quot;,
                &quot;margin_at_risk&quot;: 12500.00,
                &quot;status&quot;: &quot;CRITICAL LTB REQUIRED&quot;,
                &quot;trigger_mpn&quot;: &quot;TI-CAP-10U-50&quot;
            }
            # ...
        ],
        &quot;agent_logs&quot;: [
            &quot;[18:31:02] [Webhook] Inbound email parsed.&quot;,
            &quot;[18:31:05] [CrewAI] Parsing semantic text. MPN extracted: TI-CAP-10U-50.&quot;,
            &quot;[18:31:07] [CrewAI] P&amp;amp;L calculated. Retained Margin at Risk: 12,500.00 EUR.&quot;
        ]
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The absolute numerical aggregate indexed directly as &lt;code&gt;total_risk_eur&lt;/code&gt; unifies the financial exposure stemming from synchronous alert computations, guaranteeing clear C-Level synthesis.&lt;/p&gt;
&lt;h4 id=&quot;the-frontend-engineering-paradigm-minimalist-fundamentals&quot;&gt;The Frontend Engineering Paradigm: Minimalist Fundamentals&lt;/h4&gt;
&lt;p&gt;The modern software landscape predominantly cultivates vast dependencies enveloping Node.js-based heavy frameworks (such as React or Angular), frequently extending these burdens even toward purely unidirectional Read-Only telemetry boards.&lt;/p&gt;
&lt;p&gt;Contrasting this convention, a functional operational factory monitor (Andon boarding mechanics) strictly dictates absolute instantaneous availability, zero massive transient module repositories, and static instantaneous rendering boundaries. Employing rigid &lt;em&gt;Dogfooding&lt;/em&gt; and adherence to strict First Principles methodologies, we outline a standard static &lt;em&gt;index.html&lt;/em&gt; directly provisioned via CDN utilities.&lt;/p&gt;
&lt;p&gt;The underlying stack distills precisely toward standard native Web API functionalities mapping the unadorned &lt;code&gt;Fetch()&lt;/code&gt; standard enclosed inside strict &lt;strong&gt;Vanilla JavaScript&lt;/strong&gt; bound dynamically by atomic atomic styling via &lt;strong&gt;Tailwind CSS&lt;/strong&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;fetch(&quot;http://localhost:8001/api/v1/dashboard/risk-metrics&quot;)
    .then(response =&amp;gt; response.json())
    .then(data =&amp;gt; {
        // Enforcing Macro-Econometric bindings
        document.getElementById('totalRisk').innerText =
            new Intl.NumberFormat('en-US', { style: 'currency', currency: 'EUR' }).format(data.total_risk_eur);
       
        // Channeling active event telemetry loops
        const consoleDiv = document.getElementById('telemetryConsole');
        data.agent_logs.forEach(log =&amp;gt; {
            const line = document.createElement('div');
            line.innerText = `&amp;gt; ${log}`;
            consoleDiv.appendChild(line);
        });
    });
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;visual-engineering-for-critical-operations-industrial-ux&quot;&gt;Visual Engineering for Critical Operations: Industrial UX&lt;/h4&gt;
&lt;p&gt;To aggressively preempt “Analysis Paralysis” constraints across executive viewers, structural interface segregation clusters data exclusively within three core categorical axes:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;The Critical Analysis Threshold (Macro KPI):&lt;/strong&gt; Disclosing the consolidated risk metric bound heavily inside an unmissable perimeter. Direct and literal exposure communicating the 15,450.00 EUR justifying the technological expenditure securing AI frameworks within the board room.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Constraint Tabulation (Blacklist Registers):&lt;/strong&gt; A starkly structured matrix elevating purely the top-tier commercial product identifier base (&lt;code&gt;DRONE-X1&lt;/code&gt;), deliberately isolating the complex inner web of microscopic resistors below. The target centers entirely upon final operational line constraints.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The Live Core (Active Telemetry Processing):&lt;/strong&gt; Real-time monitoring console mapped visually to broadcast the raw cognitive iterations driven by the internal CrewAI process. Reassuring operational directors visually confirming the endless, uninterrupted analytic progression actively shielding logistics lines without manual prompting.&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 id=&quot;series-culmination-a-blue-ocean-extracted-in-logistics&quot;&gt;Series Culmination: A Blue Ocean Extracted in Logistics&lt;/h4&gt;
&lt;p&gt;This specific deployment sequence concludes our intensive technical trajectory mapped consistently to operational engineering, advancing drastically past speculative archaic forecasting techniques relying broadly on weak statistical projections.&lt;/p&gt;
&lt;p&gt;We acknowledged material component obsolescence strictly as systematic margin erosion outlined prominently across IEC 62402 requirements. We encoded intricate supply interdependencies mapping vectors within rigorous recursive Graph databases. We algorithmically neutralized chaotic raw ingestion inconsistencies integrating Pandas filters; allocated heavily restrained advanced Language Agents dynamically converting abstract linguistic payloads autonomously into structured data matrices, and decisively scaled robust event-driven operations persistently spanning active Background FastAPI nodes.&lt;/p&gt;
&lt;p&gt;Physical logistics obsolescence fundamentally resigns its classification as an uncontrollable, unpredictable Black Swan occurrence. Converted exclusively into a precisely measurable, purely mitigated domestic phenomenon, we have ultimately established a definitive and asymmetric tactical advantage securing systemic industrial resilience.&lt;/p&gt;
</description>
<category>Tutorials</category>
<guid isPermaLink="true">https://coderlegion.com/18142/the-executive-interface-translating-ai-telemetry-into-c-level-decisions</guid>
<pubDate>Sun, 17 May 2026 07:27:53 +0000</pubDate>
</item>
<item>
<title>The Central Nervous System: Scaling the Agentic Radar to 24/7 with FastAPI and Webhooks</title>
<link>https://coderlegion.com/17141/the-central-nervous-system-scaling-the-agentic-radar-to-24-7-with-fastapi-and-webhooks</link>
<description>&lt;p&gt;In the previous section of this series, we demonstrated that Artificial Intelligence can calculate the P&amp;amp;L impact of component obsolescence. This was achieved by isolating the semantic inference module from execution, using deterministic SQL tools. &lt;/p&gt;
&lt;p&gt;However, running a Python script locally in a terminal is not suitable for production. Product Discontinuance Notices (PDNs) arrive continuously across global time zones. Supply chain operations require a centralized, continuous, and scalable system.&lt;/p&gt;
&lt;p&gt;To achieve production readiness, the architecture must transition to an &lt;strong&gt;Event-Driven Architecture (EDA)&lt;/strong&gt;.&lt;/p&gt;
&lt;h4 id=&quot;blueprint-topology-legacy-vectors-vs-modern-api-frameworks&quot;&gt;Blueprint Topology: Legacy Vectors vs. Modern API Frameworks&lt;/h4&gt;
&lt;p&gt;At the architectural level, supply chain logistics systems must accommodate two distinct alert ingestion methods:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;The Modern API Vector (B2B SaaS):&lt;/strong&gt; Commercial component lifecycle management tools, such as SiliconExpert or Accuris, provide structured data. These platforms dispatch standardized JSON payloads detailing market lifecycle transitions. &lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The Legacy Vector (Email):&lt;/strong&gt; Many manufacturers and Tier 2 suppliers continue to use plain text emails or PDF attachments to announce fabrication facility shutdowns or EOL statuses. &lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;The Software Engineering Decision:&lt;/strong&gt; Polling IMAP mailboxes continuously with Python processes is resource-intensive and prone to latency. Instead, we use &lt;em&gt;Inbound Parse&lt;/em&gt; gateways (like SendGrid or Mailgun). These services intercept emails, extract the relevant properties (Subject, Body), package them into a standardized JSON payload, and forward them to an integration endpoint. Through this approach, both communication channels are normalized into standard HTTPS POST requests.&lt;/p&gt;
&lt;h4 id=&quot;routing-logic-structuring-the-backbone-with-fastapi&quot;&gt;Routing Logic: Structuring the Backbone with FastAPI&lt;/h4&gt;
&lt;p&gt;We utilize &lt;strong&gt;FastAPI&lt;/strong&gt; to build the asynchronous microservice in Python. The objective is to deploy a routing layer that directs incoming alerts to the CrewAI framework.&lt;/p&gt;
&lt;p&gt;The following simplified code demonstrates the dual webhook implementation:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;@app.post(&quot;/api/v1/webhooks/commercial-radar&quot;)
async def commercial_radar_webhook(alert: CommercialAlert, background_tasks: BackgroundTasks):
    # Vector 1 Protocol: Commercial API Consumption
    synthetic_pdn = f&quot;Manufacturer: {alert.manufacturer}. MPN: {alert.mpn}. Status: EOL.&quot;
    
    background_tasks.add_task(process_obsolescence_background, synthetic_pdn)
    return {&quot;status&quot;: &quot;accepted&quot;}

@app.post(&quot;/api/v1/webhooks/inbound-email&quot;)
async def inbound_email_webhook(email: InboundEmail, background_tasks: BackgroundTasks):
    # Vector 2 Protocol: Inbound Parsed Email Payload
    pdn_text = f&quot;Subject: {email.subject}\nBody: {email.text}&quot;
    
    background_tasks.add_task(process_obsolescence_background, pdn_text)
    return {&quot;status&quot;: &quot;accepted&quot;}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;asynchronous-execution-handling-llm-latency&quot;&gt;Asynchronous Execution: Handling LLM Latency&lt;/h4&gt;
&lt;p&gt;The snippet above highlights a mandatory pattern for web resilience when integrating LLMs. A CrewAI-orchestrated inference cycle typically requires 5 to 15 seconds to complete. This process involves parsing the input, extracting the part number, querying the Supabase relational graph, calculating the financial impact, and formatting the response.&lt;/p&gt;
&lt;p&gt;Keeping the HTTP socket open while awaiting this execution will cause the sending API (e.g., SendGrid) to encounter a Timeout error (usually capped at 10 seconds), leading to redundant internal retries. The standard solution is to decouple the execution using &lt;strong&gt;Background Tasks&lt;/strong&gt;. &lt;/p&gt;
&lt;p&gt;The server immediately returns an &quot;HTTP 202 Accepted&quot; status code, closing the connection with the client. Concurrently, the internal worker instantiates the LLM operations in the background without blocking network resources.&lt;/p&gt;
&lt;h4 id=&quot;the-closed-control-loop-system-notifications&quot;&gt;The Closed Control Loop: System Notifications&lt;/h4&gt;
&lt;p&gt;If the autonomous agent successfully evaluates the downtime risk but only logs the output locally, the system does not fulfill its operational purpose. The output data must be pushed to the relevant stakeholders.&lt;/p&gt;
&lt;p&gt;The final stage of the architecture involves sending the generated mitigation brief to the procurement team's communication channels (such as Microsoft Teams or Slack) via an outbound webhook.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;def process_obsolescence_background(pdn_text: str):
    # ... Multi-Agent Inference Iteration ...
    assessment = execute_obsolescence_analysis(pdn_text)
    
    # MS Teams/Slack Procurement Alert
    header = f&quot; Agentic P&amp;amp;L Alert Processed\n&quot;
    notify_teams(header + str(assessment))
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;complete-system-architecture&quot;&gt;Complete System Architecture&lt;/h4&gt;
&lt;p&gt;The integration of these modules forms an event-driven pipeline where SQL table queries and LLM text processing operate together asynchronously.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://coderlegion.com/?qa=blob&amp;amp;qa_blobid=6752501081950087219&quot; alt=&quot;architecture&quot;&gt;&lt;/p&gt;
&lt;h4 id=&quot;next-steps&quot;&gt;Next Steps&lt;/h4&gt;
&lt;p&gt;We have configured the data ingestion engine (Block 2), established the semantic inference framework (Block 3), and deployed an API-centric service to process global component anomalies 24/7 (Block 4). &lt;/p&gt;
&lt;p&gt;In the final segment of this engineering series, we will focus on data visualization. We will document how to expose these asynchronous alerts by building an &lt;strong&gt;Executive Dashboard&lt;/strong&gt;, making the agent's operations accessible for management review.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/17141/the-central-nervous-system-scaling-the-agentic-radar-to-24-7-with-fastapi-and-webhooks</guid>
<pubDate>Sat, 09 May 2026 08:48:42 +0000</pubDate>
</item>
<item>
<title>Getting Started with Google Colab: Run Python in the Cloud for Free</title>
<link>https://coderlegion.com/13280/getting-started-with-google-colab-run-python-in-the-cloud-for-free</link>
<description>&lt;p&gt;If you want to write and run Python code without installing anything on your machine, &lt;strong&gt;Google Colab&lt;/strong&gt; is one of the easiest ways to start.&lt;/p&gt;
&lt;p&gt;Whether you're learning Python, building AI models, or analyzing data, Colab gives you a powerful environment—right in your browser.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;what-is-google-colab-&quot;&gt;What is Google Colab?&lt;/h3&gt;
&lt;p&gt;Google Colab (short for &lt;em&gt;Colaboratory&lt;/em&gt;) is a &lt;strong&gt;cloud-based notebook environment&lt;/strong&gt; that lets you:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Write and execute Python code&lt;/li&gt;
&lt;li&gt;Use GPUs/TPUs for free&lt;/li&gt;
&lt;li&gt;Share notebooks like Google Docs&lt;/li&gt;
&lt;li&gt;Install libraries on the fly&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt; Think of it as:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;Jupyter Notebook + Free Cloud Compute + Collaboration&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h3 id=&quot;-why-developers-love-colab&quot;&gt;⚡ Why Developers Love Colab&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;No setup required&lt;/strong&gt; – runs in your browser&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Free GPU access&lt;/strong&gt; – great for AI/ML&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Auto-saves to Google Drive&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Easy sharing &amp;amp; collaboration&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Pre-installed libraries&lt;/strong&gt; (NumPy, Pandas, TensorFlow, PyTorch)&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;-getting-started-step-by-step&quot;&gt;️ Getting Started Step-by-Step&lt;/h3&gt;
&lt;h4 id=&quot;step-1-open-google-colab&quot;&gt;Step 1: Open Google Colab&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Go to Colab&lt;/li&gt;
&lt;li&gt;Sign in with your Google account&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h4 id=&quot;step-2-create-a-new-notebook&quot;&gt;Step 2: Create a New Notebook&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Click &lt;strong&gt;“New Notebook”&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;A &lt;code&gt;.ipynb&lt;/code&gt; file opens instantly&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h4 id=&quot;step-3-understand-the-interface&quot;&gt;Step 3: Understand the Interface&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Code cells&lt;/strong&gt; → write Python code&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Text cells&lt;/strong&gt; → write notes (Markdown)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Run button ▶️&lt;/strong&gt; → execute code&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;-your-first-code&quot;&gt;✍️ Your First Code&lt;/h3&gt;
&lt;h4 id=&quot;example-hello-world&quot;&gt;Example: Hello World&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;print(&quot;Hello, Google Colab!&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Click ▶️ and see output instantly.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;example-data-analysis&quot;&gt;Example: Data Analysis&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import pandas as pd

data = {
    &quot;Name&quot;: [&quot;Alice&quot;, &quot;Bob&quot;, &quot;Charlie&quot;],
    &quot;Score&quot;: [85, 90, 95]
}

df = pd.DataFrame(data)
df
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt; Output is displayed as a clean table.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;using-colab-for-ai-ml&quot;&gt;Using Colab for AI/ML&lt;/h3&gt;
&lt;p&gt;Colab is widely used for:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Training machine learning models&lt;/li&gt;
&lt;li&gt;Running deep learning experiments&lt;/li&gt;
&lt;li&gt;Building NLP applications&lt;/li&gt;
&lt;li&gt;Prototyping AI ideas&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;enable-gpu&quot;&gt;Enable GPU&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Click &lt;strong&gt;Runtime → Change runtime type&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Select &lt;strong&gt;GPU&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt; Now your notebook runs faster for heavy tasks.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;installing-libraries&quot;&gt;Installing Libraries&lt;/h3&gt;
&lt;p&gt;Need extra packages? Just run:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;!pip install transformers
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;working-with-files&quot;&gt;Working with Files&lt;/h3&gt;
&lt;h4 id=&quot;upload-file&quot;&gt;Upload file&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from google.colab import files
files.upload()
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;mount-google-drive&quot;&gt;Mount Google Drive&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from google.colab import drive
drive.mount('/content/drive')
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;sharing-your-notebook&quot;&gt;Sharing Your Notebook&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Click &lt;strong&gt;Share&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Add collaborators&lt;/li&gt;
&lt;li&gt;Set permissions (view/edit)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Just like Google Docs!&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;real-world-use-cases&quot;&gt;Real-World Use Cases&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Data analysis &amp;amp; visualization&lt;/li&gt;
&lt;li&gt;Machine learning experiments&lt;/li&gt;
&lt;li&gt;AI model prototyping&lt;/li&gt;
&lt;li&gt;Teaching &amp;amp; tutorials&lt;/li&gt;
&lt;li&gt;Research projects&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;-limitations-to-know&quot;&gt;⚠️ Limitations to Know&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Sessions disconnect after inactivity&lt;/li&gt;
&lt;li&gt;Limited compute time&lt;/li&gt;
&lt;li&gt;Not ideal for production apps&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;when-should-you-use-colab-&quot;&gt;When Should You Use Colab?&lt;/h3&gt;
&lt;p&gt;Use it when you want to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Learn Python or ML&lt;/li&gt;
&lt;li&gt;Prototype quickly&lt;/li&gt;
&lt;li&gt;Avoid local setup&lt;/li&gt;
&lt;li&gt;Use GPU without cost&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Avoid it when:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Building production systems&lt;/li&gt;
&lt;li&gt;Running long-duration jobs&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;final-thoughts-2&quot;&gt;Final Thoughts&lt;/h3&gt;
&lt;p&gt;Google Colab removes the friction of setup and gives you instant access to powerful computing.&lt;/p&gt;
&lt;p&gt;Instead of worrying about environments, you can focus on:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt; Learning, experimenting, and building&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h3 id=&quot;what-to-try-next&quot;&gt;What to Try Next&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Train a simple ML model&lt;/li&gt;
&lt;li&gt;Use Hugging Face transformers&lt;/li&gt;
&lt;li&gt;Build a chatbot&lt;/li&gt;
&lt;li&gt;Analyze a dataset&lt;/li&gt;
&lt;li&gt;Visualize data with Matplotlib&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;p&gt;kind of free GPU&lt;br&gt;
&lt;a rel=&quot;nofollow&quot; href=&quot;https://colab.research.google.com/&quot;&gt;https://colab.research.google.com/&lt;/a&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/13280/getting-started-with-google-colab-run-python-in-the-cloud-for-free</guid>
<pubDate>Sat, 21 Mar 2026 09:05:41 +0000</pubDate>
</item>
<item>
<title>Dashboard Operasional Armada Rental Mobil dengan Python + FastAPI</title>
<link>https://coderlegion.com/12157/dashboard-operasional-armada-rental-mobil-dengan-python-fastapi</link>
<description>&lt;p&gt;Di banyak bisnis rental, masalahnya bukan kekurangan data—melainkan data tercecer: ketersediaan unit ada di chat, jadwal servis ada di catatan, dan laporan pemakaian tercebur di spreadsheet. Tren “dashboard-first” yang sering dibahas komunitas (misalnya artikel CoderLegion tentang &lt;a rel=&quot;nofollow&quot; href=&quot;https://coderlegion.com/12111/membangun-dashboard-monitoring-proyek-konstruksi-dengan-python-fastapi-progress-lapor?is_promoted=1&quot;&gt;dashboard monitoring Python + FastAPI&lt;/a&gt;) membuat kita bisa merapikan alur operasional jadi satu layar. Di artikel ini, kita bongkar cara membangun &lt;strong&gt;dashboard operasional armada rental&lt;/strong&gt; yang ringan, real-time, dan enak dipakai tim lapangan.&lt;/p&gt;
&lt;p&gt;Secara akademik, pendekatan dashboard monitoring terbukti membantu efisiensi pemantauan dan akurasi pelaporan pada studi pengembangan dashboard monitoring proyek (lihat landasan pada &lt;a rel=&quot;nofollow&quot; href=&quot;https://ejournal.unuja.ac.id/index.php/jeecom/article/download/13084/pdf&quot;&gt;jurnal dashboard monitoring berbasis KPI&lt;/a&gt;). Walau konteksnya bukan rental mobil, prinsipnya identik: KPI yang terukur + visualisasi yang tepat = keputusan lebih cepat. Itu alasan kenapa tema ini relevan untuk pembaca developer: kalian bisa membawa “engineering mindset” ke problem operasional yang nyata.&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;“Dashboard yang bagus itu bukan sekadar cantik—dia memotong waktu debat, mempercepat keputusan, dan mengurangi miskomunikasi.”&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h3 id=&quot;1-kenapa-dashboard-operasional-itu-penting-untuk-armada-rental&quot;&gt;1. Kenapa Dashboard Operasional Itu Penting untuk Armada Rental&lt;/h3&gt;
&lt;p&gt;Sebelum nulis satu baris kode, kita perlu sepakat: dashboard bukan “aksesori”, tapi &lt;em&gt;control plane&lt;/em&gt; untuk operasional. Kalau operasional kamu masih bergantung pada ingatan admin dan scroll chat panjang, risiko bentrok jadwal, servis telat, dan salah alokasi unit itu tinggal menunggu waktu.&lt;/p&gt;
&lt;h4 id=&quot;masalah-yang-sering-kejadian-di-lapangan&quot;&gt;Masalah yang sering kejadian di lapangan&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Double booking&lt;/strong&gt;: unit dianggap available karena belum di-update.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Servis kelewat&lt;/strong&gt;: jadwal maintenance tidak punya pengingat yang konsisten.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Laporan pemakaian bias&lt;/strong&gt;: data odometer/BBM masuknya sporadis.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;SLA respon lambat&lt;/strong&gt;: customer nunggu kepastian unit karena admin cek manual.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;kpi-yang-wajib-ada-untuk-rental&quot;&gt;KPI yang “wajib ada” untuk rental&lt;/h4&gt;
&lt;p&gt;Agar &lt;strong&gt;dashboard operasional armada rental&lt;/strong&gt; tidak jadi pajangan, tetapkan KPI sederhana:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;Utilization rate&lt;/em&gt; per tipe kendaraan&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Availability&lt;/em&gt; harian (available / on-rent / maintenance)&lt;/li&gt;
&lt;li&gt;&lt;em&gt;On-time return rate&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;Jumlah unit “overdue service”&lt;/li&gt;
&lt;li&gt;Lead time dari inquiry → konfirmasi&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;2-data-model-minimal-yang-bikin-dashboard-nendang&quot;&gt;2. Data Model Minimal yang Bikin Dashboard Nendang&lt;/h3&gt;
&lt;p&gt;Dashboard yang efektif biasanya justru lahir dari skema data yang tidak ribet. Mulai dari model minimal, lalu berkembang.&lt;/p&gt;
&lt;h4 id=&quot;entity-yang-disarankan&quot;&gt;Entity yang disarankan&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;Fleet&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;vehicle_id&lt;/code&gt;, &lt;code&gt;plate_number&lt;/code&gt;, &lt;code&gt;type&lt;/code&gt; (sedan/SUV/minivan/city car/pickup), &lt;code&gt;status&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Booking&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;booking_id&lt;/code&gt;, &lt;code&gt;vehicle_id&lt;/code&gt;, &lt;code&gt;start_at&lt;/code&gt;, &lt;code&gt;end_at&lt;/code&gt;, &lt;code&gt;customer_name&lt;/code&gt;, &lt;code&gt;channel&lt;/code&gt; (WA/web), &lt;code&gt;status&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Maintenance&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;maintenance_id&lt;/code&gt;, &lt;code&gt;vehicle_id&lt;/code&gt;, &lt;code&gt;service_type&lt;/code&gt;, &lt;code&gt;due_at&lt;/code&gt; / &lt;code&gt;due_odometer&lt;/code&gt;, &lt;code&gt;status&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Usage Log&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;usage_id&lt;/code&gt;, &lt;code&gt;vehicle_id&lt;/code&gt;, &lt;code&gt;date&lt;/code&gt;, &lt;code&gt;odometer&lt;/code&gt;, &lt;code&gt;fuel&lt;/code&gt;, &lt;code&gt;notes&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;tabel-ringkas-status-unit-agar-semua-orang-1-bahasa-&quot;&gt;Tabel ringkas status unit (agar semua orang 1 bahasa)&lt;/h4&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Status      &lt;/th&gt;&lt;th&gt;Definisi                          &lt;/th&gt;&lt;th&gt;Dampak di Dashboard&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;available   &lt;/td&gt;&lt;td&gt;Unit siap disewa                  &lt;/td&gt;&lt;td&gt;Bisa dibooking&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;on_rent     &lt;/td&gt;&lt;td&gt;Sedang dipakai customer           &lt;/td&gt;&lt;td&gt;Tampil sebagai terpakai&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;maintenance &lt;/td&gt;&lt;td&gt;Servis/repair                     &lt;/td&gt;&lt;td&gt;Disembunyikan dari listing booking&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;hold        &lt;/td&gt;&lt;td&gt;Diblock sementara (mis. inspeksi) &lt;/td&gt;&lt;td&gt;Perlu approval sebelum release&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;blockquote&gt;&lt;p&gt;Tip: status &lt;strong&gt;hold&lt;/strong&gt; sering menyelamatkan ketika ada unit “abu-abu” (baru balik, belum dicek).&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h3 id=&quot;3-arsitektur-cepat-fastapi-sebagai-backend-dashboard-sebagai-produk&quot;&gt;3. Arsitektur Cepat: FastAPI sebagai Backend, Dashboard sebagai Produk&lt;/h3&gt;
&lt;p&gt;Untuk komunitas CoderLegion, stack paling “masuk akal” adalah FastAPI + Postgres + frontend dashboard (bisa Next.js, atau minimal Jinja/HTMX kalau mau hemat).&lt;/p&gt;
&lt;h4 id=&quot;komponen-yang-saya-pakai-pragmatis-&quot;&gt;Komponen yang saya pakai (pragmatis)&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;FastAPI&lt;/strong&gt;: REST + WebSocket untuk update real-time&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;PostgreSQL&lt;/strong&gt;: data transaksi booking/servis&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Redis (opsional)&lt;/strong&gt;: cache availability dan event stream&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Background jobs&lt;/strong&gt;: scheduler reminder servis &amp;amp; overdue return&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;kenapa-fastapi-cocok&quot;&gt;Kenapa FastAPI cocok&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;Typed&lt;/em&gt; (Pydantic) → validasi data operasional rapi&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Async-ready&lt;/em&gt; → cocok untuk notifikasi dan stream&lt;/li&gt;
&lt;li&gt;Ekosistem luas → gampang integrasi scheduler, websocket, auth&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Sebagai referensi internal yang relevan, kamu juga bisa lihat &lt;a rel=&quot;nofollow&quot; href=&quot;https://coderlegion.com/5893/fastapi-bgtasks-dashboard&quot;&gt;fastapi-bgtasks-dashboard&lt;/a&gt; untuk ide “observability” background task di ekosistem FastAPI.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;4-cara-menghitung-ketersediaan-unit-tanpa-drama&quot;&gt;4. Cara Menghitung Ketersediaan Unit Tanpa Drama&lt;/h3&gt;
&lt;p&gt;Bagian paling krusial dari &lt;strong&gt;dashboard operasional armada rental&lt;/strong&gt; adalah &lt;em&gt;availability engine&lt;/em&gt;. Kita tidak mau “available” versi admin A berbeda dengan admin B.&lt;/p&gt;
&lt;h4 id=&quot;prinsip-kalkulasi&quot;&gt;Prinsip kalkulasi&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;Ambil semua unit yang &lt;strong&gt;bukan&lt;/strong&gt; &lt;code&gt;maintenance&lt;/code&gt; dan &lt;strong&gt;bukan&lt;/strong&gt; &lt;code&gt;hold&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Untuk rentang waktu yang dipilih, cek apakah ada booking berstatus &lt;code&gt;confirmed&lt;/code&gt;/&lt;code&gt;active&lt;/code&gt; yang overlap.&lt;/li&gt;
&lt;li&gt;Jika overlap → &lt;code&gt;on_rent&lt;/code&gt;, jika tidak → &lt;code&gt;available&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 id=&quot;edge-case-yang-sering-dilupakan&quot;&gt;Edge case yang sering dilupakan&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Booking yang &lt;em&gt;extend&lt;/em&gt; (end time berubah)&lt;/li&gt;
&lt;li&gt;Unit yang &lt;em&gt;swap&lt;/em&gt; karena kerusakan&lt;/li&gt;
&lt;li&gt;Booking pending (belum dibayar DP) — putuskan apakah memblok unit atau tidak&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;tabel-contoh-aturan-overlap&quot;&gt;Tabel contoh aturan overlap&lt;/h4&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Kondisi                                                     &lt;/th&gt;&lt;th&gt;Overlap? &lt;/th&gt;&lt;th&gt;Status&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;booking end &amp;lt; start filter                                  &lt;/td&gt;&lt;td&gt;   tidak &lt;/td&gt;&lt;td&gt;available&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;booking start &amp;gt; end filter                                  &lt;/td&gt;&lt;td&gt;   tidak &lt;/td&gt;&lt;td&gt;available&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;booking start &amp;lt;= end filter dan booking end &amp;gt;= start filter &lt;/td&gt;&lt;td&gt;      ya &lt;/td&gt;&lt;td&gt;on_rent&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h3 id=&quot;5-jadwal-servis-dari-ingat-ingat-jadi-sistematis&quot;&gt;5. Jadwal Servis: Dari “Ingat-ingat” Jadi Sistematis&lt;/h3&gt;
&lt;p&gt;Di rental mobil, maintenance itu bukan biaya—dia investasi. Dashboard harus membuat servis terlihat dan &lt;em&gt;actionable&lt;/em&gt;.&lt;/p&gt;
&lt;h4 id=&quot;pola-yang-efektif&quot;&gt;Pola yang efektif&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Due by date&lt;/strong&gt;: servis berkala tiap X bulan&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Due by odometer&lt;/strong&gt;: servis tiap X km&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Alert window&lt;/strong&gt;: misalnya H-7 / 500 km sebelum due&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;checklist-servis-yang-bisa-ditampilkan&quot;&gt;Checklist servis yang bisa ditampilkan&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Oli + filter&lt;/li&gt;
&lt;li&gt;Rem &amp;amp; kampas&lt;/li&gt;
&lt;li&gt;Ban &amp;amp; rotasi&lt;/li&gt;
&lt;li&gt;AC &amp;amp; filter kabin&lt;/li&gt;
&lt;li&gt;Pemeriksaan kelistrikan&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;&lt;p&gt;Pro tip: tampilkan widget “unit yang &lt;strong&gt;overdue&lt;/strong&gt;” di atas. Manusia cenderung mengerjakan yang paling terlihat.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h3 id=&quot;6-laporan-pemakaian-biar-keputusan-tarif-nggak-pakai-perasaan&quot;&gt;6. Laporan Pemakaian: Biar Keputusan Tarif Nggak Pakai Perasaan&lt;/h3&gt;
&lt;p&gt;Kalau kamu punya log odometer dan pemakaian, kamu bisa:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Deteksi unit yang “boros” (indikasi masalah mesin/ban)&lt;/li&gt;
&lt;li&gt;Rekomendasi rotasi armada&lt;/li&gt;
&lt;li&gt;Hitung biaya per km untuk evaluasi tarif&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;format-laporan-yang-enak-dibaca&quot;&gt;Format laporan yang enak dibaca&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Per unit per minggu&lt;/strong&gt;: km tempuh, hari terpakai, downtime&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Per tipe kendaraan&lt;/strong&gt;: utilisasi, revenue proxy, complaint rate&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Top 5 penyebab downtime&lt;/strong&gt;: maintenance, hold, overdue return&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;contoh-ringkas-tabel-laporan-mingguan&quot;&gt;Contoh ringkas tabel laporan mingguan&lt;/h4&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Plat      &lt;/th&gt;&lt;th&gt;Hari terpakai &lt;/th&gt;&lt;th&gt; KM &lt;/th&gt;&lt;th&gt;Downtime (hari) &lt;/th&gt;&lt;th&gt;Catatan&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;B 1234 XX &lt;/td&gt;&lt;td&gt;            5 &lt;/td&gt;&lt;td&gt;410 &lt;/td&gt;&lt;td&gt;              1 &lt;/td&gt;&lt;td&gt;Servis oli&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;B 9876 YY &lt;/td&gt;&lt;td&gt;            3 &lt;/td&gt;&lt;td&gt;220 &lt;/td&gt;&lt;td&gt;              2 &lt;/td&gt;&lt;td&gt;Ban depan&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h3 id=&quot;7-ui-ux-dashboard-yang-coderlegion-banget-&quot;&gt;7. UI/UX Dashboard yang “CoderLegion Banget”&lt;/h3&gt;
&lt;p&gt;Di 2026, dashboard itu bukan sekadar chart. Ini tentang &lt;em&gt;workflow&lt;/em&gt;.&lt;/p&gt;
&lt;h4 id=&quot;pola-ui-yang-terasa-modern&quot;&gt;Pola UI yang terasa modern&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Global filter&lt;/strong&gt;: tanggal, cabang, tipe kendaraan&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Quick actions&lt;/strong&gt;: tombol “Block unit”, “Jadwalkan servis”, “Extend booking”&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Activity feed&lt;/strong&gt;: perubahan status unit (audit trail)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Real-time badge&lt;/strong&gt;: indikator data terakhir update&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;microcopy-yang-membantu-operasional&quot;&gt;Microcopy yang membantu operasional&lt;/h4&gt;
&lt;p&gt;Daripada “Maintenance”, gunakan:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;“Sedang Servis (estimasi selesai: …)”&lt;/li&gt;
&lt;li&gt;“Hold: menunggu inspeksi”&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Dengan microcopy yang jelas, &lt;strong&gt;dashboard operasional armada rental&lt;/strong&gt; jadi “bahasa bersama” antara admin, driver, dan owner.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;8-security-amp-reliability-jangan-sampai-dashboard-jadi-titik-lemah&quot;&gt;8. Security &amp;amp; Reliability: Jangan Sampai Dashboard Jadi Titik Lemah&lt;/h3&gt;
&lt;p&gt;Dashboard operasional itu sensitif: ada data customer, jadwal, dan status aset.&lt;/p&gt;
&lt;h4 id=&quot;minimal-baseline-yang-saya-sarankan&quot;&gt;Minimal baseline yang saya sarankan&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Auth&lt;/strong&gt;: JWT + refresh token / session cookie&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;RBAC&lt;/strong&gt;: admin, supervisor, driver&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Audit log&lt;/strong&gt;: siapa ubah status unit kapan&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Rate limiting&lt;/strong&gt; untuk endpoint publik&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;observability-yang-simple-tapi-berguna&quot;&gt;Observability yang simple tapi berguna&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Log structured (JSON)&lt;/li&gt;
&lt;li&gt;Metrics (request latency, error rate)&lt;/li&gt;
&lt;li&gt;Tracing untuk alur booking → notifikasi → update status&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;faq&quot;&gt;FAQ&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Q: Saya sudah pakai spreadsheet, perlu dashboard?&lt;/strong&gt;&lt;br&gt;
A: Spreadsheet bagus untuk input, tapi lemah untuk &lt;em&gt;real-time visibility&lt;/em&gt; dan kontrol status. Dashboard mengurangi “interpretasi” dan mempercepat eksekusi.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Q: Harus WebSocket?&lt;/strong&gt;&lt;br&gt;
A: Tidak wajib. Kamu bisa mulai polling tiap 10–30 detik. WebSocket jadi relevan saat tim kamu banyak dan perubahan status sering.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Q: Bagaimana menghindari double booking?&lt;/strong&gt;&lt;br&gt;
A: Pastikan ada &lt;em&gt;transaction/locking&lt;/em&gt; di sisi database saat konfirmasi booking, dan availability dihitung dari data booking yang sama.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Q: Tool frontend yang paling cepat?&lt;/strong&gt;&lt;br&gt;
A: Untuk MVP: Next.js + Tailwind + chart library. Untuk super cepat: template admin + fetch REST.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Q: KPI apa yang paling berdampak?&lt;/strong&gt;&lt;br&gt;
A: Availability harian + overdue service + overdue return. Tiga ini biasanya langsung terasa efeknya.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;howto&quot;&gt;HowTo&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;Tentukan KPI inti (availability, overdue service, overdue return).&lt;/li&gt;
&lt;li&gt;Buat data model minimal (Fleet, Booking, Maintenance, Usage Log).&lt;/li&gt;
&lt;li&gt;Bangun API FastAPI untuk list unit, availability, servis, laporan.&lt;/li&gt;
&lt;li&gt;Buat dashboard dengan global filter + widget prioritas (overdue).&lt;/li&gt;
&lt;li&gt;Tambahkan audit log, RBAC, dan notifikasi operasional.&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h3 id=&quot;dari-data-jadi-insight-yang-bisa-dipakai-tim&quot;&gt;Dari Data Jadi Insight yang Bisa Dipakai Tim&lt;/h3&gt;
&lt;p&gt;Sebagai penutup, ingat bahwa tujuan utama &lt;strong&gt;dashboard operasional armada rental&lt;/strong&gt; bukan “biar kelihatan canggih”, melainkan mengurangi friksi kerja: lebih sedikit cek manual, lebih sedikit miskomunikasi, dan lebih cepat ambil keputusan.&lt;/p&gt;
&lt;p&gt;Carly Fiorina—mantan CEO Hewlett-Packard yang dikenal vokal soal kepemimpinan berbasis teknologi—pernah mengatakan: &lt;strong&gt;“The goal is to turn data into information, and information into insight.”&lt;/strong&gt; Artinya: &lt;em&gt;tujuan kita adalah mengubah data menjadi informasi, dan informasi menjadi wawasan.&lt;/em&gt; Kamu bisa cek kutipan dan profilnya di &lt;a rel=&quot;nofollow&quot; href=&quot;https://en.wikiquote.org/wiki/Carly_Fiorina&quot;&gt;Wikiquote Carly Fiorina&lt;/a&gt;. Dalam konteks dashboard, ini mengingatkan kita bahwa angka mentah tidak cukup; yang dibutuhkan tim operasional adalah konteks, prioritas, dan tindakan berikutnya.&lt;/p&gt;
&lt;p&gt;Jika kamu mengelola rental mobil di Karawang–Bekasi dan ingin operasional lebih rapi dengan pemesanan cepat serta dukungan 24 jam, kamu bisa lihat profil layanan kami di &lt;strong&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://www.ototrack.co.id/&quot;&gt;Oto Track&lt;/a&gt;&lt;/strong&gt; untuk gambaran proses bisnis yang bisa dijadikan studi kasus implementasi dashboard.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &quot;@context&quot;: &quot;https://schema.org&quot;,
  &quot;@type&quot;: &quot;HowTo&quot;,
  &quot;name&quot;: &quot;Membangun dashboard operasional armada rental dengan Python dan FastAPI&quot;,
  &quot;description&quot;: &quot;Panduan ringkas untuk membangun dashboard operasional armada rental: ketersediaan unit, jadwal servis, dan laporan pemakaian.&quot;,
  &quot;totalTime&quot;: &quot;PT6H&quot;,
  &quot;supply&quot;: [
    {&quot;@type&quot;: &quot;HowToSupply&quot;, &quot;name&quot;: &quot;Database (PostgreSQL)&quot;},
    {&quot;@type&quot;: &quot;HowToSupply&quot;, &quot;name&quot;: &quot;Python 3.11+&quot;},
    {&quot;@type&quot;: &quot;HowToSupply&quot;, &quot;name&quot;: &quot;FastAPI&quot;}
  ],
  &quot;tool&quot;: [
    {&quot;@type&quot;: &quot;HowToTool&quot;, &quot;name&quot;: &quot;VS Code&quot;},
    {&quot;@type&quot;: &quot;HowToTool&quot;, &quot;name&quot;: &quot;Docker (opsional)&quot;}
  ],
  &quot;step&quot;: [
    {
      &quot;@type&quot;: &quot;HowToStep&quot;,
      &quot;name&quot;: &quot;Definisikan KPI inti&quot;,
      &quot;text&quot;: &quot;Pilih KPI yang paling berdampak: availability harian, overdue service, overdue return.&quot;,
      &quot;url&quot;: &quot;https://coderlegion.com/&quot;
    },
    {
      &quot;@type&quot;: &quot;HowToStep&quot;,
      &quot;name&quot;: &quot;Buat data model minimal&quot;,
      &quot;text&quot;: &quot;Siapkan entity Fleet, Booking, Maintenance, dan Usage Log agar status unit konsisten.&quot;,
      &quot;url&quot;: &quot;https://coderlegion.com/12111/membangun-dashboard-monitoring-proyek-konstruksi-dengan-python-fastapi-progress-lapor?is_promoted=1&quot;
    },
    {
      &quot;@type&quot;: &quot;HowToStep&quot;,
      &quot;name&quot;: &quot;Implementasi API FastAPI&quot;,
      &quot;text&quot;: &quot;Bangun endpoint untuk ketersediaan unit, jadwal servis, dan laporan pemakaian dengan validasi Pydantic.&quot;,
      &quot;url&quot;: &quot;https://coderlegion.com/groups/PythonDev&quot;
    },
    {
      &quot;@type&quot;: &quot;HowToStep&quot;,
      &quot;name&quot;: &quot;Bangun UI dashboard&quot;,
      &quot;text&quot;: &quot;Buat global filter, widget overdue, quick actions, dan audit trail agar workflow operasional lancar.&quot;,
      &quot;url&quot;: &quot;https://coderlegion.com/5893/fastapi-bgtasks-dashboard&quot;
    },
    {
      &quot;@type&quot;: &quot;HowToStep&quot;,
      &quot;name&quot;: &quot;Tambah security &amp;amp; observability&quot;,
      &quot;text&quot;: &quot;Aktifkan RBAC, audit log, rate limiting, dan monitoring error/latency untuk produksi.&quot;,
      &quot;url&quot;: &quot;https://coderlegion.com/&quot;
    }
  ]
}
&lt;/code&gt;&lt;/pre&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/12157/dashboard-operasional-armada-rental-mobil-dengan-python-fastapi</guid>
<pubDate>Thu, 12 Mar 2026 17:00:00 +0000</pubDate>
</item>
<item>
<title>Membangun Dashboard Monitoring Proyek Konstruksi dengan Python + FastAPI: Progress, RAB, dan Lapor</title>
<link>https://coderlegion.com/12111/membangun-dashboard-monitoring-proyek-konstruksi-dengan-python-fastapi-progress-lapor</link>
<description>&lt;p&gt;Dalam konstruksi modern, masalahnya jarang pada &lt;em&gt;kurang data&lt;/em&gt;—yang sering terjadi adalah data terpencar: progres di lapangan ada di form harian, biaya ada di spreadsheet, material dan alat berat ada di catatan berbeda. Tren integrasi software konstruksi juga bergerak ke arah sistem yang saling “ngobrol” lewat API agar keputusan bisa diambil lebih cepat dan lebih akurat. Itulah kenapa gagasan &lt;strong&gt;dashboard monitoring proyek konstruksi&lt;/strong&gt; makin relevan ketika kita ingin menyatukan progres, RAB, dan laporan harian ke satu sumber kebenaran (single source of truth), selaras dengan pembahasan tren integrasi pada artikel &lt;strong&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://cmicglobal.com/resources/article/construction-software-integrations-trends&quot;&gt;Construction Software Integration Trends for 2026&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Dari sisi ilmiah, pendekatan &lt;em&gt;real-time tracking&lt;/em&gt; dan visualisasi data lintas komponen proyek juga terbukti krusial untuk mengurangi keterlambatan notifikasi dan keputusan berbasis “feeling”. Studi &lt;strong&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://www.sciencedirect.com/science/article/pii/S1474034625004045&quot;&gt;Real-Time tracking and analysis in construction projects&lt;/a&gt;&lt;/strong&gt; membahas tantangan data multi-sumber (BIM, platform manajemen proyek, jadwal, dan data site) serta dampaknya pada analisis proyek. Karena itu, tema ini layak diangkat untuk pembaca CoderLegion: bukan sekadar “bikin dashboard”, tapi membangun fondasi API yang rapi—yang bisa dipakai ulang untuk otomasi laporan, integrasi ERP, sampai &lt;em&gt;project intelligence&lt;/em&gt; berbasis data.&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;Kesimpulan cepat:&lt;/strong&gt; Dashboard yang “bagus tampilannya” belum tentu berguna. Yang membuatnya benar-benar dipakai adalah &lt;em&gt;data pipeline&lt;/em&gt; yang bersih, API yang konsisten, dan definisi metrik yang disepakati sejak awal—baru setelah itu visualisasi jadi senjata.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h3 id=&quot;1-dari-laporan-harian-ke-api-kenapa-fastapi-cocok-untuk-proyek-&quot;&gt;1. Dari Laporan Harian ke API: Kenapa FastAPI Cocok untuk Proyek?&lt;/h3&gt;
&lt;p&gt;Bayangkan setiap site punya cara masing-masing untuk melaporkan progres: foto, catatan WhatsApp, Excel, atau form manual. Ketika semua itu masuk ke satu &lt;em&gt;backend&lt;/em&gt; berbasis API, Anda dapat memvalidasi, menyatukan format data, dan mengekspor ke dashboard dengan ritme yang konsisten. FastAPI cocok karena cepat, modern, dan punya &lt;em&gt;automatic docs&lt;/em&gt; (OpenAPI/Swagger) yang memudahkan kolaborasi lintas tim.&lt;/p&gt;
&lt;h4 id=&quot;apa-yang-dimaksud-monitoring-di-proyek-konstruksi-&quot;&gt;Apa yang Dimaksud “Monitoring” di Proyek Konstruksi?&lt;/h4&gt;
&lt;p&gt;Monitoring yang sehat itu bukan sekadar “berapa persen selesai”. Praktiknya biasanya memadukan:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Progress fisik&lt;/strong&gt;: volume pekerjaan (m³, m², meter lari), bobot item, &lt;em&gt;earned value&lt;/em&gt; sederhana.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Biaya&lt;/strong&gt;: realisasi vs RAB, komitmen PO, biaya alat berat.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Produktivitas&lt;/strong&gt;: output per jam/shift, &lt;em&gt;downtime&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Kualitas &amp;amp; HSE&lt;/strong&gt;: temuan, &lt;em&gt;near miss&lt;/em&gt;, checklist.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;prinsip-desain-api-yang-bikin-dashboard-tidak-berantakan-&quot;&gt;Prinsip Desain API yang Bikin Dashboard Tidak “Berantakan”&lt;/h4&gt;
&lt;p&gt;Sebelum menulis endpoint, biasakan satu prinsip: &lt;strong&gt;dashboard itu “makan” data yang konsisten&lt;/strong&gt;.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Gunakan &lt;em&gt;resource-oriented&lt;/em&gt; endpoint: &lt;code&gt;/projects&lt;/code&gt;, &lt;code&gt;/daily-reports&lt;/code&gt;, &lt;code&gt;/cost-ledger&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Tentukan versi API sejak awal (&lt;code&gt;/v1/...&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Standarisasi format tanggal, timezone, dan &lt;em&gt;unit&lt;/em&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Bacaan internal yang relevan untuk memperkuat “cara berpikir API”:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://coderlegion.com/11707/best-practices-for-restful-api-design-for-dev-tools&quot;&gt;Best practices for RESTful API design for Dev Tools&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://coderlegion.com/4597/versioning-your-api-the-right-way-rest-best-practices&quot;&gt;Versioning Your API the Right Way (REST Best Practices)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;2-arsitektur-minimalis-yang-siap-dipakai-data-layanan-dan-dashboard&quot;&gt;2. Arsitektur Minimalis yang “Siap Dipakai”: Data, Layanan, dan Dashboard&lt;/h3&gt;
&lt;p&gt;Agar artikel ini tidak berakhir di level konsep, kita bikin arsitektur yang realistis untuk tim kecil: satu API, satu database, dan satu layer agregasi untuk dashboard. Targetnya sederhana: &lt;strong&gt;dashboard monitoring proyek konstruksi&lt;/strong&gt; yang dapat menampilkan progres, ringkasan biaya, serta laporan harian.&lt;/p&gt;
&lt;h4 id=&quot;komponen-arsitektur&quot;&gt;Komponen Arsitektur&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;FastAPI&lt;/strong&gt;: pintu masuk data (input) dan penyedia data (output).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;PostgreSQL&lt;/strong&gt;: sumber data terstruktur.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Worker&lt;/strong&gt; (opsional): untuk sinkronisasi/ETL ringan (mis. impor spreadsheet, parsing foto metadata).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Frontend dashboard&lt;/strong&gt;: bisa React/Vue/Metabase/Superset—bebas. Fokus artikel ini di API.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;tabel-data-yang-perlu-dikumpulkan-versi-praktis-&quot;&gt;Tabel: Data yang Perlu Dikumpulkan (Versi Praktis)&lt;/h4&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Domain      &lt;/th&gt;&lt;th&gt;Contoh data                   &lt;/th&gt;&lt;th&gt;Sumber umum               &lt;/th&gt;&lt;th&gt;Update          &lt;/th&gt;&lt;th&gt;Risiko jika tidak rapi&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Progres     &lt;/td&gt;&lt;td&gt;volume, bobot item, % capaian &lt;/td&gt;&lt;td&gt;laporan harian, checklist &lt;/td&gt;&lt;td&gt;harian          &lt;/td&gt;&lt;td&gt;% “ngambang”, sulit audit&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Biaya (RAB) &lt;/td&gt;&lt;td&gt;budget item, realisasi        &lt;/td&gt;&lt;td&gt;RAB, invoice, PO          &lt;/td&gt;&lt;td&gt;mingguan/harian &lt;/td&gt;&lt;td&gt;cost overrun terlambat ketahuan&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Material    &lt;/td&gt;&lt;td&gt;jenis, kuantitas, lokasi      &lt;/td&gt;&lt;td&gt;delivery note, stok       &lt;/td&gt;&lt;td&gt;harian          &lt;/td&gt;&lt;td&gt;shortage mendadak&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Alat berat  &lt;/td&gt;&lt;td&gt;jam kerja, downtime           &lt;/td&gt;&lt;td&gt;log operator/telemetri    &lt;/td&gt;&lt;td&gt;harian          &lt;/td&gt;&lt;td&gt;produktivitas turun tanpa sebab&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h3 id=&quot;3-model-data-skema-yang-tidak-membuat-anda-menyesal&quot;&gt;3. Model Data: Skema yang Tidak Membuat Anda Menyesal&lt;/h3&gt;
&lt;p&gt;Kunci &lt;em&gt;dashboard&lt;/em&gt; bukan hanya query, tetapi skema yang memudahkan agregasi. Anda ingin bisa bertanya: “proyek A minggu ini progresnya berapa, biaya lari berapa, dan top 3 item penyebab deviasi?”&lt;/p&gt;
&lt;h4 id=&quot;entitas-inti-yang-disarankan&quot;&gt;Entitas Inti yang Disarankan&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;projects&lt;/strong&gt;: metadata proyek.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;work_items&lt;/strong&gt;: item pekerjaan (berat/bobot).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;daily_reports&lt;/strong&gt;: catatan harian per proyek.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;daily_progress&lt;/strong&gt;: detail progres per item per hari.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;budget_items&lt;/strong&gt;: struktur RAB.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;cost_ledger&lt;/strong&gt;: transaksi biaya (aktual).&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;tip-praktis&quot;&gt;Tip Praktis&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Simpan &lt;strong&gt;unit&lt;/strong&gt; dan &lt;strong&gt;koefisien&lt;/strong&gt; di &lt;code&gt;work_items&lt;/code&gt; agar konversi tidak dilakukan di sisi frontend.&lt;/li&gt;
&lt;li&gt;Pisahkan “header laporan” dan “detail progres” agar update parsial tidak menyiksa.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;4-implementasi-fastapi-endpoint-yang-ramah-dashboard&quot;&gt;4. Implementasi FastAPI: Endpoint yang Ramah Dashboard&lt;/h3&gt;
&lt;p&gt;Di bab ini, kita turunkan konsep jadi endpoint yang bisa langsung dimakan UI. Setiap endpoint harus punya tujuan yang jelas: input data atau output agregasi.&lt;/p&gt;
&lt;h4 id=&quot;endpoint-minimum-mvp-&quot;&gt;Endpoint Minimum (MVP)&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;POST /v1/projects&lt;/code&gt; — membuat proyek.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;POST /v1/daily-reports&lt;/code&gt; — input laporan harian.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;GET /v1/projects/{id}/dashboard&lt;/code&gt; — agregasi untuk dashboard.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;GET /v1/projects/{id}/progress?from=...&amp;amp;to=...&lt;/code&gt; — time series progres.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;GET /v1/projects/{id}/cost-summary&lt;/code&gt; — ringkasan biaya vs RAB.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;pola-response-untuk-agregasi-dashboard&quot;&gt;Pola Response untuk Agregasi Dashboard&lt;/h4&gt;
&lt;p&gt;Gunakan response yang “siap render”, contoh:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;kpis&lt;/code&gt;: progres total, deviasi biaya, burn rate.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;charts&lt;/code&gt;: series harian/mingguan.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;alerts&lt;/code&gt;: item deviasi paling besar.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;&lt;p&gt;Pro tip: untuk endpoint agregasi, simpan query berat di layer service + caching. Dashboard yang lambat = dashboard yang ditinggal.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4 id=&quot;catatan-tentang-background-task&quot;&gt;Catatan tentang Background Task&lt;/h4&gt;
&lt;p&gt;Kalau Anda punya proses impor (mis. upload Excel RAB), Anda bisa pakai background task. Untuk inspirasi &lt;em&gt;visibility&lt;/em&gt; task, lihat &lt;a rel=&quot;nofollow&quot; href=&quot;https://coderlegion.com/5893/fastapi-bgtasks-dashboard&quot;&gt;fastapi-bgtasks-dashboard&lt;/a&gt; di CoderLegion.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;5-how-to-dari-nol-ke-dashboard-data-ready-step-by-step-&quot;&gt;5. How-To: Dari Nol ke Dashboard Data-Ready (Step-by-Step)&lt;/h3&gt;
&lt;p&gt;Bab ini dibuat sengaja “taktis”, supaya Anda bisa meniru alurnya lalu menyesuaikan ke real project.&lt;/p&gt;
&lt;h4 id=&quot;langkah-1-definisikan-kpi-yang-disepakati-di-lapangan&quot;&gt;Langkah 1 — Definisikan KPI yang Disepakati di Lapangan&lt;/h4&gt;
&lt;p&gt;Checklist KPI yang biasanya relevan:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Progress plan vs actual (harian/mingguan)&lt;/li&gt;
&lt;li&gt;Cost actual vs budget (per item &amp;amp; total)&lt;/li&gt;
&lt;li&gt;Produktivitas alat berat (jam operasi vs downtime)&lt;/li&gt;
&lt;li&gt;Lead time material (PO → datang)&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;langkah-2-normalisasi-input-laporan-harian&quot;&gt;Langkah 2 — Normalisasi Input Laporan Harian&lt;/h4&gt;
&lt;p&gt;Buat form digital (mobile-friendly). Minimal:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;tanggal, cuaca, shift&lt;/li&gt;
&lt;li&gt;item pekerjaan + volume&lt;/li&gt;
&lt;li&gt;tenaga kerja&lt;/li&gt;
&lt;li&gt;alat berat (jenis + jam)&lt;/li&gt;
&lt;li&gt;material masuk&lt;/li&gt;
&lt;li&gt;catatan kendala&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;langkah-3-bangun-api-kontrak-yang-stabil&quot;&gt;Langkah 3 — Bangun API Kontrak yang Stabil&lt;/h4&gt;
&lt;p&gt;Gunakan:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;validasi ketat (Pydantic)&lt;/li&gt;
&lt;li&gt;idempotency key untuk submit ulang&lt;/li&gt;
&lt;li&gt;error format konsisten&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;langkah-4-buat-endpoint-agregasi-untuk-ui&quot;&gt;Langkah 4 — Buat Endpoint Agregasi untuk UI&lt;/h4&gt;
&lt;p&gt;Hindari “frontend mengolah semua data mentah”. UI sebaiknya konsumsi:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;KPI ringkas&lt;/li&gt;
&lt;li&gt;data chart&lt;/li&gt;
&lt;li&gt;daftar deviasi&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;langkah-5-terapkan-audit-trail&quot;&gt;Langkah 5 — Terapkan Audit Trail&lt;/h4&gt;
&lt;p&gt;Konstruksi itu dunia audit. Simpan:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;created_by&lt;/code&gt;, &lt;code&gt;created_at&lt;/code&gt;, &lt;code&gt;source&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;histori revisi laporan&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;6-praktik-lapangan-data-material-amp-alat-berat-biar-tidak-cuma-jadi-angka&quot;&gt;6. Praktik Lapangan: Data Material &amp;amp; Alat Berat Biar Tidak Cuma Jadi Angka&lt;/h3&gt;
&lt;p&gt;Di proyek nyata, suplai material alam (tanah, batu, basecourse) dan rental alat berat sering jadi titik kebocoran biaya jika pencatatan tidak disiplin. &lt;strong&gt;Dashboard monitoring proyek konstruksi&lt;/strong&gt; yang bagus harus bisa menjawab pertanyaan sederhana: “berapa yang masuk, dipakai untuk apa, dan dampaknya ke progres?”&lt;/p&gt;
&lt;h4 id=&quot;material-hindari-masuk-banyak-hilang-pelan-pelan-&quot;&gt;Material: Hindari “Masuk Banyak, Hilang Pelan-Pelan”&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Wajibkan nomor dokumen (delivery note/DO)&lt;/li&gt;
&lt;li&gt;Catat lokasi drop dan penerima&lt;/li&gt;
&lt;li&gt;Cocokkan volume dengan progres work item terkait&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;alat-berat-jam-kerja-yang-mengubah-narasi-biaya&quot;&gt;Alat Berat: Jam Kerja yang Mengubah Narasi Biaya&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Catat jam operasi, jam standby, downtime&lt;/li&gt;
&lt;li&gt;Simpan alasan downtime (menunggu material, breakdown, cuaca)&lt;/li&gt;
&lt;li&gt;Hubungkan ke produktivitas per work item&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;7-seo-friendly-admin-friendly-cara-menulis-agar-artikel-nempel-di-coderlegion&quot;&gt;7. SEO-Friendly, Admin-Friendly: Cara Menulis Agar Artikel “Nempel” di CoderLegion&lt;/h3&gt;
&lt;p&gt;CoderLegion menyukai tulisan yang terasa &lt;em&gt;hands-on&lt;/em&gt;, punya struktur jelas, dan tidak mengawang. Agar konten Anda tidak tenggelam:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Buka dengan problem nyata + solusi teknis.&lt;/li&gt;
&lt;li&gt;Sisipkan tabel/diagram alur.&lt;/li&gt;
&lt;li&gt;Tambahkan FAQ yang menjawab keberatan umum.&lt;/li&gt;
&lt;li&gt;Sertakan &lt;em&gt;HowTo&lt;/em&gt; (seperti di atas) agar pembaca cepat praktik.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Dan yang paling penting: ulangi istilah utama secukupnya. Di artikel ini, &lt;strong&gt;dashboard monitoring proyek konstruksi&lt;/strong&gt; sengaja dipakai beberapa kali sebagai konteks, bukan sekadar pengulangan.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;faq-1&quot;&gt;FAQ&lt;/h3&gt;
&lt;h4 id=&quot;1-apakah-dashboard-harus-dibuat-dengan-react-&quot;&gt;1) Apakah dashboard harus dibuat dengan React?&lt;/h4&gt;
&lt;p&gt;Tidak. Anda bisa memakai BI tools (Metabase/Superset) selama API Anda rapi. Yang penting adalah kontrak data.&lt;/p&gt;
&lt;h4 id=&quot;2-bagaimana-kalau-rab-masih-berupa-excel-&quot;&gt;2) Bagaimana kalau RAB masih berupa Excel?&lt;/h4&gt;
&lt;p&gt;Mulai dari impor terstruktur: mapping kolom → &lt;code&gt;budget_items&lt;/code&gt;, lalu simpan versi (v1, v2) agar revisi RAB tidak merusak histori.&lt;/p&gt;
&lt;h4 id=&quot;3-data-di-lapangan-sering-tidak-lengkap-apa-yang-dilakukan-&quot;&gt;3) Data di lapangan sering tidak lengkap. Apa yang dilakukan?&lt;/h4&gt;
&lt;p&gt;Mulai dari “minimum viable reporting”: 5–7 field wajib. Lalu tambahkan disiplin lewat validasi dan &lt;em&gt;feedback loop&lt;/em&gt; (dashboard menampilkan data kosong sebagai alert).&lt;/p&gt;
&lt;h4 id=&quot;4-apakah-butuh-iot-untuk-alat-berat-&quot;&gt;4) Apakah butuh IoT untuk alat berat?&lt;/h4&gt;
&lt;p&gt;Tidak wajib. Tapi kalau ada telemetri, simpan sebagai event time series dan agregasikan per shift/hari.&lt;/p&gt;
&lt;h4 id=&quot;5-apa-metrik-paling-cepat-memberi-dampak-&quot;&gt;5) Apa metrik paling cepat memberi dampak?&lt;/h4&gt;
&lt;p&gt;Progress vs biaya per item pekerjaan. Itu paling cepat memunculkan deviasi yang bisa ditindak.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;closing-dashboard-yang-bekerja-untuk-keputusan-bukan-sekadar-tampilan&quot;&gt;Closing: Dashboard yang Bekerja untuk Keputusan, Bukan Sekadar Tampilan&lt;/h3&gt;
&lt;p&gt;Sebagai penutup, ada kutipan yang relevan untuk budaya engineering yang berujung pada kualitas sistem dan data: &lt;strong&gt;“You build it, you run it.”&lt;/strong&gt; — sebuah prinsip yang sering dikaitkan dengan &lt;strong&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://en.wikipedia.org/wiki/Werner_Vogels&quot;&gt;Werner Vogels&lt;/a&gt;&lt;/strong&gt;, CTO Amazon yang dikenal luas di ranah &lt;em&gt;distributed systems&lt;/em&gt; dan &lt;em&gt;cloud architecture&lt;/em&gt;. Artinya dalam bahasa Indonesia: &lt;strong&gt;“Kamu yang membangun, kamu juga yang menjalankan.”&lt;/strong&gt; Dalam konteks artikel ini, maknanya sederhana: kalau tim membangun API dan dashboard, tim yang sama harus peduli pada kualitas data, performa endpoint, dan dampaknya pada operasi proyek—karena di sanalah &lt;em&gt;feedback loop&lt;/em&gt; yang membuat &lt;strong&gt;dashboard monitoring proyek konstruksi&lt;/strong&gt; benar-benar dipakai.&lt;/p&gt;
&lt;p&gt;Jika Anda ingin melihat bagaimana pendekatan ini dipakai di proyek nyata—mulai dari konstruksi sipil/struktur, MEP, suplai material alam, sampai rental alat berat—kami membuka ruang diskusi dan kolaborasi. Kunjungi profil perusahaan kami di &lt;strong&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://www.abidarmasejahtra.co.id/&quot;&gt;PT Abi Darma Sejahtra&lt;/a&gt;&lt;/strong&gt; untuk melihat layanan dan portofolio.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &quot;@context&quot;: &quot;https://schema.org&quot;,
  &quot;@type&quot;: &quot;HowTo&quot;,
  &quot;name&quot;: &quot;Membangun Dashboard Monitoring Proyek Konstruksi dengan Python + FastAPI&quot;,
  &quot;description&quot;: &quot;Panduan membangun API berbasis FastAPI untuk menyuplai data progres, RAB, dan laporan harian ke dashboard monitoring proyek konstruksi.&quot;,
  &quot;totalTime&quot;: &quot;PT6H&quot;,
  &quot;supply&quot;: [
    {&quot;@type&quot;: &quot;HowToSupply&quot;, &quot;name&quot;: &quot;Dataset laporan harian (CSV/Google Form/Spreadsheet)&quot;},
    {&quot;@type&quot;: &quot;HowToSupply&quot;, &quot;name&quot;: &quot;Struktur RAB (Excel/CSV)&quot;}
  ],
  &quot;tool&quot;: [
    {&quot;@type&quot;: &quot;HowToTool&quot;, &quot;name&quot;: &quot;Python 3.10+&quot;},
    {&quot;@type&quot;: &quot;HowToTool&quot;, &quot;name&quot;: &quot;FastAPI&quot;},
    {&quot;@type&quot;: &quot;HowToTool&quot;, &quot;name&quot;: &quot;PostgreSQL&quot;}
  ],
  &quot;step&quot;: [
    {
      &quot;@type&quot;: &quot;HowToStep&quot;,
      &quot;name&quot;: &quot;Definisikan KPI proyek&quot;,
      &quot;text&quot;: &quot;Tetapkan KPI yang disepakati (progress plan vs actual, biaya vs RAB, produktivitas alat, lead time material) agar dashboard punya tujuan yang jelas.&quot;,
      &quot;url&quot;: &quot;https://www.abidarmasejahtra.co.id/&quot;
    },
    {
      &quot;@type&quot;: &quot;HowToStep&quot;,
      &quot;name&quot;: &quot;Normalisasi format laporan harian&quot;,
      &quot;text&quot;: &quot;Buat format input minimal yang konsisten: tanggal, shift, item pekerjaan, volume, alat, material, dan kendala. Terapkan validasi agar data siap diproses.&quot;,
      &quot;url&quot;: &quot;https://coderlegion.com/11707/best-practices-for-restful-api-design-for-dev-tools&quot;
    },
    {
      &quot;@type&quot;: &quot;HowToStep&quot;,
      &quot;name&quot;: &quot;Bangun kontrak API versi pertama&quot;,
      &quot;text&quot;: &quot;Rancang endpoint resource-oriented, gunakan versioning (/v1), format error konsisten, dan idempotency key untuk menghindari duplikasi submit.&quot;,
      &quot;url&quot;: &quot;https://coderlegion.com/4597/versioning-your-api-the-right-way-rest-best-practices&quot;
    },
    {
      &quot;@type&quot;: &quot;HowToStep&quot;,
      &quot;name&quot;: &quot;Buat endpoint agregasi dashboard&quot;,
      &quot;text&quot;: &quot;Sediakan endpoint khusus agregasi KPI, time series chart, dan daftar deviasi agar frontend tidak mengolah data mentah secara berlebihan.&quot;,
      &quot;url&quot;: &quot;https://cmicglobal.com/resources/article/construction-software-integrations-trends&quot;
    },
    {
      &quot;@type&quot;: &quot;HowToStep&quot;,
      &quot;name&quot;: &quot;Tambahkan audit trail dan kontrol kualitas data&quot;,
      &quot;text&quot;: &quot;Simpan created_by/created_at, histori revisi laporan, dan lakukan pemeriksaan silang volume material vs progres. Gunakan caching untuk respons dashboard yang cepat.&quot;,
      &quot;url&quot;: &quot;https://www.sciencedirect.com/science/article/pii/S1474034625004045&quot;
    }
  ]
}
&lt;/code&gt;&lt;/pre&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/12111/membangun-dashboard-monitoring-proyek-konstruksi-dengan-python-fastapi-progress-lapor</guid>
<pubDate>Tue, 24 Feb 2026 03:52:49 +0000</pubDate>
</item>
<item>
<title>Building a Production-Ready Ecommerce Backend with FastAPI</title>
<link>https://coderlegion.com/9935/building-a-production-ready-ecommerce-backend-with-fastapi</link>
<description>&lt;p&gt;When learning backend development, most projects stop at CRUD APIs.&lt;br&gt;
I wanted to go a step further and build a realistic ecommerce backend—one that follows industry practices, not shortcuts.&lt;/p&gt;
&lt;p&gt;This blog walks through what I built, why I built it that way, and the key backend concepts involved.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Project Goal&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;To design a secure, scalable ecommerce backend that handles:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Authentication&lt;/li&gt;
&lt;li&gt;Cart management&lt;/li&gt;
&lt;li&gt;Checkout flow&lt;/li&gt;
&lt;li&gt;Orders&lt;/li&gt;
&lt;li&gt;Payments&lt;/li&gt;
&lt;li&gt;Stock consistency&lt;/li&gt;
&lt;li&gt;Password recovery&lt;br&gt;
All while following real-world backend design principles.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Tech Stack&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;FastAPI – high-performance Python API framework&lt;/li&gt;
&lt;li&gt;SQLAlchemy – ORM for database modeling&lt;/li&gt;
&lt;li&gt;PostgreSQL – relational database&lt;/li&gt;
&lt;li&gt;Alembic – database migrations&lt;/li&gt;
&lt;li&gt;JWT – authentication&lt;/li&gt;
&lt;li&gt;bcrypt – password hashing&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Authentication &amp;amp; Security&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Passwords are never stored in plain text.&lt;br&gt;
Passwords are hashed using bcrypt&lt;br&gt;
JWT is used for authentication&lt;br&gt;
Password recovery is handled via secure, time-bound reset tokens&lt;br&gt;
No password is ever decrypted or sent back to users&lt;br&gt;
Password recovery is implemented by resetting, not revealing passwords.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Cart Design (Temporary State)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The cart is designed as a mutable, temporary state:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Users can add, update, or remove items&lt;/li&gt;
&lt;li&gt;Only one active cart per user&lt;/li&gt;
&lt;li&gt;Prices are snapshotted at the time of adding to cart&lt;/li&gt;
&lt;li&gt;This prevents price mismatch issues during checkout.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Checkout (State Transition)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Checkout is a critical boundary:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Converts a cart into an immutable order&lt;/li&gt;
&lt;li&gt;Validates stock&lt;/li&gt;
&lt;li&gt;Calculates final price&lt;/li&gt;
&lt;li&gt;Creates a PENDING order&lt;/li&gt;
&lt;li&gt;Cart represents intent.&lt;/li&gt;
&lt;li&gt;Checkout represents commitment.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Orders (Immutable Records)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Orders are treated as financial records:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Created only via checkout &lt;/li&gt;
&lt;li&gt;Read-only for users&lt;/li&gt;
&lt;li&gt;Status-based lifecycle: PENDING → PAID → CANCELLED&lt;/li&gt;
&lt;li&gt;This ensures auditability and consistency.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Payments (Asynchronous &amp;amp; Safe)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Payments are handled separately from checkout:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Payment intents are created for orders&lt;/li&gt;
&lt;li&gt;Payments are confirmed asynchronously&lt;/li&gt;
&lt;li&gt;Stock is reduced only after payment success&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This avoids overselling and handles real payment gateway behavior.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Stock Management&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Stock updates are protected using row-level locking to avoid race conditions during concurrent purchases.&lt;/p&gt;
&lt;p&gt;Stock is reduced only after confirmed payment—not during checkout.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;API Documentation&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Swagger (OpenAPI) is fully integrated:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Cleanly grouped APIs&lt;/li&gt;
&lt;li&gt;Request &amp;amp; response schemas&lt;/li&gt;
&lt;li&gt;JWT authorization support&lt;/li&gt;
&lt;li&gt;Ready for frontend or third-party integration&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Database Migrations&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;All schema changes are managed via Alembic migrations&lt;/li&gt;
&lt;li&gt;No manual DB edits&lt;/li&gt;
&lt;li&gt;Reproducible environments&lt;/li&gt;
&lt;li&gt;Safe schema evolution&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://coderlegion.com/?qa=blob&amp;amp;qa_blobid=6023982787907800547&quot; alt=&quot;&quot;&gt;&lt;br&gt;
&lt;strong&gt;What I Learned&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Why carts and orders must be separate&lt;/li&gt;
&lt;li&gt;Why payments must be asynchronous&lt;/li&gt;
&lt;li&gt;Why passwords must be irreversible&lt;/li&gt;
&lt;li&gt;How ORM relationships fail if not symmetric&lt;/li&gt;
&lt;li&gt;How real-world ecommerce systems prevent overselling&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Source Code&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The complete project is available on GitHub:&lt;br&gt;
 &lt;em&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/sanjay-dot/Ecommerce&quot;&gt;https://github.com/sanjay-dot/Ecommerce&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Final Thoughts&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;This project helped me move from API writing to system thinking.&lt;/p&gt;
&lt;p&gt;If you’re learning backend development, I highly recommend building projects that:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;model real workflows&lt;/li&gt;
&lt;li&gt;enforce correct state transitions&lt;/li&gt;
&lt;li&gt;prioritize security from day one&lt;/li&gt;
&lt;/ul&gt;
</description>
<category>Tutorials</category>
<guid isPermaLink="true">https://coderlegion.com/9935/building-a-production-ready-ecommerce-backend-with-fastapi</guid>
<pubDate>Sun, 18 Jan 2026 08:03:04 +0000</pubDate>
</item>
<item>
<title>fastapi-bgtasks-dashboard</title>
<link>https://coderlegion.com/5893/fastapi-bgtasks-dashboard</link>
<description>&lt;p&gt;One of the great things about FastAPI is how easily you can offload work to background tasks with BackgroundTasks. But once tasks are fired off, they often disappear into a black box and you don’t know if they’re still running, when they’ll finish, or whether they failed silently.&lt;/p&gt;
&lt;p&gt;That’s where fastapi-bgtasks-dashboard comes in. It’s a lightweight, plug-and-play dashboard SDK that gives FastAPI developers real-time visibility into their background tasks. With just a one-liner, you can embed a live dashboard in your app and instantly see what your background workers are doing.&lt;/p&gt;
&lt;p&gt;Transparency – Monitor queued, running, and completed tasks at a glance.&lt;br&gt;
Zero overhead – Designed to be minimal and fast, so it won’t slow down your API.&lt;br&gt;
Plug-and-play – Simple integration with your existing FastAPI project.&lt;br&gt;
Developer-friendly – Debug issues and optimize background job handling in development or production.&lt;/p&gt;
&lt;p&gt;This tool was born out of real-world needs while building scalable financial backends. Developers often struggled with invisible background jobs, making debugging painful. By adding this dashboard, your team gets confidence and control over background processing without adding Redis, Celery, or other heavyweight dependencies.&lt;/p&gt;
&lt;p&gt;I’m sharing this project with the FastAPI ecosystem because I believe observability should be simple. If you find fastapi-bgtasks-dashboard useful:&lt;/p&gt;
&lt;p&gt;⭐ Give it a star on GitHub to help others discover it.&lt;br&gt;
 Contribute ideas, bug fixes, or new features.&lt;br&gt;
 Consider sponsoring to support ongoing improvements and maintenance.&lt;/p&gt;
&lt;p&gt;Together, we can make the FastAPI ecosystem even more developer-friendly by solving everyday pain points in a clean, open-source way.&lt;/p&gt;
</description>
<category>APIs &amp; SDKs</category>
<guid isPermaLink="true">https://coderlegion.com/5893/fastapi-bgtasks-dashboard</guid>
<pubDate>Thu, 02 Oct 2025 10:33:13 +0000</pubDate>
</item>
<item>
<title>FastAPI Error Handling in Python: Unify Responses with APIException</title>
<link>https://coderlegion.com/4932/fastapi-error-handling-in-python-unify-responses-with-apiexception</link>
<description>&lt;blockquote&gt;&lt;p&gt;[SPOILER ALERT] Watch how a messy error handling system transforms into a clean, unified API in under a minute.  &lt;a rel=&quot;nofollow&quot; href=&quot;https://youtu.be/pCBelB8DMCc&quot;&gt;Quick Demo Video&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h3 id=&quot;apiexception-from-chaos-to-clean-predictable-api-responses-&quot;&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/akutayural/APIException&quot;&gt;APIException&lt;/a&gt;: from chaos to clean, predictable API responses.&lt;/h3&gt;
&lt;p&gt;While working with &lt;strong&gt;FastAPI&lt;/strong&gt; for quite a long time, I kept running into a couple of annoying problems that occurred in almost all projects I started with FastAPI.&lt;/p&gt;
&lt;p&gt;Don’t get me wrong because of what I said above; I’m a big FastAPI fan and have been using it for over 4 years in almost all my projects.  &lt;/p&gt;
&lt;p&gt;It’s still my go-to framework for backend development. But like any tool, there are small pain points that start to pile up over time.&lt;/p&gt;
&lt;p&gt;Nothing major at first glance, but these issues caused headaches for frontend integration, documentation, and even debugging.&lt;br&gt;
Let’s break them down one by one.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;problem-1-inconsistent-response-formats&quot;&gt;Problem 1 – Inconsistent Response Formats&lt;/h3&gt;
&lt;p&gt;Anyone who has spent some time with FastAPI knows that, by default, responses vary depending on the error type:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;// Endpoint 1 error
{&quot;error&quot;: &quot;User not found&quot;}

// Endpoint 2 validation [422] error
{&quot;detail&quot;: [{&quot;loc&quot;: [&quot;body&quot;, &quot;name&quot;], &quot;msg&quot;: &quot;field required&quot;}]}

// Unhandled exception
&amp;lt;html&amp;gt;...HTML error page...&amp;lt;/html&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This means:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;422 Validation errors return Pydantic’s default format&lt;/li&gt;
&lt;li&gt;HTTPExceptions have a different JSON structure&lt;/li&gt;
&lt;li&gt;Unhandled exceptions may even return HTML&lt;/li&gt;
&lt;li&gt;Also, they don’t look great on Swagger. Some aren’t even documented by default.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This is frustrating for API consumers because they need to handle multiple formats for different error types. &lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;problem-2-registering-exception-handlers-in-every-app-is-a-pain&quot;&gt;Problem 2 – Registering Exception Handlers in Every App is a Pain&lt;/h3&gt;
&lt;p&gt;If you work on &lt;strong&gt;multiple projects&lt;/strong&gt; at the same time, you quickly realise that setting up everything you’ve already done in previous projects for each new one &lt;strong&gt;gets old fast&lt;/strong&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from fastapi import FastAPI, HTTPException, Request
from fastapi.responses import JSONResponse
from fastapi.exceptions import RequestValidationError
import logging

logger = logging.getLogger(__name__)

app = FastAPI()

@app.exception_handler(HTTPException)
async def http_exception_handler(request: Request, exc: HTTPException):
    logger.error(f&quot;HTTPException: {exc.detail}&quot;)
    return JSONResponse(
        status_code=exc.status_code,
        content={&quot;error&quot;: exc.detail},
    )

@app.exception_handler(ValueError)
async def value_error_handler(request: Request, exc: ValueError):
    logger.error(f&quot;ValueError: {str(exc)}&quot;)
    return JSONResponse(
        status_code=400,
        content={&quot;error&quot;: str(exc)},
    )

@app.exception_handler(RequestValidationError)
async def validation_exception_handler(request: Request, exc: RequestValidationError):
    logger.error(f&quot;Validation error: {exc.errors()}&quot;)
    return JSONResponse(
        status_code=422,
        content={&quot;error&quot;: exc.errors()},
    )
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For &lt;strong&gt;every app&lt;/strong&gt; you build, you have to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Register exception handlers  &lt;/li&gt;
&lt;li&gt;Ensure the response format remains consistent across all exceptions  &lt;/li&gt;
&lt;li&gt;Repeat the same boilerplate code over and over again&lt;br&gt;
And yes... It's boring, and I &lt;strong&gt;hate&lt;/strong&gt; it.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;problem-3-the-repetitive-code-you-keep-writing-in-every-endpoint-at-least-once&quot;&gt;Problem 3 – The Repetitive Code You Keep Writing in Every Endpoint At Least Once&lt;/h3&gt;
&lt;p&gt;In many FastAPI projects, I often see (and used to write) error handling like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;if some_event:
    logger.error(&quot;User not found&quot;)
    raise HTTPException(detail=&quot;User not found&quot;, status_code=404)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I believe that every exception should be logged in most projects, unless you have intentionally configured it not to log a specific error.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-goal&quot;&gt;The Goal&lt;/h3&gt;
&lt;p&gt;In short, the goal is to solve the problems described above using fewer lines of code while maintaining the same functionality, adding no extra overhead, and causing no performance issues.&lt;/p&gt;
&lt;p&gt;I wanted a solution that would:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A single &lt;strong&gt;unified&lt;/strong&gt; response schema for all cases (success or exception/error)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Swagger/OpenAPI docs&lt;/strong&gt; to reflect all possible responses&lt;/li&gt;
&lt;li&gt;Unhandled exceptions to return clean JSON&lt;/li&gt;
&lt;li&gt;Automatic logging unless you explicitly disable it&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;solutions&quot;&gt;Solutions&lt;/h3&gt;
&lt;p&gt;I've created a Python package called &lt;strong&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/akutayural/APIException&quot;&gt;APIException&lt;/a&gt;&lt;/strong&gt; that includes solutions for problems described above and more... &lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://akutayural.github.io/APIException/&quot;&gt;Documentation&lt;/a&gt;&lt;/strong&gt; is clear and suggested.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;installation-1&quot;&gt;Installation&lt;/h3&gt;
&lt;p&gt;You can install it directly via &lt;a rel=&quot;nofollow&quot; href=&quot;https://pypi.org/project/apiexception/&quot;&gt;pip&lt;/a&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;pip install apiexception  
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And you can import it:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import api_exception
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Once you have it installed, you're good to go.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;1-the-solution-for-problem---1-unified-responsemodel&quot;&gt;1) The Solution for Problem - 1: Unified &lt;em&gt;ResponseModel&lt;/em&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;One of the biggest things I wanted&lt;/strong&gt; was for &lt;strong&gt;every single API response&lt;/strong&gt;, whether it’s a success or an error, to have the &lt;strong&gt;exact same JSON format&lt;/strong&gt;.&lt;br&gt;
So, here’s what I did: I created a &lt;strong&gt;ResponseModel&lt;/strong&gt; that &lt;strong&gt;every single endpoint&lt;/strong&gt; and &lt;strong&gt;every single exception&lt;/strong&gt; in the app will use.&lt;/p&gt;
&lt;h4 id=&quot;why-do-i-think-that-using-responsemodel-is-awesome-&quot;&gt;Why do I think that using &lt;code&gt;ResponseModel&lt;/code&gt; is awesome?&lt;/h4&gt;
&lt;p&gt;When the response format is &lt;strong&gt;standardised&lt;/strong&gt; for both success and error cases, the frontend integration becomes a breeze.&lt;/p&gt;
&lt;p&gt;The frontend team always knows the API will return &lt;strong&gt;the same structure&lt;/strong&gt;, so after parsing the JSON, it can simply check the &lt;code&gt;status&lt;/code&gt; field:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;If&lt;/strong&gt; &lt;code&gt;status&lt;/code&gt; is &lt;code&gt;&quot;SUCCESS&quot;&lt;/code&gt; → grab the payload from the &lt;code&gt;data&lt;/code&gt; field.  &lt;/li&gt;
&lt;li&gt;&lt;strong&gt;If&lt;/strong&gt; &lt;code&gt;status&lt;/code&gt; is &lt;code&gt;&quot;FAIL&quot;&lt;/code&gt; → check the &lt;code&gt;error_code&lt;/code&gt;.  &lt;ul&gt;
&lt;li&gt;From there, the frontend can either use its own custom error message or use the &lt;code&gt;message&lt;/code&gt; or &lt;code&gt;description&lt;/code&gt; fields returned by the API.  &lt;/li&gt;
&lt;li&gt;The &lt;code&gt;description&lt;/code&gt; and &lt;code&gt;message&lt;/code&gt; fields can also serve different purposes in &lt;strong&gt;success&lt;/strong&gt; cases (for example, human-readable info or debugging context).&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;example-before-amp-after-using-responsemodel-in-fastapi-router-s-response-model&quot;&gt;Example: Before &amp;amp; After using &lt;code&gt;ResponseModel&lt;/code&gt; in FastAPI router's &lt;code&gt;response_model&lt;/code&gt;&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;Before:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{&quot;error&quot;: &quot;User not found&quot;}
{&quot;detail&quot;: [{&quot;loc&quot;: [&quot;body&quot;, &quot;name&quot;], &quot;msg&quot;: &quot;field required&quot;}]}
&amp;lt;html&amp;gt;...HTML error page...&amp;lt;/html&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;After – Error Case (APIException)&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &quot;data&quot;: null,
  &quot;status&quot;: &quot;FAIL&quot;,
  &quot;error_code&quot;: &quot;USR-404&quot;,
  &quot;message&quot;: &quot;User not found.&quot;,
  &quot;description&quot;: &quot;The user ID does not exist.&quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;After – Error Case (Unhandled Exception)&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &quot;data&quot;: null,
  &quot;status&quot;: &quot;FAIL&quot;,
  &quot;error_code&quot;: &quot;SRV-500&quot;,
  &quot;message&quot;: &quot;Internal Server Error&quot;,
  &quot;description&quot;: &quot;An unexpected error occurred.&quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;After – Success Case&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &quot;data&quot;: {
    &quot;id&quot;: 1,
    &quot;name&quot;: &quot;John Doe&quot;
  },
  &quot;status&quot;: &quot;SUCCESS&quot;,
  &quot;error_code&quot;: null,
  &quot;message&quot;: &quot;Operation completed successfully.&quot;,
  &quot;description&quot;: &quot;User gathered successfully.&quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I believe that the above &lt;code&gt;ResponseModel&lt;/code&gt; described above is &lt;strong&gt;cleaner&lt;/strong&gt;, &lt;strong&gt;more consistent&lt;/strong&gt;, and makes things easier for both backend and frontend teams. The backend team will always know the &lt;code&gt;error_code&lt;/code&gt;, which makes it easier to find bugs from the logs, while the frontend team can handle responses much more efficiently. They no longer need to check if the &lt;code&gt;http_status_code&lt;/code&gt; is 20x and then parse the response. Instead, they can simply parse the response once, check the &lt;code&gt;status&lt;/code&gt; parameter to determine whether it is a success or error, and then look at either the &lt;code&gt;data&lt;/code&gt; parameter or the &lt;code&gt;error_code&lt;/code&gt; parameter accordingly.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;2-the-solution-for-problem---2-apiexception&quot;&gt;2) The Solution for Problem - 2: &lt;em&gt;APIException&lt;/em&gt;&lt;/h3&gt;
&lt;p&gt;Another major improvement I wanted was to avoid writing repetitive logging code in every endpoint before raising exceptions.&lt;br&gt;
With &lt;code&gt;APIException&lt;/code&gt;, &lt;strong&gt;all exceptions are automatically logged&lt;/strong&gt; unless you explicitly disable logging for a specific error.&lt;/p&gt;
&lt;p&gt;This means you can replace code like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;if not user:
    logger.error(&quot;User not found&quot;)
    raise HTTPException(status_code=404, detail=&quot;User not found&quot;)]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With a much cleaner and shorter version:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from api_exception import APIException, BaseExceptionCode

class CustomExceptionCode(BaseExceptionCode):
    USER_NOT_FOUND = (&quot;USR-404&quot;, &quot;User not found.&quot;, &quot;The user ID does not exist.&quot;)

if not user:
    raise APIException(ExceptionCode.USER_NOT_FOUND)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Using &lt;code&gt;APIException&lt;/code&gt; together with a &lt;code&gt;CustomExceptionCode&lt;/code&gt; that extends your &lt;code&gt;BaseExceptionCode&lt;/code&gt; not only makes logs easier to read, but also creates a reusable structure for handling repeated exception messages.  When you used &lt;code&gt;APIException&lt;/code&gt; together with a &lt;code&gt;CustomExceptionCode&lt;/code&gt; it automatically provides the &lt;strong&gt;&lt;code&gt;error_code&lt;/code&gt;&lt;/strong&gt;, &lt;strong&gt;&lt;code&gt;message&lt;/code&gt;&lt;/strong&gt;, and &lt;strong&gt;&lt;code&gt;description&lt;/code&gt;&lt;/strong&gt; from there. So define your own custom exception class and make the backend more consistent in terms of logging and code repetition. &lt;/p&gt;
&lt;p&gt;When raised, &lt;code&gt;APIException&lt;/code&gt; will:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Automatically log the error with the provided &lt;strong&gt;&lt;code&gt;error_code&lt;/code&gt;&lt;/strong&gt;, &lt;strong&gt;&lt;code&gt;message&lt;/code&gt;&lt;/strong&gt;, and &lt;strong&gt;&lt;code&gt;description&lt;/code&gt;&lt;/strong&gt; &lt;/li&gt;
&lt;li&gt;Return a &lt;strong&gt;unified response format&lt;/strong&gt; (via &lt;code&gt;ResponseModel&lt;/code&gt;)  &lt;/li&gt;
&lt;li&gt;Optionally disable logging for certain exceptions (e.g., &lt;code&gt;log=False&lt;/code&gt; if you don’t want it logged) &lt;/li&gt;
&lt;li&gt;Optionally you can add extra log message by using &lt;code&gt;log_message: [str | dict]&lt;/code&gt; parameter. &lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;3-the-solution-for-problem---3-one-time-handler-registration-with-register-exception-handlers&quot;&gt;3) The Solution for Problem - 3: One-Time Handler Registration with &lt;em&gt;register_exception_handlers&lt;/em&gt;&lt;/h3&gt;
&lt;p&gt;Instead of adding exception handlers all over the place and duplicating &lt;code&gt;app.add_exception_handler(...)&lt;/code&gt; calls,&lt;br&gt;
I wanted a &lt;strong&gt;single entry point&lt;/strong&gt; to set up my API’s exception handling.&lt;br&gt;
That’s exactly what &lt;code&gt;register_exception_handlers&lt;/code&gt; does.&lt;/p&gt;
&lt;p&gt;When you call it, it:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Attaches APIException handlers&lt;/strong&gt; so all your &lt;code&gt;APIException&lt;/code&gt; raises return the same format.&lt;/li&gt;
&lt;li&gt;Optionally &lt;strong&gt;adds a fallback middleware&lt;/strong&gt; to catch &lt;em&gt;all unhandled exceptions&lt;/em&gt; (like runtime errors, DB failures, or 3rd-party API crashes).&lt;/li&gt;
&lt;li&gt;Ensures &lt;strong&gt;consistent logging&lt;/strong&gt;:  &lt;ul&gt;
&lt;li&gt;Always logs &lt;code&gt;error_code&lt;/code&gt;, &lt;code&gt;message&lt;/code&gt;, &lt;code&gt;description&lt;/code&gt;.  &lt;/li&gt;
&lt;li&gt;Can log full tracebacks for handled errors or only for unhandled ones.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Supports &lt;strong&gt;multiple response formats&lt;/strong&gt;:  &lt;ul&gt;
&lt;li&gt;&lt;code&gt;ResponseModel&lt;/code&gt; (your unified API response structure)  &lt;/li&gt;
&lt;li&gt;&lt;code&gt;RFC7807&lt;/code&gt; (Problem Details for HTTP APIs)  &lt;/li&gt;
&lt;li&gt;&lt;code&gt;dict&lt;/code&gt; (plain JSON dicts)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Can decide if &lt;code&gt;null&lt;/code&gt; fields are &lt;strong&gt;included in OpenAPI docs&lt;/strong&gt; or hidden.&lt;/li&gt;
&lt;li&gt;Centralizes all logging options in one place.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Why it’s great:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;I don’t have to remember to register handlers in every new project.&lt;/li&gt;
&lt;li&gt;I can switch response formats for the whole API in one line.&lt;/li&gt;
&lt;li&gt;Logging behavior is configurable without touching the actual endpoints.&lt;/li&gt;
&lt;li&gt;It keeps my project clean and DRY.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Example – minimal setup:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from fastapi import FastAPI
from api_exception import register_exception_handlers

app = FastAPI()
register_exception_handlers(app) 
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Example:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from fastapi import FastAPI
from api_exception import (
    register_exception_handlers,
    ResponseFormat,
    logger
)

app = FastAPI()
  #You can set different log levels. `default=&quot;WARNING&quot;`
logger.setLevel(&quot;DEBUG&quot;)

  #Default settings are provided below.

register_exception_handlers(
    app=app,
    response_format=ResponseFormat.RESPONSE_MODEL,
    use_fallback_middleware=True,
    log_traceback=True,
    log_traceback_unhandled_exception=True,
    include_null_data_field_in_openapi=True,

    # v0.2.0 new features 
    log_level=&quot;ERROR&quot;,                        # override verbosity for exception logs
    log_request_context=True,                 # include request headers in logs
    log_header_keys=(&quot;x-request-id&quot;, &quot;user-agent&quot;),
    extra_log_fields={&quot;service&quot;: &quot;user-api&quot;}, # inject structured metadata
    response_headers=(&quot;x-user-id&quot;,),          # echo back specific headers in response
)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;-copy-paste-example&quot;&gt;“Copy-Paste&quot; Example&lt;/h3&gt;
&lt;p&gt;Below is a minimal, end-to-end example showing:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;one unified &lt;code&gt;ResponseModel&lt;/code&gt; for success &lt;strong&gt;and&lt;/strong&gt; errors  &lt;/li&gt;
&lt;li&gt;global handler registration with &lt;code&gt;register_exception_handlers&lt;/code&gt; which let's you auto log, show tracebacks for both APIException and unhandled exceptions such as db or third party errors. Moreover, the &lt;code&gt;ResponseFormat&lt;/code&gt; will be &lt;code&gt;ResponseModel&lt;/code&gt;. &lt;/li&gt;
&lt;li&gt;a custom exception code set via &lt;code&gt;BaseExceptionCode&lt;/code&gt;  &lt;/li&gt;
&lt;li&gt;how unhandled exceptions are caught and shaped the same way&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from typing import List
from fastapi import FastAPI, Path
from pydantic import BaseModel, Field
from api_exception import (
    APIException,
    BaseExceptionCode,
    ResponseModel,
    register_exception_handlers,
    APIResponse,
    logger,
)

app = FastAPI()

 #1) Register once → consistent responses + logging everywhere
register_exception_handlers(app=app)

 # Optional: set a different log level for 'apiexception'
logger.setLevel(&quot;DEBUG&quot;)

 #2) Define your custom exception codes (clean logs, reusable messages)
class CustomExceptionCode(BaseExceptionCode):
    USER_NOT_FOUND = (&quot;USR-404&quot;, &quot;User not found.&quot;, &quot;The user ID does not exist.&quot;)
    INVALID_API_KEY = (&quot;API-401&quot;, &quot;Invalid API key.&quot;, &quot;Provide a valid API key.&quot;)
    PERMISSION_DENIED = (&quot;PERM-403&quot;, &quot;Permission denied.&quot;, &quot;Access to this resource is forbidden.&quot;)

 #3) Your domain models
class UserModel(BaseModel):
    id: int = Field(...)
    username: str = Field(...)

class UserResponse(BaseModel):
    users: List[UserModel] = Field(..., description=&quot;List of user objects&quot;)

 #4) An endpoint that returns unified responses in all cases
@app.get(
    &quot;/user/{user_id}&quot;,
    response_model=ResponseModel[UserResponse],
    responses=APIResponse.default(),
)
async def user(user_id: int = Path(..., description=&quot;The ID of the user&quot;)):
    # Handled error example (uses your code+message+description and logs it)
    if user_id == 1:
        raise APIException(
            error_code=CustomExceptionCode.USER_NOT_FOUND,
            http_status_code=404,  # or 401 if you prefer
        )

    # Unhandled error example (still unified + logged)
    if user_id == 3:
        a = 1
        b = 0
        c = a / b   # ZeroDivisionError,caught by fallback, same JSON shape
        return c

    # Success case (same structure, frontend always reads status+data)
    users = [
        UserModel(id=1, username=&quot;John Doe&quot;),
        UserModel(id=2, username=&quot;Jane Smith&quot;),
        UserModel(id=3, username=&quot;Alice Johnson&quot;),
    ]
    data = UserResponse(users=users)
    return ResponseModel[UserResponse](
        data=data,
        description=&quot;User found and returned.&quot;,
    )

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After running the above app, you will have the swagger documentation as in the video below.&lt;/p&gt;
&lt;p&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://youtu.be/pCBelB8DMCc&quot;&gt;Click to Watch The Video&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;When the app &lt;code&gt;raise APIException&lt;/code&gt;, the exception will be logged as below &lt;u&gt;&lt;em&gt;by default&lt;/em&gt;&lt;/u&gt;: &lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0v37lo9ncbhrk552pkbi.png&quot; alt=&quot;APIException Log&quot;&gt;&lt;/p&gt;
&lt;p&gt;When the app raise unexpected error such as a &lt;strong&gt;ZeroDivisionError&lt;/strong&gt;, a &lt;strong&gt;database connection failure&lt;/strong&gt;, or a &lt;strong&gt;third-party API timeout&lt;/strong&gt;, the exception will be logged as below &lt;u&gt;&lt;em&gt;by default&lt;/em&gt;&lt;/u&gt;:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ie3ln2z9jvredmibfjen.png&quot; alt=&quot;APIException Unhandled Exception Log&quot;&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;what-s-new-in-v0-2-0&quot;&gt;What’s New in v0.2.0&lt;/h3&gt;
&lt;p&gt;The &lt;strong&gt;v0.2.0 release&lt;/strong&gt; focused on making exception handling even more flexible and logging more powerful:  &lt;/p&gt;
&lt;h4 id=&quot;added&quot;&gt;Added&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Advanced logging customizations&lt;/strong&gt; in &lt;code&gt;register_exception_handlers&lt;/code&gt;:  &lt;ul&gt;
&lt;li&gt;&lt;code&gt;log_level&lt;/code&gt;: override verbosity for exception logging.  &lt;/li&gt;
&lt;li&gt;&lt;code&gt;log_request_context&lt;/code&gt;: toggle to include/exclude request headers in logs.  &lt;/li&gt;
&lt;li&gt;&lt;code&gt;log_header_keys&lt;/code&gt;: define which headers are logged (default: &lt;code&gt;x-request-id&lt;/code&gt;, &lt;code&gt;user-agent&lt;/code&gt;, etc.).  &lt;/li&gt;
&lt;li&gt;&lt;code&gt;extra_log_fields&lt;/code&gt;: inject custom structured metadata into logs (e.g. &lt;code&gt;user_id&lt;/code&gt;, masked API keys).  &lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Response headers echo&lt;/strong&gt; in &lt;code&gt;register_exception_handlers&lt;/code&gt;:  &lt;ul&gt;
&lt;li&gt;&lt;code&gt;response_headers=True&lt;/code&gt; → default headers echoed back.  &lt;/li&gt;
&lt;li&gt;&lt;code&gt;response_headers=False&lt;/code&gt; → no headers echoed.  &lt;/li&gt;
&lt;li&gt;&lt;code&gt;response_headers=(&quot;x-user-id&quot;,)&lt;/code&gt; → custom headers echoed.  &lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;APIException&lt;/code&gt; now accepts header parameters (responses can carry custom headers).  &lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Mypy&lt;/strong&gt; support for static type checking.  &lt;/li&gt;
&lt;li&gt;Extended docs with logging patterns and advanced usage examples.  &lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;changed&quot;&gt;Changed&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Logging format revamped&lt;/strong&gt; → now more structured, readable, and consistent.  &lt;/li&gt;
&lt;li&gt;Error logs enriched with request path, method, client IP, HTTP version, etc.  &lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;fixed&quot;&gt;Fixed&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Swagger/OpenAPI now shows consistent error schemas even when &lt;code&gt;data&lt;/code&gt; was missing.  &lt;/li&gt;
&lt;li&gt;&lt;strong&gt;File structure cleanup&lt;/strong&gt; → simplified imports in &lt;code&gt;__init__.py&lt;/code&gt;.  &lt;/li&gt;
&lt;li&gt;Type hint fixes for &lt;code&gt;error_code&lt;/code&gt; as &lt;code&gt;BaseExceptionCode&lt;/code&gt;.  &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt; Upgrade now:  &lt;/p&gt;
&lt;p&gt;&lt;code&gt;pip install apiexception --upgrade&lt;/code&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;performance-impact&quot;&gt;Performance Impact&lt;/h3&gt;
&lt;p&gt;I wanted to make sure this solution would not slow down the API.&lt;br&gt;
So, I ran a simple benchmark with &lt;strong&gt;200 concurrent users&lt;/strong&gt; and compared it to FastAPI’s built-in &lt;code&gt;HTTPException&lt;/code&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;fastapi.HTTPException → &lt;strong&gt;2.00 ms&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;api_exception.APIException → &lt;strong&gt;2.72 ms&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That’s only &lt;strong&gt;+0.72 ms&lt;/strong&gt; difference, when you think that &lt;code&gt;HTTPException&lt;/code&gt; doesn't log the exceptions however &lt;code&gt;APIException&lt;/code&gt; does log the exceptions that occur in the app so the difference is fair for me. &lt;/p&gt;
&lt;h3 id=&quot;community-recognition&quot;&gt;&lt;strong&gt;Community recognition&lt;/strong&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;The library was featured on &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.pythonweekly.com/p/python-weekly-issue-710-august-14-2025-3200567a10d37d87&quot;&gt;Python Weekly #710&lt;/a&gt;, one of the largest Python newsletters worldwide.  &lt;/li&gt;
&lt;li&gt;It also reached &lt;strong&gt;#3&lt;/strong&gt; in r/FastAPI’s &lt;em&gt;pip-package&lt;/em&gt; flair on Reddit.  &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This kind of feedback and visibility motivates me to keep improving APIException and adding more features for the community.&lt;/p&gt;
&lt;h3 id=&quot;why-i-think-this-matters&quot;&gt;Why I Think This Matters&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Frontend developers&lt;/strong&gt; love it because they can handle every response the same way.  &lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Backend developers&lt;/strong&gt; love it because debugging is easier with consistent logs.  &lt;/li&gt;
&lt;li&gt;&lt;strong&gt;QA testers&lt;/strong&gt; love it because error states are predictable.  &lt;/li&gt;
&lt;li&gt;&lt;strong&gt;API consumers&lt;/strong&gt; love it because the docs clearly show all response shapes.  &lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;p&gt;If this resonates with you, give it a quick spin:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Install: &lt;code&gt;pip install apiexception&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Register: &lt;code&gt;register_exception_handlers(app)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Define codes: extend &lt;code&gt;BaseExceptionCode&lt;/code&gt; from &lt;code&gt;api_exception&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Success: return &lt;code&gt;ResponseModel[...]&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Errors: raise &lt;code&gt;APIException(...)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Docs: add &lt;code&gt;APIResponse.default()&lt;/code&gt; to your routes → makes Swagger docs much cleaner&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I’m actively improving the library—feedback, issues, and PRs are very welcome. And if it saves you some time (it probably will), a GitHub star would mean a lot.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Links&lt;/strong&gt;  &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;GitHub:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/akutayural/APIException&quot;&gt;https://github.com/akutayural/APIException&lt;/a&gt;  &lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Docs:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://akutayural.github.io/APIException/&quot;&gt;https://akutayural.github.io/APIException/&lt;/a&gt;  &lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;PyPI:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://pypi.org/project/apiexception/&quot;&gt;https://pypi.org/project/apiexception/&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
</description>
<category>Python</category>
<guid isPermaLink="true">https://coderlegion.com/4932/fastapi-error-handling-in-python-unify-responses-with-apiexception</guid>
<pubDate>Sun, 24 Aug 2025 09:43:32 +0000</pubDate>
</item>
<item>
<title>Securing ASGI Server with AWS Cognito</title>
<link>https://coderlegion.com/1187/securing-asgi-server-with-aws-cognito</link>
<description>&lt;h2 id=&quot;introduction&quot;&gt;Introduction&lt;/h2&gt;
&lt;p&gt;I published an article on hash node a couple weeks ago titled: [Deploying Complete Serverless API with any containerised Python ASGI Framework on AWS Lambda, API][1] &lt;/p&gt;
&lt;p&gt;I figured I needed to add a logical next step to securing the api. I was also partly inspired by the course by Steven Marek towards AWS Solutions Architect Certification, which I'm studying.&lt;/p&gt;
&lt;h3 id=&quot;-src-code&quot;&gt;/src_code&lt;/h3&gt;
&lt;p&gt;The GitHub code repo for this project is here: &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/652-Animashaun/blog-serverless.git&quot;&gt;https://github.com/652-Animashaun/blog-serverless.git&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Its written in Python - FastAPI, deployed to AWS Lambda, then put an API Gateway in front to route client requests to appropriate endpoints. That's it you're all caught up. &lt;/p&gt;
&lt;p&gt;While code example used here is Fast API framework, I implore you the implementation discussed is python framework agnostic. Come on I'll show you.&lt;/p&gt;
&lt;h2 id=&quot;what-is-aws-cognito-&quot;&gt;What is AWS Cognito?&lt;/h2&gt;
&lt;p&gt;I must admit the AWS Cognito documentation gave me the run around. I spent way more time than I thought I should implementing this mechanism. I'm not all that new to JWT authentication. I've implemented mechanisms using the OAuth before. &lt;br&gt;
Anyways I’ve done the work, gone through the belly of the beast that is the AWS documentation, so that you won’t have to. I’ll try to cut out the fluff.&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;Amazon Cognito is an identity platform for web and mobile apps. It’s a&lt;br&gt;
user directory, an authentication server, and an authorization service&lt;br&gt;
for OAuth 2.0 access tokens and AWS credentials. With Amazon Cognito,&lt;br&gt;
you can authenticate and authorize users from the built-in user&lt;br&gt;
directory, from your enterprise directory, and from consumer identity&lt;br&gt;
providers like Google and Facebook.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;So off the bat, we can already see several use cases for the AWS Cognito service. For now we want a simple authentication process, i.e user is served a login page to enter credentials, redirected to homepage on successful login, or presented a register page to create an account get OTP via provided email to verify ID. They get a token (JWT) which is presented to the server’s gate keeper for some resource access, in this case an API endpoint.&lt;br&gt;
What is happening under the hood is simple enough too. Well relatively, if you compare to implementing the mechanism from scratch.&lt;/p&gt;
&lt;h2 id=&quot;user-authentication-with-cognito-user-pools&quot;&gt;User authentication with Cognito user pools&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Login &amp;amp; Register Pages - Cognito&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;While you can build and customise to your liking, but AWS Cognito comes with a ready-to-use login and register pages where you can just redirect users to, you’ll also provide a redirect url where the returned token can be processed to authorize user.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Sending &amp;amp; Verifying OTP - Cognito&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Usually one would be inclined to use a service like SES or SNS on AWS or Twilio, but you’ll have to manually call or implement the mechanism from the server side. AWS cognito takes care of this out of the box.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Encoding &amp;amp; Signing Tokens - Cognito&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Cognito also takes care of encoding tokens. RS256 or HS256 base64encoding, also add features like expiry, a refresh token if you want. A Json Web Token  contains 3 parts header, payload and a signature. Payload is where the user identity like email, username are returned. Cognito returns 3 tokens, the access_token, id_token and a refresh_token, more about that later.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Decoding and Granting Access - Server&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;When a token is returned to the server, the receiver of the JWT verifies the signature using the secret key or the public key. And maybe go a step further and include it in request header so that subsequent requests can bear the authorisation token.&lt;/p&gt;
&lt;p&gt;Look, point is Cognito takes a lot of manual implementation out of the developers hand. I’m sold!.&lt;/p&gt;
&lt;p&gt;Let's dive right into the AWS Cognito dashboard create a user pool in 3 steps.&lt;/p&gt;
&lt;h3 id=&quot;creating-and-configuring-a-user-pool&quot;&gt;Creating and configuring a user pool&lt;/h3&gt;
&lt;p&gt;Select Create user pool from the User pools menu, or select Get started for free in less than five minutes.&lt;/p&gt;
&lt;p&gt;Under Define your application, I’m choosing “traditional web application” you can choose the Application type that best fits the application scenario that you want to create authentication and authorization services for.&lt;/p&gt;
&lt;p&gt;In Name your application, enter a descriptive name or proceed with the default name.&lt;/p&gt;
&lt;p&gt;You must make some basic choices under Configure options that support settings that you can't change after you create your user pool. For this use case I’m selecting username, email and full name.&lt;/p&gt;
&lt;p&gt;Add a return url on the server that handles payload receipts and process the payload. in this case mine is &lt;a rel=&quot;nofollow&quot; href=&quot;http://localhost:8000/api/v1/users/token&quot;&gt;http://localhost:8000/api/v1/users/token&lt;/a&gt; for development and testing purposes. You can add more redirect url options. Once you're ready to update lambda function with local changes, we'll be sure to add another return url , this time with the API Gateway stage endpoint. Else we get some funny error &lt;code&gt;client_id redirect_uri mismatch&lt;/code&gt;. Hit create.&lt;/p&gt;
&lt;h2 id=&quot;web-app-server&quot;&gt;Web App Server&lt;/h2&gt;
&lt;p&gt;Lets take a look in the user pool thats just been created:&lt;/p&gt;
&lt;p&gt;Under recommendations you should go ahead and click “setup your web app”.&lt;/p&gt;
&lt;p&gt;AWS was kind enough to show some code examples for different languages.&lt;/p&gt;
&lt;p&gt;Selecting python will give you a quick preview how things would look like:&lt;/p&gt;
&lt;p&gt;As shown, the framework in Cognitgo example is Flask, but it doesnt matter, that about how we’ll be implementing ours. I can already see that to start with I’ll need a /login endpoints and an /authorize endpoints at a minimum.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;/app.api.enpoints.users.py&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import os
import jwt
from pprint import pprint
from fastapi import APIRouter, HTTPException, status, Depends
from starlette.requests import Request
from app.utils.auth import oauth, verify_token
from starlette.responses import RedirectResponse

router = APIRouter()

@router.get(&quot;/login&quot;)
async def login(request: Request):
    return await oauth.oidc.authorize_redirect(request, &quot;http://localhost:8000/api/v1/users/token&quot;)

@router.get(&quot;/token&quot;)
async def authorize(request: Request):
    payload = await oauth.oidc.authorize_access_token(request)
    token = payload[&quot;id_token&quot;]
    res = await verify_token(token)
    user = payload[&quot;userinfo&quot;]
    return token
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;These endpoints aren’t doing much heavy lifting. &lt;code&gt;/login&lt;/code&gt; simply calls on &lt;code&gt;oidc.authorize_redirect()&lt;/code&gt; provided by the oauth import from utils where OAuth was instanced and passed to oauth, more on that subsequently. It takes a request and a redirect url to process response.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;async def authorize(request)&lt;/code&gt; will receive a get request on signing completion on cognito, with a json payload that looks like:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{ &quot;id_token&quot;: &quot;eyJraWQiOiJ3ekxUT1lrNVR....&quot;, &quot;access_token&quot;: &quot;eyJraWQiOiI3b0pqeFhY...&quot;, &quot;refresh_token&quot;: &quot;eyJjdHkiOiJKV1QiLC....&quot;, &quot;expires_in&quot;: 3600, &quot;token_type&quot;: &quot;Bearer&quot;, &quot;expires_at&quot;: 1738976151, &quot;userinfo&quot;: { &quot;at_hash&quot;: &quot;aYWSJq...&quot;, &quot;sub&quot;: &quot;b4d8e428-f091-7040-80a5-f2&quot;, &quot;email_verified&quot;: true, &quot;iss&quot;: &quot;https://cognito-idp.us-east-1.amazonaws.com/us-east-1_ftTN1aEr4&quot;, &quot;cognito:username&quot;: &quot;shaun&quot;, &quot;nonce&quot;: &quot;SYqZdIip5wBjjI10983&quot;, &quot;origin_jti&quot;: &quot;edc6ytb3c-4870-4979-8672-7d9a261ff471&quot;, &quot;aud&quot;: &quot;47re0a7e8l43ok719rtqcqnjih&quot;, &quot;token_use&quot;: &quot;id&quot;, &quot;auth_time&quot;: 1738972551, &quot;exp&quot;: 1738976151, &quot;iat&quot;: 1738972551, &quot;jti&quot;: &quot;f20cf2a4-5c3f-44f9-8d0b-919816d41f38&quot;, &quot;email&quot;: &quot;*Emails are not allowed*&quot; } }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As seen above we can already grab userinfo and quickly see the user ID. But theoretically some skilled man in the middle can provide their own ID, I dont know how it's done, the main thing is it is possible. And I like to preempt that, I want to do a quick verify.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import os
import jwt
from jwt import PyJWKClient
from authlib.integrations.starlette_client import OAuth
from fastapi import APIRouter, HTTPException, status, Depends, Security, HTTPException
from datetime import date, datetime, time, timedelta, timezone
from typing import Annotated
from app.models.users import User
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials, OAuth2PasswordBearer
from starlette.responses import RedirectResponse
from starlette.requests import Request


signin_url = 'https://cognito-idp.us-east-1.amazonaws.com/us-east-1_ftTN1aEr4/.well-known/jwks.json'
security = HTTPBearer(auto_error=False)

CLIENT_SECRET = os.environ[&quot;CLIENT_SECRET&quot;]
CLIENT_ID = os.environ[&quot;CLIENT_ID&quot;]
ISSUER = &quot;https://cognito-idp.us-east-1.amazonaws.com/us-east-1_ftTN1aEr4&quot;


oauth = OAuth()
oauth.register(
  name='oidc',
  authority=ISSUER,
  client_id=CLIENT_ID,
  client_secret=CLIENT_SECRET,
  server_metadata_url='https://cognito-idp.us-east-1.amazonaws.com/us-east-1_ftTN1aEr4/.well-known/openid-configuration',
  client_kwargs={'scope': 'email openid phone'}
)

async def verify_token(token:str)-&amp;gt; User:
jwks_client = PyJWKClient(signin_url)

try:
signing_key = jwks_client.get_signing_key_from_jwt(token)
payload = jwt.decode(
token,
signing_key,
audience=CLIENT_ID,
options={&quot;verify_exp&quot;: True},
algorithms=[&quot;RS256&quot;],
)
user = User(username=payload[&quot;cognito:username&quot;], email=payload[&quot;email&quot;], access_token=token)
return User
except jwt.ExpiredSignatureError:
raise HTTPException(status_code=401, detail=&quot;Token has expired&quot;)
except jwt.InvalidTokenError as e:
raise HTTPException(status_code=403, detail=&quot;Invalid token&quot;)

async def get_current_user(credentials: Annotated[HTTPAuthorizationCredentials, Security(security)]):
if not credentials:
return False
try:
return await verify_token(credentials.credentials)
except HTTPException:
return False
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Lets do a quick run through the code:&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;singing_url&lt;/code&gt; is provided by congnito in the userpool dashboard, this an endpoint provided by Cognito. At the back of it is a json_body of keys used to sign the token. More on that subsequently.&lt;/p&gt;
&lt;p&gt;In FastApi using Oauth2 as dependency is through the &lt;code&gt;Security()&lt;/code&gt; class provided. You can read more about the stuff here. TLDR &lt;code&gt;fastapi.security.HTTPBearer&lt;/code&gt; is one of the tools provided to use called as a dependable during visit of protect endpoints. It’s use case is for bearer token authentication. &lt;code&gt;auto_error=False&lt;/code&gt; because as per docs, by default, if the HTTP Bearer token is not provided (in an Authorization header), it'll automatically cancel the request and send the client an error. I don’t want that, I want to catch that and redirect to login so user can try challenge again.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;HTTPBearer&lt;/code&gt; returns an object &lt;code&gt;HTTPAuthorizationCredentials&lt;/code&gt; which I believe is received via the request header and provides it to the application.&lt;/p&gt;
&lt;p&gt;Then we just shamelessly copied the boiler plate code on cognito recommendation page to instantiate &lt;code&gt;Oauth.register()&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;async def verify_token()&lt;/code&gt; will take in a token, first it want’s to retrieve signing keys from the signing_url using the python module PyJWKClient class provided by JWT module. Next it calls jwt.decode(), takes the token, signing_key and a couple other keyword arguments. The decoded payload contains a dictionary with several user identifying information, but it’ll go ahead, returning username and email.&lt;/p&gt;
&lt;p&gt;In &lt;code&gt;async def get_current_user()&lt;/code&gt; where the first injection takes place. I’ve provided the &lt;code&gt;HTTPAuthorizationCredentials&lt;/code&gt; as HTTPBearer security which would try to retrieve the automaticall from the incoming request header the Bearer Authorization this is where the token is expected to be for an authenticated request.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;/app.api.endpoints.posts.py&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;To show the authentication mechanism actually works we have to create an endpoint that is protected:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from fastapi import APIRouter, Depends, HTTPException, status
from typing_extensions import Annotated
from app.utils.auth import oauth, verify_token, get_current_user
from app.models.users import User
from starlette.requests import Request
from starlette.responses import RedirectResponse

router = APIRouter()

@router.get(&quot;/&quot;)
async def all_posts(current_user: Annotated[User, Depends(get_current_user)]):
    if not current_user:
        return RedirectResponse(url=&quot;/api/v1/users/login&quot;)
    print(&quot;current_user&quot;, current_user)
    return {&quot;Welcome&quot;: &quot;Shaun's Blog&quot;, &quot;user&quot;: current_user}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Pretty straightforward right? &lt;code&gt;async def all_posts&lt;/code&gt; depends on &lt;code&gt;get_current_user&lt;/code&gt; which in turns returns a user object  or None, in which case user will be redirected to /login.&lt;/p&gt;
&lt;p&gt;Oh did I mention and AWS Cognito is free for 10K monthly active users. Check it out:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;Amazon Cognito Essentials and Lite have a free tier. The free tier&lt;br&gt;
does not automatically expire at the end of your 12-month AWS Free&lt;br&gt;
Tier term, and it is available to both existing and new AWS customers&lt;br&gt;
indefinitely. Please note - the free tier pricing isn’t available in&lt;br&gt;
the AWS GovCloud (US-West) region.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;For users who sign in directly via Amazon Cognito or through a social identity provider, Amazon Cognito user pools has a free tier of&lt;br&gt;
10,000 monthly active user (MAU) per month per account or per AWS&lt;br&gt;
organization. This free tier is applicable for customers that&lt;br&gt;
configure their user pools to either the Lite or Essentials tier.&lt;br&gt;
There is no free tier for the Plus tier.&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;p&gt;Stick around next one up is integrating social ID providers like google social media accounts. Would be fun.&lt;/p&gt;
&lt;p&gt;One more thing, remember when you push changes to lambda, add the api gateway endpoint version of your /token (authorise) to the list of return urls in the Cognito UserPool dashboard. This might be the cause of client_id redirect_uri mismatch error, watch out.&lt;/p&gt;
&lt;h3 id=&quot;references-&quot;&gt;References:&lt;/h3&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.aws.amazon.com/cognito/latest/developerguide/getting-started-user-pools-application.html&quot;&gt;https://docs.aws.amazon.com/cognito/latest/developerguide/getting-started-user-pools-application.html&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://pyjwt.readthedocs.io/en/stable/installation.html&quot;&gt;https://pyjwt.readthedocs.io/en/stable/installation.html&lt;/a&gt;&lt;/p&gt;
</description>
<category>Getting Started</category>
<guid isPermaLink="true">https://coderlegion.com/1187/securing-asgi-server-with-aws-cognito</guid>
<pubDate>Thu, 20 Feb 2025 19:02:49 +0000</pubDate>
</item>
<item>
<title>When to Choose FastAPI Over Django or Flask: A Comprehensive Guide with Practical Examples</title>
<link>https://coderlegion.com/884/when-to-choose-fastapi-over-django-or-flask-comprehensive-guide-with-practical-examples</link>
<description>&lt;p&gt;Python is home to several powerful web frameworks, each designed to cater to different needs. Django and Flask have long been dominant players, offering solutions for web development for over a decade. However, in recent years, FastAPI has emerged as a modern framework, purpose-built for APIs and asynchronous programming. Its speed, simplicity, and robust features make it an appealing choice for developers.&lt;/p&gt;
&lt;p&gt;In this guide, we’ll explore:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;What makes FastAPI unique. &lt;/li&gt;
&lt;li&gt;Key differences compared to Django and Flask. &lt;/li&gt;
&lt;li&gt;Specific use cases where FastAPI excels.&lt;/li&gt;
&lt;li&gt;Detailed examples demonstrating its strengths.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;what-is-fastapi-&quot;&gt;&lt;strong&gt;What is FastAPI?&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;FastAPI is a Python framework for building APIs with modern design principles. It’s built on the Starlette framework (for the web parts) and Pydantic (for data validation). Unlike Django or Flask, FastAPI fully embraces asynchronous programming with asyncio, making it one of the fastest Python frameworks available.&lt;/p&gt;
&lt;h3 id=&quot;key-features-&quot;&gt;&lt;strong&gt;Key Features:&lt;/strong&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;High Performance: Comparable to Node.js and Go, thanks to ASGI and&lt;br&gt;
async support.&lt;/li&gt;
&lt;li&gt;Automatic Documentation: Swagger UI and ReDoc are auto-generated.&lt;/li&gt;
&lt;li&gt;Type Safety: Input validation and response types are built-in using&lt;br&gt;
Python's type hints.&lt;/li&gt;
&lt;li&gt;Ease of Use: FastAPI is simple to use, even for beginners, while&lt;br&gt;
offering powerful capabilities for experts.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;why-choose-fastapi-&quot;&gt;Why Choose FastAPI?&lt;/h3&gt;
&lt;p&gt;FastAPI is specifically designed for building modern APIs with a focus on performance and developer experience. Let’s delve deeper into scenarios where it outshines Django and Flask:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1. High-Performance APIs&lt;/strong&gt;&lt;br&gt;
If your application needs to handle thousands of simultaneous requests efficiently, FastAPI is a game-changer.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Built on ASGI, it supports asynchronous requests out of the box.&lt;/li&gt;
&lt;li&gt;Ideal for real-time applications, such as chat servers, streaming&lt;br&gt;
platforms, or IoT systems.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Example: Asynchronous API with FastAPI&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from fastapi import FastAPI
import asyncio

app = FastAPI()

@app.get(&quot;/long-task/&quot;)
async def long_task():
      await asyncio.sleep(5)  # Simulate a time-consuming operation
      return {&quot;message&quot;: &quot;Task completed after 5 seconds&quot;}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This endpoint:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Demonstrates how FastAPI handles concurrent requests without blocking&lt;br&gt;
other users.&lt;/li&gt;
&lt;li&gt;Would be cumbersome to implement in Flask or Django without&lt;br&gt;
significant modifications.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;2. Automatic Documentation&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;FastAPI automatically generates Swagger UI and ReDoc documentation for your API. This is a huge time-saver for teams working on complex APIs.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Example: Auto-Generated Docs&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from fastapi import FastAPI

app = FastAPI()

@app.get(&quot;/items/{item_id}&quot;)
async def read_item(item_id: int, q: str = None):
&quot;&quot;&quot;
Fetch an item by ID.

  - **item_id**: The ID of the item to fetch.  
  - **q**: An optional query string for filtering results.  
&quot;&quot;&quot;
    return {&quot;item_id&quot;: item_id, &quot;query&quot;: q}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To access the docs:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Run the app using uvicorn:&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt; uvicorn main:app --reload&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Visit:&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Swagger UI: &lt;a rel=&quot;nofollow&quot; href=&quot;http://127.0.0.1:8000/docs&quot;&gt;http://127.0.0.1:8000/docs&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;ReDoc: &lt;a rel=&quot;nofollow&quot; href=&quot;http://127.0.0.1:8000/redoc&quot;&gt;http://127.0.0.1:8000/redoc&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Both provide an interactive interface to test your API endpoints.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;3. Data Validation and Serialization&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;FastAPI uses Pydantic for data validation, which ensures robust type checking and error handling.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Example: Input Validation with Pydantic&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()

class User(BaseModel):
    username: str
    email: str
    age: int

@app.post(&quot;/users/&quot;)
async def create_user(user: User):
    return {&quot;message&quot;: &quot;User created successfully!&quot;, &quot;user&quot;: user}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;What happens here:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;FastAPI automatically validates that the username, email, and age&lt;br&gt;
fields are present and of the correct type.&lt;/li&gt;
&lt;li&gt;If validation fails, the    API returns a detailed error message&lt;br&gt;
without any extra coding.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;4. WebSocket Support&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Real-time communication is a breeze with FastAPI, thanks to native WebSocket support.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Example: Building a WebSocket Chat Server&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;from fastapi &lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import FastAPI, WebSocket

app = FastAPI()

@app.websocket(&quot;/ws&quot;)
async def websocket_endpoint(websocket: WebSocket):
    await websocket.accept()
    while True:
        data = await websocket.receive_text()
        await websocket.send_text(f&quot;You said: {data}&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Use this for:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Chat applications. &lt;/li&gt;
&lt;li&gt;Real-time notifications or dashboards. &lt;/li&gt;
&lt;li&gt;Multiplayer    games.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;5. Ease of Scaling and Deployment&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;FastAPI integrates well with modern deployment tools like Docker and Kubernetes. Its ASGI compatibility makes it ideal for scaling microservices.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Example: Dockerizing FastAPI&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;FROM python:3.10-slim

WORKDIR /app
COPY requirements.txt requirements.txt
RUN pip install -r requirements.txt
COPY . .

CMD [&quot;uvicorn&quot;, &quot;main:app&quot;, &quot;--host&quot;, &quot;0.0.0.0&quot;, &quot;--port&quot;, &quot;8000&quot;]
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;when-django-or-flask-might-be-better&quot;&gt;When Django or Flask Might Be Better&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Django is unbeatable for full-stack applications with templates,&lt;br&gt;
authentication, and ORM out of the box.&lt;/li&gt;
&lt;li&gt;Flask is perfect for small projects or when you need a quick&lt;br&gt;
prototype without the complexity of Django or FastAPI.&lt;/li&gt;
&lt;li&gt;Legacy Systems: If your team already uses Django or Flask&lt;br&gt;
extensively, switching to FastAPI might not justify the learning&lt;br&gt;
curve.&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&quot;div-green&quot;&gt;
&lt;span class=&quot;alert-header&quot;&gt;Tip:&lt;/span&gt;
&lt;span class=&quot;alert-body&quot;&gt;When starting with FastAPI, use Uvicorn as your ASGI server for a smooth development experience. It’s lightweight, easy to configure, and perfectly complements FastAPI’s capabilities.&lt;/span&gt;
&lt;/div&gt;
&lt;div class=&quot;div-red&quot;&gt;
&lt;span class=&quot;alert-header&quot;&gt;Caution:&lt;/span&gt;
&lt;span class=&quot;alert-body&quot;&gt;If you are migrating from Django or Flask to FastAPI, note that FastAPI does not come with built-in components like an ORM (similar to Django’s) or a templating engine. Be prepared to integrate libraries like SQLAlchemy or Jinja2 for those functionalities.&lt;/span&gt;
&lt;/div&gt;
&lt;div class=&quot;div-blue&quot;&gt;
&lt;span class=&quot;alert-header&quot;&gt;Note:&lt;/span&gt;
&lt;span class=&quot;alert-body&quot;&gt;FastAPI’s auto-generated documentation supports both Swagger UI and ReDoc. These are especially useful for teams collaborating on API development as they provide an interactive interface for testing endpoints directly.&lt;/span&gt;
&lt;/div&gt;
&lt;div class=&quot;div-faq&quot;&gt;
&lt;span class=&quot;faq-header&quot;&gt;FAQ&lt;/span&gt;
&lt;span class=&quot;faq-body&quot;&gt;Q: Is FastAPI better than Django and Flask for all use cases?
A: Not necessarily. While FastAPI is excellent for APIs and asynchronous programming, Django is better suited for full-stack applications, and Flask is ideal for lightweight, quick prototypes. &lt;br&gt; &lt;br&gt;

Q: Does FastAPI support databases out of the box?
A: No, FastAPI doesn’t include a built-in ORM. However, you can easily integrate libraries like SQLAlchemy, Tortoise ORM, or GINO for database support. &lt;br&gt; &lt;br&gt;

Q: How does FastAPI handle validation errors?
A: FastAPI uses Pydantic for validation and automatically generates detailed error messages for invalid data inputs, saving developers significant time. &lt;br&gt; &lt;br&gt;

Q: Can I use FastAPI for real-time features like WebSockets?
A: Yes, FastAPI has built-in support for WebSockets, making it a great choice for real-time applications such as chat systems and live dashboards. &lt;br&gt; &lt;br&gt;

Q: What’s the best way to deploy FastAPI applications?
A: FastAPI integrates well with Docker for containerization and Kubernetes for scaling. Use an ASGI server like Uvicorn or Daphne for production deployment.&lt;/span&gt;
&lt;/div&gt; &lt;br&gt; &lt;br&gt;
&lt;h3 id=&quot;conclusion-3&quot;&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;FastAPI excels in modern, API-driven development, offering unparalleled performance, automatic documentation, and type safety. While Django and Flask still shine in specific scenarios, FastAPI is the future of Python APIs, designed for speed and scalability.&lt;/p&gt;
&lt;p&gt;Ready to start? Install FastAPI today and see how it transforms your API development!&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pip install fastapi uvicorn
&lt;/code&gt;&lt;/pre&gt;
</description>
<category>Python</category>
<guid isPermaLink="true">https://coderlegion.com/884/when-to-choose-fastapi-over-django-or-flask-comprehensive-guide-with-practical-examples</guid>
<pubDate>Wed, 22 Jan 2025 10:22:44 +0000</pubDate>
</item>
<item>
<title>Building MUNDRA: Revolutionizing MUN Delegate Management with Tech</title>
<link>https://coderlegion.com/483/building-mundra-revolutionizing-mun-delegate-management-with-tech</link>
<description>&lt;p&gt;When it comes to managing Model United Nations (MUN) events, the challenges are endless—keeping track of hundreds of delegates, streamlining communication, and planning large-scale events with precision. As the &lt;strong&gt;Joint General Secretary - Tech&lt;/strong&gt; of the &lt;strong&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://munsocietympstme.com&quot;&gt;MUN Society MPSTME&lt;/a&gt;&lt;/strong&gt;, I decided to tackle these challenges head-on.&lt;/p&gt;
&lt;p&gt;That’s how &lt;strong&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/nnisarggada/mundra&quot;&gt;MUNDRA&lt;/a&gt;&lt;/strong&gt; – the &lt;strong&gt;MUNSoc Delegate Resource Application&lt;/strong&gt; – was born. Named after the bustling Mundra Port in Gujarat, this backend application is designed to be the central hub for all delegate-related activities. More than just a database, MUNDRA is a scalable, secure, and modular solution that will power everything from our apps to email workflows and websites.&lt;/p&gt;
&lt;p&gt;For developers and enthusiasts, MUNDRA's API is fully documented and accessible &lt;a rel=&quot;nofollow&quot; href=&quot;https://mundra.nnisarg.in/redoc&quot;&gt;here&lt;/a&gt;, showcasing its well-structured design and functionality.&lt;/p&gt;
&lt;h3 id=&quot;the-idea-behind-mundra&quot;&gt;The Idea Behind MUNDRA&lt;/h3&gt;
&lt;p&gt;MUNs are as much about organization as they are about debate. Without a centralized system to manage delegate data, events can quickly devolve into chaos. MUNDRA was built to simplify this process by:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Centralizing all delegate and event data into a single database.&lt;/li&gt;
&lt;li&gt;Automating repetitive tasks like account management and email verification.&lt;/li&gt;
&lt;li&gt;Supporting scalable growth for larger MUN events in the future.&lt;/li&gt;
&lt;li&gt;Offering seamless integration with websites, apps, and other tools.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;As someone who loves creating meaningful tech solutions, this wasn’t just another project for me. It was about building a product that could evolve with our society’s needs.&lt;/p&gt;
&lt;h3 id=&quot;diving-into-the-tech&quot;&gt;Diving Into the Tech&lt;/h3&gt;
&lt;p&gt;MUNDRA is built on &lt;strong&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://fastapi.tiangolo.com&quot;&gt;FastAPI&lt;/a&gt;&lt;/strong&gt;, a modern Python framework known for its speed and simplicity. Here’s what makes MUNDRA stand out:&lt;/p&gt;
&lt;h4 id=&quot;key-features&quot;&gt;&lt;strong&gt;Key Features&lt;/strong&gt;&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Comprehensive User Management&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Delegate accounts with creation, deletion, and updates.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Email verification&lt;/strong&gt; ensures only genuine users have access.&lt;/li&gt;
&lt;li&gt;Hassle-free password recovery and change workflows.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Centralized Database&lt;/strong&gt;&lt;br&gt;
A unified storage system for delegate and event data, enabling easy retrieval and scalability for future growth.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Built-In Security&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Passwords are hashed using modern cryptographic standards.&lt;/li&gt;
&lt;li&gt;JWT-based authentication ensures secure access.&lt;/li&gt;
&lt;li&gt;Additional measures like rate limiting protect against abuse.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Integration Ready&lt;/strong&gt;&lt;br&gt;
MUNDRA is designed to plug into various tools—our app, event websites, and even automated email scripts.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;why-fastapi-&quot;&gt;Why FastAPI?&lt;/h3&gt;
&lt;p&gt;Choosing the right framework was critical to the success of MUNDRA. FastAPI emerged as the clear winner for its modern features:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Performance:&lt;/strong&gt; It’s built for high-speed asynchronous processing.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Ease of Use:&lt;/strong&gt; Automatic API documentation and intuitive type hints made development quick and error-free.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Scalability:&lt;/strong&gt; FastAPI handles high-concurrency environments effortlessly.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You can explore the live API documentation &lt;a rel=&quot;nofollow&quot; href=&quot;https://mundra.nnisarg.in/redoc&quot;&gt;here&lt;/a&gt;, powered by FastAPI's built-in OpenAPI support.&lt;/p&gt;
&lt;h3 id=&quot;building-mundra-challenges-and-lessons-learned&quot;&gt;Building MUNDRA: Challenges and Lessons Learned&lt;/h3&gt;
&lt;p&gt;No journey is without its hurdles. Building MUNDRA taught me some valuable lessons:&lt;/p&gt;
&lt;h4 id=&quot;challenge-scalability&quot;&gt;&lt;strong&gt;Challenge: Scalability&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;MUN events grow larger every year. To ensure MUNDRA could keep up, I carefully designed the database schema and optimized API endpoints for handling large datasets.&lt;/p&gt;
&lt;h4 id=&quot;challenge-security&quot;&gt;&lt;strong&gt;Challenge: Security&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;Account workflows like password recovery are potential attack vectors. I implemented secure password hashing, email token validation, and rate limiting to safeguard user data.&lt;/p&gt;
&lt;h4 id=&quot;challenge-modularity&quot;&gt;&lt;strong&gt;Challenge: Modularity&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;MUNDRA had to be versatile enough to power apps, websites, and email scripts. I focused on designing clean, self-contained APIs that would work seamlessly across different front-end systems.&lt;/p&gt;
&lt;h3 id=&quot;the-impact-of-mundra&quot;&gt;The Impact of MUNDRA&lt;/h3&gt;
&lt;p&gt;The first version of MUNDRA, &lt;strong&gt;v1.0.0&lt;/strong&gt;, is already proving to be a game-changer for our society. With centralized data and automated workflows, we’ve saved countless hours of manual effort.&lt;/p&gt;
&lt;p&gt;This backend isn’t just a tool; it’s a foundation for future innovation. Whether it’s real-time delegate tracking, analytics dashboards, or AI-driven insights, MUNDRA is built to evolve.&lt;/p&gt;
&lt;h3 id=&quot;beyond-code-what-mundra-means-to-me&quot;&gt;Beyond Code: What MUNDRA Means to Me&lt;/h3&gt;
&lt;p&gt;This project represents more than technical growth—it’s a testament to my ability to take an idea from concept to execution. As the &lt;strong&gt;Joint General Secretary - Tech&lt;/strong&gt;, I didn’t just build a backend; I created a &lt;strong&gt;scalable product&lt;/strong&gt; tailored to real-world needs.&lt;/p&gt;
&lt;p&gt;MUNDRA reflects my passion for leveraging technology to solve problems at scale, and it’s something I’m incredibly proud of.&lt;/p&gt;
&lt;h3 id=&quot;what-s-next--1&quot;&gt;What’s Next?&lt;/h3&gt;
&lt;p&gt;MUNDRA is just getting started. Here’s a glimpse into the roadmap:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Real-Time Communication Tools:&lt;/strong&gt; Instant updates for delegates.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Analytics Dashboards:&lt;/strong&gt; Insights into delegate participation and event performance.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;AI-Powered Tools:&lt;/strong&gt; Recommendations for committee assignments and event planning.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;explore-mundra&quot;&gt;Explore MUNDRA&lt;/h3&gt;
&lt;p&gt;Interested in the technical details or want to contribute?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Check out the source code on &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/nnisarggada/mundra&quot;&gt;GitHub&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;View the live API documentation at &lt;a rel=&quot;nofollow&quot; href=&quot;https://mundra.nnisarg.in/redoc&quot;&gt;mundra.nnisarg.in/redoc&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Let’s build the future of MUN management together.&lt;/p&gt;
</description>
<category>Python</category>
<guid isPermaLink="true">https://coderlegion.com/483/building-mundra-revolutionizing-mun-delegate-management-with-tech</guid>
<pubDate>Mon, 25 Nov 2024 21:06:26 +0000</pubDate>
</item>
</channel>
</rss>