MoreRSS

site iconThe Practical DeveloperModify

A constructive and inclusive social network for software developers.
Please copy the RSS to your reader, or quickly subscribe to:

Inoreader Feedly Follow Feedbin Local Reader

Rss preview of Blog of The Practical Developer

The future

2025-12-01 00:32:26

I recently attended GOTO conference in Copenhagen (https://gotocph.com/2025, you can find the slides for some of the presentations there) and would like to share some of the interesting topics. This topic is going to be the last one.

The talk about the future by Kevlin Henney and James Lewis

The presenters shared very interesting story about the future we planned and the future we are going to have. Here are the main points and my comments as well.

  1. The future was meant to be for everyone, but we landed in the place where it is actually for the few with unevenly distributed resources - yes, one can look at UNICEF report (https://data.unicef.org/resources/sofi-2025/) to see that 8.3 percent of global population faced hunger in 2024. The food is the basic need of the human and it is somehow not possible to provide it for the acceptable rate which should be like 99,9 % of the population.

  2. The future tends to bring more infrastructure and information networks so that everything and everyone become more connected - I agree, we can see this is happening: we are connecting with the people using various social networks, we can travel quickly all over the world. The companies create customer experience which bases on all the online and possibly offline activities to be so close as virtual friend or maybe closer. They are able to gather huge amount of data and turn it into large income.
    I am just not sure if this is the right direction, do we really want to be more connected with people and the business? Did anyone ask? I am sceptical, I can think of the scenario that we are heading towards digital crowd where everyone is just too close to each other.

  3. We should still however have hope and excitement looking into it - Yes we should, I especially like the Mars colonisation idea. It seems it doesn't have much sense, it is extremly hard, but because of this it is exciting for sure.

  4. The change is more late, gradual and cumulative than we think - These are tips on how to think about the future in general. We can use them when trying to foresee the evolution of quantum computing, genes edition, internet of things or other future technologies mentioned for example here: https://www.sciencenewstoday.org/the-future-of-technology-what-will-the-next-decade-bring.

As I see it, in my environment, there is not enough discussion about the future. I rarely can read anything in the news, I know that nobody teaches about it in the schools. We as the society are living in the past and only sometimes plan for the very near future. The Status Quo is the king.
The only exceptions are literature and movies but I feel like it is not taken seriously by the people, it is just the entertainment. I am sure we should be more aware about the processes which take place around us nowadays and plan more for the distant future as well to know at least the general direction we would like to follow.

When Should You Use Backblaze B2 and When Should You Use Cloudflare R2?

2025-12-01 00:23:23

Backblaze B2 and Cloudflare R2 are both object storage services that people often consider as affordable and flexible alternatives to AWS S3. Although they look similar, each service has its own strengths and ideal use cases.

If you are building something heavily file based such as an image hosting service, static asset delivery, or a backup system, choosing the right storage solution can save a lot of money and improve performance.

When Is Backblaze B2 a Good Fit?

Backblaze B2 is excellent when your storage needs focus on low cost pricing and large volumes of data that are not accessed very frequently.

Common scenarios where B2 works really well:
• Database and server backups

• Long term archiving and cold storage

• Server logs, large documents, older data

• Internal storage without global delivery needs

• Large files for backend pipelines

In short, if your priority is cheap storage, Backblaze B2 is often the first choice.

You can check the official B2 pricing here:

👉 https://www.backblaze.com/cloud-storage/pricing

When Is Cloudflare R2 a Good Fit?

Cloudflare R2 is ideal for applications that frequently access files, require low latency, and serve users globally.

Common use cases for R2:
• Web assets such as images, fonts, JavaScript, and CSS

• Image hosting and public file delivery

• File based APIs

• Applications running on Cloudflare Workers

• Systems that need global traffic delivery without expensive egress costs

The biggest advantage of R2 is zero egress fees, which completely changes the cost structure for public facing applications.

You can find the full pricing for R2 here:

👉 https://developers.cloudflare.com/r2/pricing

Real Example: ImgPeek Uses Cloudflare R2

My platform ImgPeek is a free image hosting service that allows users to upload and share images quickly. Because the service is public and images are frequently accessed, choosing the right storage layer is crucial.

Why ImgPeek chose Cloudflare R2:
• Images are fetched often, which means egress would be very high

• With R2, egress cost is zero, making operational costs much more predictable

• Automatically integrated with Cloudflare’s global CDN for fast delivery

• Works seamlessly with Cloudflare Workers for extra edge processing

• Financially safer for a free service like image hosting

If ImgPeek used Backblaze B2, the egress cost would grow quickly as traffic increased.

Comparison Table: Backblaze B2 vs Cloudflare R2

Category Backblaze B2 Cloudflare R2
Primary Focus Large, low cost storage Global file delivery and active apps
Storage Pricing Cheaper per GB Slightly more expensive
Egress Cost Charges apply Zero
Global Performance Depends on region Integrated with Cloudflare CDN
Edge Compute Integration None Native with Cloudflare Workers
Ideal for Public Traffic Less ideal due to egress fees Extremely ideal
Ideal for Large Files Very suitable Still works but not the main strength
S3 Compatible Yes Yes
Ideal Use Cases Backups, archives, cold data Web assets, image hosting, file APIs

Quick Conclusion

If you need cheap storage for backups or archives, choose Backblaze B2.

If you are building a public application that frequently serves files and you want to avoid egress costs, choose Cloudflare R2, just like ImgPeek did.

Building Atlassian Forge app with Kiro

2025-12-01 00:18:20

I am so glad that I have took some time out of my day to tryout this Kiroween hackathon. A little bit of background, I have been building apps on Atlassian ecosystem for quite a number of years already. Therefore, the advantage I had in this is my domain knowledge to build on this ecosystem.

Demo

Without further a due, here is the project that I have been working on.

Guess what, all these that you're seeing, it took me not more that 2 days of my weekend to vibe this out. It was also a surprise for me seeing how easy to build this on Kiro with spec driven approach if we have all the basic agent instructions and prompts setup.

Setup

Agents.md

To start Atlassian's forge project, you're need to install the @forge/cli. Feel free to get started here.

The key thing here is, when you bootstrap the project with forge create, you'll noticed that it comes with an AGENTS.md. Kiro treated this as the steering docs, which helped the agent so much when I am vibing and building the specs on Kiro.

Forge MCP

The other things that wasn’t created by default, but I have added it is the Atlassian Forge MCP server. By just adding this to the mcp.json, we’re literally good to go.

{
  "mcpServers": {
    "forge-knowledge": {
      "url": "https://mcp.atlassian.com/v1/forge/mcp",
      "transport": "http"
    }
  }
}

Just like the Agents.md, everything works so seamlessly well together. The context of MCP helps the agent to stay in context when building the apps is what amaze me the most.

Assisted coding

The original Agents.md has enforced with UI Kit for the frontend. However, knowing that I need to use custom React Flow library to achieve the amazing drag and drop flow, I need to use custom UI. And, with the small tweak to the Agents.md everything works nicely for me.

Conclusion

Kiro is such a fun tools to work with, and while this is the second time I am using it, the output seems to be much better when it initially launched.

With the integration of Agents, Steering Docs, Specs, and MCP. We’re able to be so much more effective to prevent hallucination and enjoy the process of working with AI to pair programming or vibe code.

Git rebase vs merge

2025-12-01 00:15:51

logotech

## Diferenças na História de Commits: Navegando pelo Passado e Presente do Seu Código

A história de commits é o coração pulsante do seu projeto. Ela conta a narrativa da evolução do seu código, desde a criação inicial até as iterações mais recentes. Dominar as diferenças na história de commits é crucial para entender, colaborar e manter um projeto de software saudável.

Entendendo a História: Commits, Branches e Merges

  • Commits: Cada commit é um snapshot do seu código em um determinado momento. Ele registra as alterações feitas, quem as fez e uma mensagem descrevendo o que foi alterado. Use mensagens de commit claras e concisas para facilitar a compreensão.
  • Branches (Ramificações): Branches permitem trabalhar em funcionalidades isoladas sem afetar a linha principal de desenvolvimento (geralmente a main ou master). Elas são essenciais para colaboração e experimentação.
  • Merges (Fusões): Quando uma branch está pronta, ela é fundida de volta à branch principal. O merge combina as alterações, criando um novo commit que reflete a integração.

Visualizando as Mudanças: Ferramentas Essenciais

  • git log: Exibe o histórico de commits, incluindo IDs de commit, autores, datas e mensagens.
    • git log --oneline: Uma versão compacta, ideal para uma visão geral rápida.
    • git log -p: Mostra as diferenças (patches) de cada commit.
    • git log --graph: Visualiza a história de commits como um gráfico, mostrando branches e merges.
  • git diff: Compara as diferenças entre commits, branches, arquivos ou até mesmo o código atual e o que está no stage.
    • git diff <commit1> <commit2>: Compara dois commits específicos.
    • git diff <branch1> <branch2>: Compara as diferenças entre duas branches.
    • git diff --staged: Mostra as alterações que foram adicionadas à área de stage.
    • git diff -- <arquivo>: Mostra as diferenças de um arquivo específico.
  • Ferramentas Gráficas (GUI): Ferramentas como GitKraken, SourceTree e a interface gráfica do VS Code facilitam a visualização da história de commits e a navegação entre branches.

Resolvendo Conflitos: Quando as Mudanças Colidem

Conflitos ocorrem quando duas branches (ou commits) modificam a mesma parte de um arquivo de forma incompatível. O Git não consegue decidir qual alteração manter, então o conflito deve ser resolvido manualmente.

Passos para Resolver Conflitos:

  1. Identifique o conflito: O Git marca os conflitos no arquivo com marcadores como <<<<<<<, ======= e >>>>>>>.
  2. Edite o arquivo: Decida quais alterações manter (ou combinar) e remova os marcadores de conflito.
  3. Adicione e Commite: Adicione o arquivo modificado à área de stage (git add) e crie um novo commit para resolver o conflito.

Dicas para Evitar Conflitos:

  • Comunique-se: Converse com sua equipe para coordenar as alterações, especialmente em áreas de código compartilhadas.
  • Faça merges frequentemente: Integrar as mudanças das branches frequentemente ajuda a detectar e resolver conflitos no início.
  • Branches pequenas: Mantenha as branches com foco em tarefas específicas e de curta duração.

Fluxos de Trabalho: Adaptando-se às Necessidades

Existem diversos fluxos de trabalho para gerenciar seu código. A escolha depende da sua equipe, projeto e preferências.

  • Gitflow: Um fluxo de trabalho popular que usa branches separadas para funcionalidades, lançamentos e correções de bugs. Ideal para projetos maiores e com lançamentos regulares.
  • GitHub Flow: Um fluxo de trabalho mais simples, focado em branches de funcionalidades e merges frequentes na branch main. Bom para projetos menores e colaboração rápida.
  • Trunk-Based Development: Todos os desenvolvedores trabalham diretamente na branch main e integram suas alterações com frequência. Requer testes automatizados e uma forte disciplina de código.

Considerações:

  • Tamanho da equipe: Equipes maiores podem se beneficiar de fluxos de trabalho mais estruturados como Gitflow.
  • Frequência de lançamentos: Se você lança frequentemente, GitHub Flow ou Trunk-Based Development podem ser adequados.
  • Complexidade do projeto: Projetos complexos podem exigir fluxos de trabalho que permitam gerenciar diferentes versões do código.

Conclusão

Dominar a história de commits e os fluxos de trabalho é essencial para qualquer desenvolvedor. Ao entender as diferenças na história, resolver conflitos e escolher o fluxo de trabalho certo, você e sua equipe podem colaborar de forma mais eficiente, manter o código limpo e entregar projetos de sucesso. Explore as ferramentas, experimente diferentes fluxos e encontre o que funciona melhor para você!

Understanding User Registration: Email Agent Series Part 1

2025-12-01 00:12:36

A deep dive into building secure, scalable user registration with React, FastAPI, and modern authentication patterns

Introduction

User registration is the gateway to any application. It's the first interaction users have with your system, and it sets the tone for security, user experience, and architectural quality. In this article, I'll walk you through the complete user registration flow in my AI Email Assistant application, from the moment a user clicks "Register" to when their encrypted credentials are safely stored in the database.

We'll explore:

  • Frontend architecture with React Context and custom hooks
  • Backend API design with FastAPI and Pydantic validation
  • Security best practices including bcrypt password hashing
  • Clean architecture patterns that promote maintainability and testability

System Overview

The registration flow spans multiple layers of abstraction, each with a specific responsibility:

Frontend Stack:

  • Next.js 14 (React with TypeScript)
  • React Context for global state management
  • Custom hooks for clean component APIs
  • Layered API client architecture

Backend Stack:

  • FastAPI (Python)
  • SQLAlchemy ORM
  • Pydantic for data validation
  • bcrypt for password hashing
  • JWT for authentication tokens

The Complete Flow at a Glance

Before diving into the details, let's visualize the high-level journey:

Figure 1: High-level sequence diagram of the registration process

Figure 1: High-level sequence diagram of the registration process

Key Steps:

  1. User Input → Form submission with validation
  2. Frontend → Three-layer architecture (UI → Context → API Client)
  3. HTTP Request → POST to backend with user data
  4. Backend Validation → Pydantic schema checks
  5. Password Security → bcrypt hashing with salt
  6. Database → User record persisted
  7. Auto-Login → Seamless authentication
  8. Redirect → User sent to dashboard

Part 1: Frontend Architecture

The Three-Layer Abstraction

One of the key architectural decisions in this application is the separation of concerns on the frontend. Instead of making direct API calls from UI components, the registration flow passes through three distinct layers:

  1. UI Component Layer - User interaction and form state
  2. Context Layer - Global state management and business logic
  3. API Client Layer - HTTP communication and token management

This architecture provides several benefits:

  • Reusability: Auth functions available throughout the app
  • Testability: Each layer can be tested in isolation
  • Maintainability: Changes to API structure only affect one file
  • Type Safety: TypeScript ensures correctness across layers

Frontend Flow Diagram

Figure 2: Frontend architecture showing the flow from UI component through Context and API clients to the backend

Figure 2: Frontend architecture showing the flow from UI component through Context and API clients to the backend

Layer 1: The Registration Page Component

The registration page is a client-side React component that manages form state and user interaction.

// webapp/frontend/app/auth/register/page.tsx
export default function RegisterPage() {
  const router = useRouter();
  const { register } = useAuth(); // Custom hook from AuthContext

  const [formData, setFormData] = useState({
    email: '',
    first_name: '',
    last_name: '',
    password: '',
    confirmPassword: '',
  });

  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault();

    // Client-side validation
    const validationError = validateForm();
    if (validationError) {
      setError(validationError);
      return;
    }

    try {
      // Call register from context - clean and simple!
      await register({
        email: formData.email,
        first_name: formData.first_name,
        last_name: formData.last_name,
        password: formData.password,
      });

      // Auto-login successful, redirect to dashboard
      router.push('/accounts');

    } catch (err: any) {
      setError(err.message || 'Registration failed');
    }
  };
}

Key Observations:

  • No HTTP details in the component - it just calls register()
  • Clean separation between UI logic and API communication
  • Error handling is straightforward and user-friendly

Layer 2: Authentication Context Provider

The Auth Context is where the magic happens. It's a React Context Provider that manages global authentication state and provides auth functions to all components.

// webapp/frontend/components/auth/auth-context.tsx
export function AuthProvider({ children }: { children: React.ReactNode }) {
  const [user, setUser] = useState<User | null>(null);
  const [isLoading, setIsLoading] = useState(true);

  /**
   * Register new user and auto-login
   */
  const register = useCallback(async (data: RegisterData) => {
    setIsLoading(true);
    try {
      // Step 1: Register user
      await authClient.register(data);

      // Step 2: Auto-login after successful registration
      await login({ email: data.email, password: data.password });

    } catch (error) {
      setIsLoading(false);
      throw error;
    }
  }, [login]);

  return (
    <AuthContext.Provider value={{ user, register, login, logout }}>
      {children}
    </AuthContext.Provider>
  );
}

// Custom hook for easy access
export function useAuth() {
  const context = useContext(AuthContext);
  if (!context) {
    throw new Error('useAuth must be used within AuthProvider');
  }
  return context;
}

Why Use Context?

  • Global State: Authentication state available everywhere in the app (ie. Shared data across multiple components)
  • Avoid Prop Drilling: No need to pass auth functions through multiple components
  • Single Source of Truth: One place manages all authentication logic
  • Auto-Login Feature: Seamless UX - users are logged in immediately after registration

Why Wrap Context in a Custom Hook (useAuth)?

The useAuth() custom hook is a convenience wrapper around useContext(AuthContext). It has the CONSUMER which can consume the AuthContext PROVIDER. While we could use useContext(AuthContext) directly in components, the custom hook provides several advantages:

  1. Cleaner API: Components call useAuth() instead of useContext(AuthContext) - shorter and more semantic
  2. Error Handling: The hook throws a helpful error if used outside AuthProvider, catching mistakes early
  3. Type Safety: TypeScript knows the exact return type, providing better autocomplete
  4. Encapsulation: Implementation details hidden - we could change the underlying mechanism without affecting components
  5. Consistency: Standard React pattern - most Context APIs provide custom hooks (e.g., useRouter, useTheme)

Example:

// Without custom hook (verbose, no error checking)
const context = useContext(AuthContext);
if (!context) throw new Error('...');
const { register } = context;

// With custom hook (clean, safe)
const { register } = useAuth();

This pattern is so common in React that it's considered a best practice for any Context API.

Layer 3: API Client Architecture

The API client layer is split into two files:

auth-client.ts - Authentication-specific API calls:

export const authClient = {
  register: async (data: RegisterData): Promise<void> => {
    await api.post('/api/auth/register', data);
  },

  login: async (credentials: LoginCredentials): Promise<AuthTokens> => {
    // OAuth2 password flow
    const formData = new URLSearchParams();
    formData.append('username', credentials.email);
    formData.append('password', credentials.password);

    const response = await fetch(/* ... */);
    const tokens = await response.json();

    // Store JWT token
    tokenManager.set(tokens.access_token);
    return tokens;
  },
};

api-client.ts - Base HTTP client with automatic token injection:


// BLOCK 1

export async function apiClient<T>(
  endpoint: string,
  options: RequestOptions = {}
): Promise<T> {
  const config: RequestInit = {
    method: options.method || 'GET',
    headers: {
      'Content-Type': 'application/json',
      ...options.headers,
    },
  };

  // Auto-inject JWT token for protected endpoints
  if (options.requiresAuth) {
    const token = getAuthToken();
    if (token) {
      config.headers.Authorization = `Bearer ${token}`;
    }
  }

  const response = await fetch(`${API_BASE_URL}${endpoint}`, config);

  // Centralized error handling
  if (!response.ok) {
    const error = await response.json();
    throw new Error(error.detail || 'Request failed');
  }

  return response.json();
}


// BLOCK 2

// ❌ WITHOUT apiClient - Repetitive nightmare
async function getUser() {
  const token = localStorage.getItem('auth_token');
  const response = await fetch('http://localhost:8000/api/users/me', {
    method: 'GET',
    headers: {
      'Content-Type': 'application/json',
      'Authorization': `Bearer ${token}`,
    },
  });

  if (!response.ok) {
    const error = await response.json();
    throw new Error(error.detail || 'Request failed');
  }

  return response.json();
}

async function updateUser(data) {
  const token = localStorage.getItem('auth_token');
  const response = await fetch('http://localhost:8000/api/users/me', {
    method: 'PUT',
    headers: {
      'Content-Type': 'application/json',
      'Authorization': `Bearer ${token}`,
    },
    body: JSON.stringify(data),
  });

  if (!response.ok) {
    const error = await response.json();
    throw new Error(error.detail || 'Request failed');
  }

  return response.json();
}

// ... repeat this 50 times for every endpoint 


// BLOCK 3

// ✅ WITH apiClient - Clean and reusable
async function getUser() {
  return apiClient('/api/users/me', { requiresAuth: true });
}

async function updateUser(data) {
  return apiClient('/api/users/me', { 
    method: 'PUT', 
    requiresAuth: true,
    body: data 
  });
}

Benefits of using the apiClient:

  • DRY (Do not repeat yourself) Principle: All API calls use the same base client - See code BLOCK 2 and 3 above
  • Automatic Token Injection: Protected endpoints get JWT automatically - See code BLOCK 1 above
  • Centralized Error Handling: Consistent error messages across the app - See code BLOCK 1 above
  • Type Safety: TypeScript generics ensure type correctness - See code BLOCK 1 above

Part 2: Backend Architecture

The Service Layer Pattern

The backend follows a clean Router → Service → Database pattern:

  • Router: HTTP layer - handles requests and responses
  • Service: Business logic - validation, password hashing, database operations
  • Database: Data persistence - SQLAlchemy models

This separation ensures that business logic is decoupled from HTTP concerns, making it easier to test and maintain.

Backend Flow Diagram

Figure 3: Backend architecture showing the flow from API endpoint through validation, service layer, and database operations

Figure 3: Backend architecture showing the flow from API endpoint through validation, service layer, and database operations

The Router: API Endpoint Definition

The router is the entry point for HTTP requests. It defines the endpoint and delegates to the service layer.

# webapp/backend/auth/router.py
from fastapi import APIRouter, Depends
from sqlalchemy.orm import Session

router = APIRouter(prefix='/auth', tags=['auth'])

@router.post("/register", status_code=status.HTTP_201_CREATED)
async def register_user(
    db: Annotated[Session, Depends(get_db)],
    register_user_request: schemas.RegisterUserRequest
):
    """
    Register a new user

    - **email**: Valid email address (must be unique)
    - **first_name**: User's first name
    - **last_name**: User's last name
    - **password**: User's password (will be hashed)
    """
    service.register_user(db, register_user_request)
    return {"message": "User registered successfully"}

Key Features:

  • Dependency Injection: Database session injected automatically
  • Pydantic Validation: Request body validated against schema
  • Status Code: Returns 201 Created (REST convention)
  • Clean Separation: Router handles HTTP, service handles logic

Pydantic Schema: Request Validation

Pydantic schemas define the shape and validation rules for request data.

# webapp/backend/auth/schemas.py
from pydantic import BaseModel, EmailStr

class RegisterUserRequest(BaseModel):
    """Request model for user registration"""
    email: EmailStr
    first_name: str
    last_name: str
    password: str

What Pydantic Does:

  • Email Validation: Checks format (e.g., [email protected])
  • Type Checking: Ensures all fields are correct types
  • Automatic Parsing: Converts JSON to Python objects
  • Error Messages: Returns detailed validation errors

If validation fails, FastAPI automatically returns a 422 Unprocessable Entity response with details about what went wrong.

Service Layer: Business Logic

The service layer contains the core registration logic, including password hashing and database operations.

# webapp/backend/auth/service.py
from passlib.context import CryptContext
from uuid import uuid4

bcrypt_context = CryptContext(schemes=['bcrypt'], deprecated='auto')

def get_password_hash(password: str) -> str:
    """Hash a plain password using bcrypt"""
    return bcrypt_context.hash(password)

def register_user(db: Session, register_user_request: schemas.RegisterUserRequest):
    """
    Register a new user
    Raises UserAlreadyExistsError if email is already registered
    """
    try:
        # Create User model with hashed password
        create_user_model = User(
            id=uuid4(),  # Generate unique UUID
            email=register_user_request.email,
            first_name=register_user_request.first_name,
            last_name=register_user_request.last_name,
            password_hash=get_password_hash(register_user_request.password)
        )

        # Add to database and commit
        db.add(create_user_model)
        db.commit()

        logging.info(f"Successfully registered user: {register_user_request.email}")

    except IntegrityError:
        # Email already exists (UNIQUE constraint violation)
        db.rollback()
        raise UserAlreadyExistsError()

Critical Security Feature: bcrypt Password Hashing

The password is never stored in plain text. Instead, it's hashed using bcrypt:

Input:  "securepass123"
Output: "$2b$12$LQv3c1yqBWVHxkd0LHAkCOYz6TtxMQJqhN8/LewY5GyYzS7eFa.oi"

Why bcrypt?

  • Salted: Automatically adds random salt (prevents rainbow table attacks)
  • Adaptive: Can increase cost factor as computers get faster
  • One-way: Cannot reverse hash to get original password
  • Industry Standard: Used by GitHub, Facebook, and other major platforms

Database Model: SQLAlchemy Schema

The User model defines the database table structure.

# webapp/backend/entities/users.py
from sqlalchemy import Column, String, DateTime, UUID
from datetime import datetime, timezone

class User(Base):
    __tablename__ = 'users'

    id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
    email = Column(String, unique=True, nullable=False)
    first_name = Column(String, nullable=False)
    last_name = Column(String, nullable=False)
    password_hash = Column(String, nullable=False)
    created_at = Column(DateTime(timezone=True), default=lambda: datetime.now(timezone.utc))
    updated_at = Column(DateTime(timezone=True), onupdate=lambda: datetime.now(timezone.utc))

    # Relationships
    email_accounts = relationship("EmailAccount", back_populates="user", cascade="all, delete-orphan")

Database Constraints:

  • Primary Key: UUID (globally unique, secure)
  • Unique Email: Database enforces uniqueness
  • Not Null: All required fields enforced at DB level
  • Cascade Delete: Deleting user deletes all related email accounts

Security Considerations

Defense in Depth

Security is implemented at multiple layers:

1. Frontend Validation

  • Client-side checks for immediate feedback
  • Password length requirements (minimum 8 characters)
  • Password confirmation matching

2. Backend Validation

  • Pydantic schema validation (type checking, email format)
  • Database constraints (unique email, not null)

3. Password Security

  • bcrypt hashing with automatic salting
  • Cost factor of 12 (2^12 = 4096 iterations)
  • Never logged or displayed in plain text

4. SQL Injection Prevention

  • SQLAlchemy ORM automatically parameterizes queries
  • No raw SQL with user input

5. Token Security

  • JWT tokens for stateless authentication
  • Tokens stored in localStorage (HTTPS in production)
  • Automatic expiration (30 days)

Error Handling

The system handles errors gracefully at every layer:

Email Already Exists

HTTP 400 Bad Request
{
  "detail": "User with this email already exists"
}

Invalid Email Format

HTTP 422 Unprocessable Entity
{
  "detail": [
    {
      "loc": ["body", "email"],
      "msg": "value is not a valid email address",
      "type": "value_error.email"
    }
  ]
}

Missing Required Field

HTTP 422 Unprocessable Entity
{
  "detail": [
    {
      "loc": ["body", "first_name"],
      "msg": "field required",
      "type": "value_error.missing"
    }
  ]
}

The Auto-Login Feature

One of the UX improvements in this implementation is automatic login after registration. Here's how it works:

  1. User submits registration form
  2. Backend creates account successfully
  3. Frontend Auth Context automatically calls login() with the same credentials
  4. JWT token is retrieved and stored
  5. User state is updated globally
  6. User is redirected to /accounts dashboard

Benefits:

  • Seamless UX: No need to manually log in after registering
  • Fewer Steps: Reduces friction in onboarding flow
  • Immediate Value: User can start using the app right away

Testing the Flow

Manual Testing Steps

  1. Start Backend:
   cd webapp/backend
   uv run main.py
  1. Start Frontend:
   cd webapp/frontend
   npm run dev
  1. Navigate to Registration:
   http://localhost:3000/auth/register
  1. Fill Form:

    • Email: [email protected]
    • First Name: Test
    • Last Name: User
    • Password: password123
    • Confirm Password: password123
  2. Verify:

    • Check console logs for success
    • Verify redirect to accounts page
    • Check database for new user record

Database Verification

sqlite3 webapp/backend/databse.db
SELECT id, email, first_name, last_name, created_at 
FROM users 
WHERE email = '[email protected]';

Performance Considerations

Password Hashing Cost

The bcrypt cost factor is set to 12, which provides a good balance:

  • Cost 10: ~100ms (too fast, less secure)
  • Cost 12: ~250ms (current setting, recommended)
  • Cost 14: ~1000ms (too slow for user experience)

Database Indexing

The email field has a unique index for fast lookups:

CREATE UNIQUE INDEX idx_users_email ON users(email);

This ensures O(log n) search time for email lookups during login.

Lessons Learned

What Worked Well

Layered Architecture: Clean separation of concerns made the code maintainable and testable

React Context: Global auth state eliminated prop drilling and simplified components

Pydantic Validation: Automatic validation saved time and caught errors early

bcrypt Hashing: Industry-standard security with minimal implementation effort

Auto-Login: Improved UX significantly with minimal code

What Could Be Improved

🔄 Add Unit Tests: Currently no automated tests for registration flow

🔄 Rate Limiting: Prevent brute-force registration attempts

🔄 Email Verification: Send confirmation email before activating account

🔄 Password Strength Meter: Visual feedback on password quality

🔄 CAPTCHA: Prevent automated bot registrations

Conclusion

Building a secure, user-friendly registration flow requires attention to detail at every layer of the stack. By following clean architecture principles, implementing security best practices, and focusing on user experience, we've created a registration system that is:

  • Secure: bcrypt hashing, SQL injection prevention, multiple validation layers
  • Maintainable: Clear separation of concerns, type safety, reusable components
  • User-Friendly: Auto-login, clear error messages, responsive validation
  • Scalable: Stateless JWT authentication, efficient database queries

The key takeaway is that good architecture pays dividends. The three-layer frontend abstraction and the router-service-database pattern on the backend make the code easy to understand, test, and extend.

Whether you're building a simple side project or a production application, these patterns and practices will serve you well.

Code Repository

The complete source code for this implementation is available in my AI Email Coach project:

  • Frontend: webapp/frontend/app/auth/register/
  • Backend: webapp/backend/auth/
  • Database Models: webapp/backend/entities/users.py

Written by Nazmus Ashrafi | Built with Next.js, FastAPI, and ❤️

Full Source: Available in private repository upon request
Please contact me for access: [email protected]

La Clave para una Landing Page que Realmente Funcione

2025-12-01 00:11:52

Si te has metido en el mundo del desarrollo web, sabes que crear una landing page moderna no se trata solo de que se vea bonita, sino de que funcione de verdad y convierta visitantes en clientes. Este módulo de Your.Code.Web es un recurso excelente porque no solo te enseña a maquetar con HTML, sino que se centra en los pilares fundamentales que a menudo se olvidan: la semántica y la accesibilidad.

Aquí no solo vas a ver etiquetas, sino la razón de ser de cada una. Te explican cómo usar correctamente los elementos de HTML5 —como header, main y footer— para que tu página no solo esté bien organizada, sino que los motores de búsqueda y los lectores de pantalla puedan entenderla perfectamente. Esto es crucial en la web actual.

Además, el curso desglosa la estructura ideal de cualquier landing exitosa: desde el impacto inicial del bloque "Hero" con su llamada a la acción, pasando por cómo presentar las características y los testimonios de forma eficaz. Y lo mejor de todo es que adoptan la mentalidad mobile-first. Esto significa que estás aprendiendo a construir la página pensando primero en el móvil, garantizando que tu diseño sea responsivo y rápido para cualquier usuario, lo cual es vital para no perder ventas. Si buscas llevar tus habilidades de maquetación al nivel profesional, este tipo de enfoque detallado en las buenas prácticas es justo lo que necesitas.