Development Guide
This guide covers day-to-day development workflows, testing, debugging, and code quality practices for Noumaris.
Quick Start
See Onboarding Guide for initial setup. Once set up:
# Terminal 1: Docker services
docker-compose up -d
# Terminal 2: Backend
cd backend && poetry shell
python -m uvicorn src.noumaris_backend.api.main:app --reload
# Terminal 3: Frontend
cd frontend && npm run localhostDevelopment Workflow
Feature Development
Create feature branch
bashgit checkout develop git pull origin develop git checkout -b feature/your-feature-nameMake changes - See coding patterns below
Test locally - Manual testing + pytest for backend
Commit with meaningful messages
bashgit add . git commit -m "Add feature: brief description"Push and create PR
bashgit push -u origin feature/your-feature-name # Create PR on GitHub targeting 'develop' branch
Branch Naming
| Type | Format | Example |
|---|---|---|
| Feature | feature/description | feature/offline-mode |
| Bug fix | fix/description | fix/websocket-auth |
| Documentation | docs/description | docs/add-deployment-guide |
| Infrastructure | infra/description | infra/terraform-staging |
Commit Message Format
Add feature: Brief description (imperative mood)
- Detail 1
- Detail 2Examples:
- ✅
Add institution admin dashboard - ✅
Fix WebSocket connection timeout - ✅
Update deployment guide for Cloud Run - ❌
added stuff(vague, wrong tense) - ❌
WIP(commit unfinished work with description)
Backend Development
Database Migrations
Creating Migrations
After modifying backend/src/noumaris_backend/models/db_models.py:
cd backend
poetry shell
# Generate migration
alembic revision --autogenerate -m "add_column_to_users"
# Review generated file in alembic/versions/
# Edit if needed (Alembic isn't perfect)
# Apply migration
alembic upgrade headMigration Best Practices
- Review auto-generated migrations - Alembic can miss renames or complex changes
- Test migrations - Apply on fresh database to verify
- Downgrade support - Ensure
downgrade()function works - Data migrations - Use
op.execute()for data changes
# Example: Add column with default
def upgrade():
op.add_column('users', sa.Column('verified', sa.Boolean(), nullable=False, server_default='false'))
def downgrade():
op.drop_column('users', 'verified')Common Migration Commands
| Command | Description |
|---|---|
alembic upgrade head | Apply all pending migrations |
alembic downgrade -1 | Rollback one migration |
alembic history | View migration history |
alembic current | Show current revision |
alembic revision -m "msg" | Create blank migration |
Database Seeding
# Seed templates and tags
python seed_db.py
# Seed feature permissions
python scripts/seed_features.pyModify seed_db.py to add new templates or demo data.
API Development Patterns
Adding New Endpoint
from fastapi import APIRouter, Depends, HTTPException
from src.noumaris_backend.api.auth import get_current_user
from src.noumaris_backend.models.db_models import User
router = APIRouter()
@router.get("/endpoint")
@limiter.limit("10/minute") # Rate limiting
async def endpoint_name(
current_user: User = Depends(get_current_user)
):
# Use with statement for DB sessions
with db_manager.create_session() as session:
# Your logic here
result = session.query(Model).filter_by(user_id=current_user.id).first()
if not result:
raise HTTPException(status_code=404, detail="Not found")
return {"data": result}Key patterns:
- Always use
Depends(get_current_user)for authenticated endpoints - Always use
with db_manager.create_session() as session:for database access - Apply rate limiting with
@limiter.limit("X/minute") - Raise HTTPException for errors (don't return error dicts)
Database Session Management
# ✅ CORRECT - Context manager auto-commits and closes
with db_manager.create_session() as session:
user = session.query(User).filter_by(id=user_id).first()
user.name = "New Name"
# Auto-committed on exit
# ❌ WRONG - No context manager
session = db_manager.create_session()
user = session.query(User).first()
# Session never closed, potential leakTesting Backend
cd backend
poetry shell
pytest
# Run specific test
pytest tests/test_api.py::test_create_document
# Run with output
pytest -v -sCurrently backend has minimal tests. Add tests in backend/tests/ following pytest conventions.
Frontend Development
Component Structure
src/components/
├── layout/ # MainLayout, Sidebar (app structure)
├── ui/ # Reusable primitives (Button, Dialog)
├── features/ # Feature-specific (TemplateCard)
└── views/ # Page components (DoctorDashboard)Conventions:
- Pages go in
views/- one per route - Reusable UI goes in
ui/- buttons, inputs, dialogs - Feature modules go in
features/- domain-specific components
State Management
| Type | Tool | Example |
|---|---|---|
| Server state | React Query | User data, templates, documents |
| Auth state | AuthContext | JWT token, user info |
| Form state | React Hook Form | Template creation, user forms |
| Local UI state | useState | Modal open/closed, filters |
API Calls with React Query
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
// Fetch data
const { data, isLoading, error } = useQuery({
queryKey: ['templates'],
queryFn: async () => {
const response = await fetch(`${API_URL}/templates`, {
headers: { Authorization: `Bearer ${token}` }
});
return response.json();
}
});
// Mutation (create/update/delete)
const mutation = useMutation({
mutationFn: async (newTemplate) => {
const response = await fetch(`${API_URL}/templates`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${token}`
},
body: JSON.stringify(newTemplate)
});
return response.json();
},
onSuccess: () => {
queryClient.invalidateQueries(['templates']); // Refetch
}
});Environment Variables
Frontend uses different env files per mode:
| Mode | File | Usage |
|---|---|---|
| Local dev | .env.localhost | npm run localhost |
| Development | .env.development | npm run dev |
| Staging | .env.staging | npm run build:staging |
| Production | .env.production | npm run build:prod |
Never commit .env.localhost - gitignored for local API keys.
Styling Conventions
- TailwindCSS for layout and utilities
- Radix UI for accessible primitives (Dialog, DropdownMenu)
- CSS variables for theming (
--primary,--secondary) - Class Variance Authority (CVA) for variant components
// Example: Button component with variants
import { cva } from "class-variance-authority";
const buttonVariants = cva(
"rounded px-4 py-2 font-medium transition",
{
variants: {
variant: {
primary: "bg-blue-600 text-white hover:bg-blue-700",
secondary: "bg-gray-200 text-gray-900 hover:bg-gray-300",
},
size: {
sm: "text-sm px-3 py-1",
md: "text-base px-4 py-2",
}
},
defaultVariants: {
variant: "primary",
size: "md"
}
}
);Debugging
WebSocket Debugging
WebSocket transcription issues are common. Check:
Connection ID - Logged for every connection
INFO: WebSocket /transcribe connected (id: abc-123)Authentication - Token passed as query param
javascriptconst ws = new WebSocket(`ws://localhost:8000/transcribe?token=${jwt}`);Deepgram connection - Check backend logs
ERROR: Deepgram connection failed: timeoutRate limiting - Max 3 concurrent connections per user
ERROR: Rate limit exceeded for user_id: 123
Common fixes:
- Expired token → Refresh JWT
- Connection timeout → Check Deepgram API key
- Rate limit → Close other connections
- Audio not streaming → Check microphone permissions
Database Debugging
Use psql to inspect database directly:
# Connect to local database
docker exec -it noumaris-postgres psql -U medical_user -d medical_db
# Useful queries
\dt # List tables
SELECT * FROM users LIMIT 10; # View users
SELECT * FROM alembic_version; # Current migration
\q # QuitAPI Debugging
FastAPI provides interactive docs:
- Swagger UI: http://localhost:8000/docs
- ReDoc: http://localhost:8000/redoc
Test endpoints directly with authorization:
- Get JWT token from frontend (DevTools → Application → Local Storage)
- Click "Authorize" button in Swagger UI
- Paste token
- Test endpoints
Frontend Debugging
Common issues:
| Issue | Check | Fix |
|---|---|---|
| Auth stuck on "initializing" | AuthContext logs | Clear localStorage, re-login |
| API calls fail (401) | Token in headers | Refresh token |
| CORS error | Backend CORS config | Add frontend URL to allowed origins |
| Component not re-rendering | React Query cache | Invalidate query |
DevTools:
- React DevTools - Component tree, props, state
- Network tab - API calls, response codes
- Console - Error messages, logs
Code Quality
Linting
Frontend:
cd frontend
npm run lint # Check for issues
npm run lint:fix # Auto-fixBackend: Python linting is manual. Follow conventions:
- Type hints for function parameters and returns
- Docstrings for public functions
- PEP 8 style guide
Code Review Checklist
Before creating PR:
- [ ] Code follows existing patterns
- [ ] No hardcoded secrets or API keys
- [ ] Database sessions use context manager
- [ ] API endpoints have rate limiting
- [ ] Frontend components are reusable
- [ ] No console.log() or print() statements (use proper logging)
- [ ] Commit messages are descriptive
Performance Best Practices
Backend:
- Use
select_related()/joinedload()to avoid N+1 queries - Add database indexes for frequently queried fields
- Use async endpoints for I/O-heavy operations
Frontend:
- Use React.memo() for expensive components
- Lazy load routes:
const Page = lazy(() => import('./Page')) - Optimize images (WebP format, lazy loading)
- Debounce search inputs
Git Workflow
Working with Develop Branch
# Update your branch with latest develop
git checkout develop
git pull origin develop
git checkout feature/your-feature
git rebase develop
# Resolve conflicts if any
# Then force push (safe on feature branches)
git push --force-with-leaseResolving Merge Conflicts
# During rebase, if conflicts occur:
git status # See conflicted files
# Edit files to resolve conflicts
git add .
git rebase --continue
# Or abort and try merge instead
git rebase --abort
git merge developCommon Git Commands
| Command | Description |
|---|---|
git status | Check working tree status |
git diff | See unstaged changes |
git diff --staged | See staged changes |
git log --oneline -10 | View recent commits |
git stash | Temporarily save changes |
git stash pop | Restore stashed changes |
git reset HEAD~1 | Undo last commit (keep changes) |
Local Keycloak Management
Updating Keycloak Configuration
Keycloak is managed via Terraform:
cd terraform/keycloak
# Modify main.tf or terraform.tfvars.local
# Apply changes
terraform apply
# Type 'yes' when promptedCreating Test Users
Use Keycloak Admin Console (http://localhost:8081):
- Login: admin / admin
- Select "noumaris" realm
- Users → Add user
- Set username, email, email verified: ON
- Credentials tab → Set password (temporary: OFF)
- Role Mappings → Assign realm roles (superadmin, institution_admin, resident, user)
Viewing Keycloak Logs
docker-compose logs -f keycloakEnvironment-Specific Notes
Running on Different Port
Backend default: 8000. To change:
# Method 1: Environment variable
PORT=8001 python -m uvicorn src.noumaris_backend.api.main:app --reload
# Method 2: Uvicorn flag
python -m uvicorn src.noumaris_backend.api.main:app --reload --port 8001Update frontend .env.localhost to match:
VITE_API_URL=http://localhost:8001Using Different Databases
Default: Docker PostgreSQL on 5433. For external database:
# Update backend/.env
DATABASE_URL=postgresql://user:password@host:port/dbname
# Run migrations
cd backend
alembic upgrade headNext Steps
- Deployment Guide - Deploy to staging/production
- Troubleshooting Guide - Common issues and fixes
- API Documentation - Complete API reference