Skip to main content

stack-2-angular-python

Stack 2: Angular + Python Backend (FastAPI + SQLAlchemy)

Stack 2 keeps Angular on the frontend (maintaining TwinSpires' frontend comfort) while introducing a Python backend with FastAPI. This option is ideal when ML/AI integration and data science capabilities are important strategic priorities, as Python is the dominant language for these domains.


1. Overview

  • Frontend: Angular (TypeScript)
  • Backend: FastAPI (Python)
  • Database: PostgreSQL (or MySQL) via SQLAlchemy + Alembic
  • Infra (example):
    • Frontend: S3 + CloudFront (static hosting)
    • Backend: Lambda + API Gateway (Serverless Framework) or containerized on ECS/Kubernetes
  • CI/CD: GitHub Actions (separate pipelines for frontend and backend)

This stack maintains the clear FE/BE separation TwinSpires is used to from Spring Boot + Angular, while introducing Python on the backend for stronger ML/AI and data science integration.


2. Architecture

At a high level:

  • Angular SPA is built and deployed as static assets behind CloudFront.
  • FastAPI runs in a serverless function or container, exposing JSON endpoints with automatic OpenAPI documentation.
  • SQLAlchemy manages database access with Alembic for migrations.
  • Python ecosystem enables easy integration with ML/AI services and data science tools.

3. Sample repo structure

Using the example repo in this project:

stack2-angular-python/
frontend/ # Angular app
src/app/
components/
services/
pages/
backend/ # FastAPI + SQLAlchemy
app/
routes/
services/
repositories/
models/
schemas/
alembic/ # Database migrations
versions/
tests/
tech-stack-docs/ # Docusaurus documentation (this site)

Key points:

  • routes/: FastAPI route handlers (APIRouter), thin controllers that map HTTP → service calls.
  • services/: Business logic, orchestrating repositories and external APIs.
  • repositories/: Data access layer using SQLAlchemy, isolated from services.
  • models/: SQLAlchemy ORM models.
  • schemas/: Pydantic schemas for request/response validation and OpenAPI generation.
  • alembic/: Database migrations.

4. Example vertical slice

This slice shows a simple "Todos" list: Angular calls a REST endpoint implemented in FastAPI, which reads from the DB via SQLAlchemy.

Backend – FastAPI route

# backend/app/routes/todos.py
from fastapi import APIRouter, Depends
from ..schemas.todos import TodoOut, TodoCreate
from ..services.todo_service import TodoService
from ..dependencies import get_todo_service

router = APIRouter(prefix="/todos", tags=["todos"])

@router.get("/", response_model=list[TodoOut])
async def list_todos(service: TodoService = Depends(get_todo_service)):
return await service.list_todos()

@router.post("/", response_model=TodoOut, status_code=201)
async def create_todo(
todo: TodoCreate,
service: TodoService = Depends(get_todo_service)
):
return await service.create_todo(todo)

Backend – Service + SQLAlchemy

# backend/app/services/todo_service.py
from sqlalchemy.ext.asyncio import AsyncSession
from ..models.todo import Todo
from ..schemas.todos import TodoCreate, TodoOut

class TodoService:
def __init__(self, db: AsyncSession):
self.db = db

async def list_todos(self) -> list[TodoOut]:
result = await self.db.execute(select(Todo))
todos = result.scalars().all()
return [TodoOut.from_orm(todo) for todo in todos]

async def create_todo(self, todo: TodoCreate) -> TodoOut:
db_todo = Todo(**todo.dict())
self.db.add(db_todo)
await self.db.commit()
await self.db.refresh(db_todo)
return TodoOut.from_orm(db_todo)

Backend – Pydantic schemas

# backend/app/schemas/todos.py
from pydantic import BaseModel

class TodoBase(BaseModel):
title: str
completed: bool = False

class TodoCreate(TodoBase):
pass

class TodoOut(TodoBase):
id: int

class Config:
from_attributes = True

Frontend – Angular service

// frontend/src/app/services/todo.service.ts
import { HttpClient } from "@angular/common/http";
import { Injectable } from "@angular/core";
import { Observable } from "rxjs";

export interface Todo {
id: number;
title: string;
completed: boolean;
}

@Injectable({ providedIn: "root" })
export class TodoService {
private readonly baseUrl = "/api/todos";

constructor(private http: HttpClient) {}

getTodos(): Observable<Todo[]> {
return this.http.get<Todo[]>(this.baseUrl);
}

createTodo(todo: { title: string; completed?: boolean }): Observable<Todo> {
return this.http.post<Todo>(this.baseUrl, todo);
}
}

Type generation from OpenAPI

FastAPI automatically generates OpenAPI/Swagger documentation. TypeScript types can be generated:

# Generate TypeScript types from FastAPI OpenAPI schema
npx openapi-typescript http://localhost:8000/openapi.json -o src/app/models/api-types.ts

This pattern scales naturally: each vertical slice adds:

  • One or more Angular components/routes
  • One Angular service method
  • One FastAPI route (and perhaps service + repository methods)
  • Pydantic schemas for validation
  • Potential schema and migration changes in Alembic

5. CI/CD outline (example with GitHub Actions)

Separate pipelines can be maintained for frontend and backend while still treating each feature as a vertical slice.

Frontend (Angular)

  1. On pull request:
    • npm ci
    • npm run lint
    • npm test
    • npm run build
    • Deploy a preview environment (e.g., temporary S3/CloudFront distro).
  2. On merge to main:
    • Build production assets.
    • Upload to S3.
    • Invalidate CloudFront cache.

Backend (FastAPI + SQLAlchemy)

  1. On pull request:
    • Set up Python environment (python -m venv venv, pip install -r requirements.txt).
    • Run linting (ruff, mypy).
    • Run tests (pytest).
    • Build and run integration tests (optionally with a disposable database).
  2. On merge to main:
    • Run Alembic migrations against a staging database.
    • Build and publish Docker image or Lambda package.
    • Deploy via Serverless Framework or IaC (CloudFormation/Terraform).

Type generation step (optional but recommended):

  • After backend deployment, generate TypeScript types from OpenAPI schema.
  • Commit generated types to frontend repo or use in CI.

Vertical slice flow

  • A feature branch can update both frontend/ and backend/.
  • CI validates both sides together.
  • Once merged, both pipelines deploy, resulting in an end-to-end slice being live.

6. Pros

  • Keeps Angular for TwinSpires frontend team:
    • Frontend teams can continue using the framework they know.
    • No forced Angular → React migration.
  • Strong ML/AI and data science integration:
    • Python is the dominant language for ML, data science, and AI.
    • Easy to share libraries and tooling between product backend and data/ML teams.
    • Can integrate ML models directly into the backend service.
  • Modern, typed Python backend:
    • FastAPI + Pydantic offer strong typing, validation, and auto-generated OpenAPI docs.
    • Async/await support for high performance.
    • Excellent developer experience with automatic API documentation.
  • Future-proof for data-driven features:
    • Easy to add data processing, analytics, and ML features.
    • Can leverage Python's rich ecosystem (pandas, numpy, scikit-learn, etc.).
  • OpenAPI integration:
    • Automatic OpenAPI/Swagger documentation.
    • Can generate TypeScript types from OpenAPI schema for frontend type safety.

7. Cons & risks

  • Two-language stack:
    • TypeScript on the frontend, Python on the backend.
    • Requires clear API contracts and documentation; no shared types out of the box (must generate from OpenAPI).
    • Different tooling, linters, and test frameworks across FE and BE.
  • Backend learning curve for TwinSpires team:
    • Java backend developers need to learn Python idioms (async, packaging, type hints, virtual environments).
    • Python packaging and dependency management differs from Java/Maven.
  • Build speed for delivery team:
    • Medium: Python adds a learning step compared to Node.js (which is in the delivery team's wheelhouse).
    • Requires Python environment setup and understanding of Python tooling.
  • Lambda packaging complexity:
    • Python Lambda packages can be larger than Node.js.
    • Need to manage dependencies carefully to keep package size reasonable.

8. Fit for BrisNet

Stack 2 is a strong fit when:

  • Build speed for our team:
  • Medium: Python backend is not in our core wheelhouse, so there's a learning curve. However, FastAPI is modern and well-documented, making it approachable.
  • Comfort for TwinSpires engineering (Angular/Java specialists):
    • Frontend: High, because Angular remains the primary UI framework.
    • Backend: Moderate, because Python is a new language/runtime, though Java experience helps with OOP patterns and async concepts.
  • Strategic priorities:
    • ML/AI integration is important for future features.
    • Data science and analytics capabilities are needed.
    • Want to leverage Python's ecosystem for data processing.
  • Long-term vision:
    • Planning to build data-driven features, recommendations, or ML-powered functionality.
    • Want to share code/libraries with data science teams.

When Stack 2 makes sense:

  • ML/AI and data science integration is a strategic priority.
  • Want to keep Angular on the frontend (TwinSpires comfort).
  • Willing to invest in Python backend skills for long-term ML/AI capabilities.
  • Planning to build data-driven features that benefit from Python's ecosystem.

Full example repo: https://github.com/MillionOnMars/tech-stack-comparison/tree/main/stack2-angular-python

Trade-off:

Stack 2 trades some immediate build speed (Python learning curve) for stronger long-term ML/AI capabilities. If ML/AI is not a near-term priority, there's little reason to pick this stack over Stack 3 / Stack 1.

In other words: Stack 2 is the best choice when ML/AI integration is important, even if it means a slightly slower initial build phase due to Python learning curve.