Appendix B — Distribution Templates
This appendix provides ready-to-use templates and configurations for multi-platform distribution. These templates complement the architecture patterns discussed in Chapter 8: Multi-Platform Distribution.
B.1 Companion Repositories
To quickly start a new project with this architecture, use one of our companion repositories:
B.1.2 GitHub Template
For quick starts without installing tools:
- Visit https://github.com/michael-borck/ship-it-template
- Click “Use this template”
- Clone your new repository and customize
B.1.3 Example Repository
A complete working example with all features:
- https://github.com/michael-borck/ship-it-example
B.2 FastAPI Backend Template
B.2.1 pyproject.toml
[project]
name = "my-app-backend"
version = "0.1.0"
description = "Backend API for My Application"
readme = "README.md"
requires-python = ">=3.11"
dependencies = [
"fastapi>=0.109.0",
"uvicorn[standard]>=0.27.0",
"pydantic>=2.5.0",
"pydantic-settings>=2.1.0",
"sqlalchemy>=2.0.0",
"alembic>=1.13.0",
"python-multipart>=0.0.6",
]
[project.optional-dependencies]
dev = [
"pytest>=7.4.0",
"pytest-cov>=4.1.0",
"pytest-asyncio>=0.23.0",
"httpx>=0.26.0",
"ruff>=0.1.0",
"mypy>=1.8.0",
]
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[tool.hatch.build.targets.wheel]
packages = ["src/my_app"]
[tool.ruff]
target-version = "py311"
line-length = 88
[tool.ruff.lint]
select = ["E", "F", "I", "B", "UP"]
[tool.mypy]
python_version = "3.11"
strict = true
[tool.pytest.ini_options]
testpaths = ["tests"]
asyncio_mode = "auto"
[tool.poe.tasks]
dev = "uvicorn my_app.main:app --reload"
test = "pytest --cov=my_app"
lint = "ruff check ."
format = "ruff format ."
typecheck = "mypy src/"
check = ["format", "lint", "typecheck", "test"]B.2.2 Application Configuration
# src/my_app/config.py
"""Application configuration using pydantic-settings."""
from pydantic_settings import BaseSettings
from functools import lru_cache
class Settings(BaseSettings):
"""Application settings loaded from environment variables."""
# Application
app_name: str = "My Application"
debug: bool = False
version: str = "0.1.0"
# Server
host: str = "0.0.0.0"
port: int = 8000
# Database
database_url: str = "sqlite:///./app.db"
# CORS
cors_origins: list[str] = ["http://localhost:5173"]
# Security
secret_key: str = "change-me-in-production"
class Config:
env_file = ".env"
env_file_encoding = "utf-8"
@lru_cache
def get_settings() -> Settings:
"""Get cached settings instance."""
return Settings()B.2.3 Database Setup with SQLAlchemy
# src/my_app/database.py
"""Database configuration and session management."""
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker, DeclarativeBase
from contextlib import contextmanager
from typing import Generator
from .config import get_settings
class Base(DeclarativeBase):
"""Base class for SQLAlchemy models."""
pass
engine = create_engine(
get_settings().database_url,
connect_args={"check_same_thread": False} # For SQLite
)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
def get_db() -> Generator:
"""Dependency for getting database sessions."""
db = SessionLocal()
try:
yield db
finally:
db.close()
@contextmanager
def get_db_context():
"""Context manager for database sessions outside of FastAPI."""
db = SessionLocal()
try:
yield db
db.commit()
except Exception:
db.rollback()
raise
finally:
db.close()
def init_db():
"""Initialize database tables."""
Base.metadata.create_all(bind=engine)B.2.4 Backend Dockerfile
# backend/Dockerfile
FROM python:3.11-slim
WORKDIR /app
# Install uv
RUN pip install uv
# Copy dependency files
COPY pyproject.toml uv.lock ./
# Install dependencies
RUN uv sync --frozen --no-dev
# Copy source code
COPY src ./src
# Create non-root user
RUN useradd -m appuser && chown -R appuser:appuser /app
USER appuser
# Expose port
EXPOSE 8000
# Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:8000/health')"
# Run application
CMD ["uv", "run", "uvicorn", "my_app.main:app", "--host", "0.0.0.0", "--port", "8000"]B.3 React Frontend Template
B.3.1 package.json
{
"name": "my-app-frontend",
"private": true,
"version": "0.1.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"preview": "vite preview",
"test": "vitest",
"test:coverage": "vitest run --coverage",
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
"typecheck": "tsc --noEmit"
},
"dependencies": {
"@tanstack/react-query": "^5.17.0",
"axios": "^1.6.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-router-dom": "^6.21.0"
},
"devDependencies": {
"@types/react": "^18.2.0",
"@types/react-dom": "^18.2.0",
"@typescript-eslint/eslint-plugin": "^6.0.0",
"@typescript-eslint/parser": "^6.0.0",
"@vitejs/plugin-react": "^4.2.0",
"eslint": "^8.56.0",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-react-refresh": "^0.4.0",
"typescript": "^5.3.0",
"vite": "^5.0.0",
"vite-plugin-pwa": "^0.17.0",
"vitest": "^1.2.0"
}
}B.3.2 Vite Configuration with PWA
// frontend/vite.config.ts
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import { VitePWA } from 'vite-plugin-pwa';
export default defineConfig({
plugins: [
react(),
VitePWA({
registerType: 'autoUpdate',
includeAssets: ['favicon.ico', 'apple-touch-icon.png', 'mask-icon.svg'],
manifest: {
name: 'My Application',
short_name: 'MyApp',
description: 'A multi-platform application',
theme_color: '#ffffff',
background_color: '#ffffff',
display: 'standalone',
scope: '/',
start_url: '/',
icons: [
{
src: 'pwa-192x192.png',
sizes: '192x192',
type: 'image/png',
},
{
src: 'pwa-512x512.png',
sizes: '512x512',
type: 'image/png',
},
{
src: 'pwa-512x512.png',
sizes: '512x512',
type: 'image/png',
purpose: 'any maskable',
},
],
},
devOptions: {
enabled: true,
},
workbox: {
globPatterns: ['**/*.{js,css,html,ico,png,svg,woff2}'],
runtimeCaching: [
{
urlPattern: /^https:\/\/api\..*/i,
handler: 'NetworkFirst',
options: {
cacheName: 'api-cache',
expiration: {
maxEntries: 100,
maxAgeSeconds: 60 * 60 * 24, // 24 hours
},
cacheableResponse: {
statuses: [0, 200],
},
},
},
],
},
}),
],
server: {
port: 5173,
proxy: {
'/api': {
target: 'http://localhost:8000',
changeOrigin: true,
},
},
},
build: {
outDir: 'dist',
sourcemap: true,
},
});B.3.3 TypeScript Configuration
// frontend/tsconfig.json
{
"compilerOptions": {
"target": "ES2020",
"useDefineForClassFields": true,
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"module": "ESNext",
"skipLibCheck": true,
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx",
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"baseUrl": ".",
"paths": {
"@/*": ["src/*"]
}
},
"include": ["src"],
"references": [{ "path": "./tsconfig.node.json" }]
}B.3.4 API Client Template
// frontend/src/services/api.ts
import axios, { AxiosInstance, AxiosError } from 'axios';
const API_BASE_URL = import.meta.env.VITE_API_URL || '/api';
class ApiClient {
private client: AxiosInstance;
constructor() {
this.client = axios.create({
baseURL: API_BASE_URL,
headers: {
'Content-Type': 'application/json',
},
});
// Response interceptor for error handling
this.client.interceptors.response.use(
(response) => response,
(error: AxiosError) => {
if (error.response?.status === 401) {
// Handle unauthorized
window.location.href = '/login';
}
return Promise.reject(error);
}
);
}
async get<T>(url: string): Promise<T> {
const response = await this.client.get<T>(url);
return response.data;
}
async post<T>(url: string, data: unknown): Promise<T> {
const response = await this.client.post<T>(url, data);
return response.data;
}
async put<T>(url: string, data: unknown): Promise<T> {
const response = await this.client.put<T>(url, data);
return response.data;
}
async delete(url: string): Promise<void> {
await this.client.delete(url);
}
setAuthToken(token: string): void {
this.client.defaults.headers.common['Authorization'] = `Bearer ${token}`;
}
clearAuthToken(): void {
delete this.client.defaults.headers.common['Authorization'];
}
}
export const apiClient = new ApiClient();B.4 Electron Wrapper Template
B.4.1 Main Process
// electron/main.js
const { app, BrowserWindow, Menu, shell, ipcMain } = require('electron');
const path = require('path');
const { autoUpdater } = require('electron-updater');
const log = require('electron-log');
// Configure logging
log.transports.file.level = 'info';
autoUpdater.logger = log;
// Handle Squirrel events for Windows
if (require('electron-squirrel-startup')) {
app.quit();
}
const isDev = process.env.NODE_ENV === 'development';
let mainWindow;
function createWindow() {
mainWindow = new BrowserWindow({
width: 1200,
height: 800,
minWidth: 800,
minHeight: 600,
webPreferences: {
preload: path.join(__dirname, 'preload.js'),
contextIsolation: true,
nodeIntegration: false,
sandbox: true,
},
show: false, // Show when ready
titleBarStyle: 'hiddenInset', // macOS
});
// Show window when ready
mainWindow.once('ready-to-show', () => {
mainWindow.show();
});
// Load content
if (isDev) {
mainWindow.loadURL('http://localhost:5173');
mainWindow.webContents.openDevTools();
} else {
mainWindow.loadFile(path.join(__dirname, '../frontend/dist/index.html'));
}
// Open external links in browser
mainWindow.webContents.setWindowOpenHandler(({ url }) => {
shell.openExternal(url);
return { action: 'deny' };
});
// Build menu
const template = [
{
label: 'File',
submenu: [
{ role: 'quit' }
]
},
{
label: 'Edit',
submenu: [
{ role: 'undo' },
{ role: 'redo' },
{ type: 'separator' },
{ role: 'cut' },
{ role: 'copy' },
{ role: 'paste' },
{ role: 'selectAll' }
]
},
{
label: 'View',
submenu: [
{ role: 'reload' },
{ role: 'forceReload' },
{ role: 'toggleDevTools' },
{ type: 'separator' },
{ role: 'resetZoom' },
{ role: 'zoomIn' },
{ role: 'zoomOut' },
{ type: 'separator' },
{ role: 'togglefullscreen' }
]
},
{
label: 'Help',
submenu: [
{
label: 'Documentation',
click: async () => {
await shell.openExternal('https://example.com/docs');
}
},
{
label: 'Check for Updates',
click: () => {
autoUpdater.checkForUpdatesAndNotify();
}
}
]
}
];
const menu = Menu.buildFromTemplate(template);
Menu.setApplicationMenu(menu);
}
// App lifecycle
app.whenReady().then(() => {
createWindow();
// Check for updates (production only)
if (!isDev) {
autoUpdater.checkForUpdatesAndNotify();
}
app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0) {
createWindow();
}
});
});
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit();
}
});
// IPC handlers
ipcMain.handle('get-version', () => {
return app.getVersion();
});
ipcMain.handle('check-for-updates', () => {
return autoUpdater.checkForUpdatesAndNotify();
});
// Auto-updater events
autoUpdater.on('checking-for-update', () => {
log.info('Checking for update...');
});
autoUpdater.on('update-available', (info) => {
log.info('Update available:', info);
});
autoUpdater.on('update-not-available', (info) => {
log.info('Update not available:', info);
});
autoUpdater.on('error', (err) => {
log.error('Error in auto-updater:', err);
});
autoUpdater.on('download-progress', (progressObj) => {
log.info(`Download speed: ${progressObj.bytesPerSecond} - Downloaded ${progressObj.percent}%`);
});
autoUpdater.on('update-downloaded', (info) => {
log.info('Update downloaded:', info);
// Optionally prompt user and restart
});B.4.2 Preload Script
// electron/preload.js
const { contextBridge, ipcRenderer } = require('electron');
contextBridge.exposeInMainWorld('electronAPI', {
// App info
getVersion: () => ipcRenderer.invoke('get-version'),
platform: process.platform,
// Updates
checkForUpdates: () => ipcRenderer.invoke('check-for-updates'),
// Window controls (if using frameless window)
minimize: () => ipcRenderer.send('window-minimize'),
maximize: () => ipcRenderer.send('window-maximize'),
close: () => ipcRenderer.send('window-close'),
});B.4.3 Electron Builder Configuration
{
"name": "my-app",
"version": "1.0.0",
"description": "My Application",
"main": "main.js",
"author": "Your Name <you@example.com>",
"license": "MIT",
"scripts": {
"start": "electron .",
"build": "electron-builder",
"build:all": "electron-builder -mwl",
"build:mac": "electron-builder --mac",
"build:win": "electron-builder --win",
"build:linux": "electron-builder --linux",
"release": "electron-builder --publish always"
},
"build": {
"appId": "com.yourname.myapp",
"productName": "My Application",
"copyright": "Copyright © 2025 Your Name",
"directories": {
"output": "dist",
"buildResources": "build"
},
"files": [
"main.js",
"preload.js",
"../frontend/dist/**/*"
],
"extraMetadata": {
"main": "main.js"
},
"mac": {
"category": "public.app-category.productivity",
"icon": "build/icon.icns",
"hardenedRuntime": true,
"gatekeeperAssess": false,
"entitlements": "build/entitlements.mac.plist",
"entitlementsInherit": "build/entitlements.mac.plist",
"target": [
{
"target": "dmg",
"arch": ["x64", "arm64"]
},
{
"target": "zip",
"arch": ["x64", "arm64"]
}
]
},
"win": {
"icon": "build/icon.ico",
"target": [
{
"target": "nsis",
"arch": ["x64"]
},
{
"target": "portable",
"arch": ["x64"]
}
]
},
"nsis": {
"oneClick": false,
"perMachine": false,
"allowToChangeInstallationDirectory": true,
"deleteAppDataOnUninstall": true
},
"linux": {
"icon": "build/icons",
"category": "Utility",
"target": [
{
"target": "AppImage",
"arch": ["x64"]
},
{
"target": "deb",
"arch": ["x64"]
}
]
},
"publish": {
"provider": "github",
"owner": "your-username",
"repo": "my-app",
"releaseType": "release"
}
},
"dependencies": {
"electron-log": "^5.0.0",
"electron-updater": "^6.1.0"
},
"devDependencies": {
"electron": "^28.0.0",
"electron-builder": "^24.9.0"
}
}B.5 Docker Configuration
B.5.1 Combined Dockerfile (Backend + Frontend)
# docker/Dockerfile.combined
# Build stage for frontend
FROM node:20-alpine AS frontend-builder
WORKDIR /app/frontend
COPY frontend/package*.json ./
RUN npm ci --production=false
COPY frontend/ ./
RUN npm run build
# Build stage for backend dependencies
FROM python:3.11-slim AS backend-builder
WORKDIR /app
RUN pip install uv
COPY backend/pyproject.toml backend/uv.lock ./
RUN uv sync --frozen --no-dev
# Production stage
FROM python:3.11-slim AS production
WORKDIR /app
# Install uv for running
RUN pip install uv
# Copy backend dependencies
COPY --from=backend-builder /app/.venv /app/.venv
# Copy backend source
COPY backend/src ./src
# Copy frontend build
COPY --from=frontend-builder /app/frontend/dist ./static
# Create non-root user
RUN useradd -m -u 1000 appuser && \
chown -R appuser:appuser /app
USER appuser
# Environment
ENV PATH="/app/.venv/bin:$PATH"
ENV PYTHONPATH="/app/src"
ENV STATIC_FILES_DIR="/app/static"
EXPOSE 8000
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:8000/health')"
CMD ["uvicorn", "my_app.main:app", "--host", "0.0.0.0", "--port", "8000"]B.5.2 Docker Compose for Development
# docker/docker-compose.yml
version: "3.9"
services:
backend:
build:
context: ../backend
dockerfile: Dockerfile.dev
ports:
- "8000:8000"
volumes:
- ../backend/src:/app/src:ro
environment:
- DEBUG=true
- DATABASE_URL=sqlite:///./dev.db
command: uv run uvicorn my_app.main:app --reload --host 0.0.0.0 --port 8000
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
interval: 30s
timeout: 10s
retries: 3
frontend:
build:
context: ../frontend
dockerfile: Dockerfile.dev
ports:
- "5173:5173"
volumes:
- ../frontend/src:/app/src:ro
- ../frontend/public:/app/public:ro
environment:
- VITE_API_URL=http://localhost:8000/api
command: npm run dev -- --host 0.0.0.0
depends_on:
- backend
# Optional: PostgreSQL for production-like development
db:
image: postgres:16-alpine
ports:
- "5432:5432"
environment:
- POSTGRES_USER=myapp
- POSTGRES_PASSWORD=devpassword
- POSTGRES_DB=myapp
volumes:
- postgres_data:/var/lib/postgresql/data
volumes:
postgres_data:B.6 GitHub Actions Workflows
B.6.1 Complete CI/CD Workflow
# .github/workflows/ci-cd.yml
name: CI/CD
on:
push:
branches: [main]
tags: ['v*']
pull_request:
branches: [main]
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
jobs:
# ============================================
# Testing
# ============================================
test-backend:
name: Test Backend
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Install uv
run: pip install uv
- name: Install dependencies
working-directory: ./backend
run: uv sync --all-extras
- name: Run linting
working-directory: ./backend
run: |
uv run ruff check .
uv run ruff format --check .
- name: Run type checking
working-directory: ./backend
run: uv run mypy src/
- name: Run tests
working-directory: ./backend
run: uv run pytest --cov --cov-report=xml
- name: Upload coverage
uses: codecov/codecov-action@v3
with:
files: ./backend/coverage.xml
flags: backend
test-frontend:
name: Test Frontend
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
cache-dependency-path: frontend/package-lock.json
- name: Install dependencies
working-directory: ./frontend
run: npm ci
- name: Run linting
working-directory: ./frontend
run: npm run lint
- name: Run type checking
working-directory: ./frontend
run: npm run typecheck
- name: Run tests
working-directory: ./frontend
run: npm run test:coverage
- name: Build
working-directory: ./frontend
run: npm run build
# ============================================
# Docker Build
# ============================================
build-docker:
name: Build Docker Image
needs: [test-backend, test-frontend]
runs-on: ubuntu-latest
if: github.event_name == 'push'
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to Container Registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=ref,event=branch
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=sha
- name: Build and push
uses: docker/build-push-action@v5
with:
context: .
file: ./docker/Dockerfile.combined
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
# ============================================
# Desktop Builds
# ============================================
build-desktop:
name: Build Desktop (${{ matrix.os }})
needs: [test-backend, test-frontend]
if: startsWith(github.ref, 'refs/tags/v')
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu-latest
platform: linux
- os: windows-latest
platform: win
- os: macos-latest
platform: mac
steps:
- uses: actions/checkout@v4
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Install frontend dependencies
working-directory: ./frontend
run: npm ci
- name: Build frontend
working-directory: ./frontend
run: npm run build
- name: Install Electron dependencies
working-directory: ./electron
run: npm ci
- name: Build Electron app
working-directory: ./electron
run: npm run build:${{ matrix.platform }}
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Upload artifacts
uses: actions/upload-artifact@v4
with:
name: desktop-${{ matrix.platform }}
path: |
electron/dist/*.dmg
electron/dist/*.zip
electron/dist/*.exe
electron/dist/*.AppImage
electron/dist/*.deb
if-no-files-found: ignore
# ============================================
# Release
# ============================================
release:
name: Create Release
needs: [build-docker, build-desktop]
if: startsWith(github.ref, 'refs/tags/v')
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/checkout@v4
- name: Download all artifacts
uses: actions/download-artifact@v4
with:
path: artifacts
- name: Create Release
uses: softprops/action-gh-release@v1
with:
files: artifacts/**/*
generate_release_notes: true
draft: false
prerelease: ${{ contains(github.ref, '-') }}B.7 CLAUDE.md Template
# Project: [Your Project Name]
## Overview
[Brief description of what this project does]
## Architecture
### Stack
- **Backend**: FastAPI (Python 3.11+)
- **Frontend**: React 18 + TypeScript + Vite
- **Desktop**: Electron 28
- **Database**: SQLite (dev) / PostgreSQL (prod)
- **Deployment**: Docker, GitHub Actions
### Directory Structureproject/ ├── backend/ # FastAPI application │ ├── src/my_app/ # Source code │ └── tests/ # Backend tests ├── frontend/ # React application │ └── src/ # Source code ├── electron/ # Desktop wrapper ├── docker/ # Container configs └── .github/ # CI/CD workflows
## Conventions
### Backend
- All API routes under `/api/` prefix
- Pydantic models for all request/response schemas
- SQLAlchemy for database operations
- Async handlers where beneficial
### Frontend
- Functional components with hooks
- React Query for server state
- Axios for API calls
- TypeScript strict mode
### General
- Semantic versioning (v1.2.3)
- Conventional commits
- All code formatted and linted before commit
## Development Commands
### Backend
```bash
cd backend
uv run uvicorn my_app.main:app --reload
uv run pytest
uv run ruff check .
B.7.1 Frontend
cd frontend
npm run dev
npm test
npm run lintB.7.2 Docker
docker-compose -f docker/docker-compose.yml upB.8 API Endpoints
GET /api/health- Health checkGET /api/items- List itemsPOST /api/items- Create itemGET /api/items/{id}- Get itemPUT /api/items/{id}- Update itemDELETE /api/items/{id}- Delete item
B.9 Environment Variables
DEBUG- Enable debug mode (default: false)DATABASE_URL- Database connection stringSECRET_KEY- Application secret keyCORS_ORIGINS- Allowed CORS origins
B.10 Key Decisions
- Using uv for Python dependency management (fast, reliable)
- Electron over Tauri (more mature, better AI support)
- SQLite for development (simple, no setup required)
- Single Docker container for deployment (simpler ops)
- GitHub Actions for CI/CD (integrated with repo)
B.11 Common Tasks
B.11.1 Adding a new API endpoint
- Define Pydantic models in
backend/src/my_app/models/ - Create route handler in
backend/src/my_app/api/ - Add tests in
backend/tests/ - Update TypeScript types in
frontend/src/services/api.ts
B.11.2 Adding a new React component
- Create component in
frontend/src/components/ - Use React Query hooks for data fetching
- Add to relevant pages/routes
B.11.3 Creating a release
- Update version in relevant package files
- Create git tag:
git tag v1.2.3 - Push tag:
git push origin v1.2.3 - GitHub Actions builds and publishes automatically ```
This appendix provides the essential templates for implementing the multi-platform distribution architecture. For complete, up-to-date templates with all configurations, see the companion repositories listed at the beginning of this appendix.