Files
zhealth/PROJECT_MGMT/coding_standards.md

5.5 KiB

Coding Standards: zhealth Monorepo

Core Principles

  1. Modularity First — Every component should be independent and reusable.
  2. Self-Documenting Structure — The folder tree and file names should explain purpose without needing comments.
  3. Small Files — Max 200 lines per file. A function belongs in a file only if its purpose is obvious from the filename. When in doubt, create a separate file.

File & Folder Naming

Type Convention Example
Folders snake_case auth_handlers/, biomarker_utils/
Rust files snake_case.rs user_service.rs, jwt_middleware.rs
TypeScript/React files PascalCase.tsx for components Dashboard.tsx, BiomarkerCard.tsx
TypeScript utilities camelCase.ts apiClient.ts, formatDate.ts
Test files *.test.ts / *_test.rs auth.test.ts, user_service_test.rs
Config files lowercase with dots vite.config.ts, .env.local

Folder Structure (Monorepo)

zhealth/
├── PROJECT_MGMT/           # Project planning & docs
├── backend/                # Rust API
│   ├── src/
│   │   ├── handlers/       # Route handlers (one file per resource)
│   │   ├── models/         # SeaORM entities
│   │   ├── services/       # Business logic
│   │   ├── middleware/     # Auth, logging, etc.
│   │   ├── utils/          # Shared helpers
│   │   └── main.rs
│   └── Cargo.toml
├── frontend/               # React + Vite
│   ├── src/
│   │   ├── components/     # Reusable UI components
│   │   ├── pages/          # Route-level components
│   │   ├── hooks/          # Custom React hooks
│   │   ├── services/       # API calls
│   │   ├── utils/          # Helper functions
│   │   └── App.tsx
│   └── package.json
└── shared/                 # Shared types/constants (if needed)

Makefile as Command Runner

All common tasks are executed through the root Makefile. Do not use raw cargo, npm, or tool-specific commands directly — always use make.

Command Purpose
make lint Run linters (Clippy + ESLint)
make typecheck Type checking (Rust + TypeScript)
make dev Start dev servers (backend + frontend)
make build Build for development
make release Build optimized production bundle
make serve Serve production build locally
make test Run all tests
make clean Clean build artifacts

Why Makefile?

  • Single source of truth for all commands
  • Works across all platforms (unlike npm scripts for Rust)
  • Self-documenting — run make help to see available targets
  • Composable — targets can depend on each other

Configuration (config.yaml)

All backend configuration is centralized in a config.yaml file at the backend root. Never hardcode paths, ports, or environment-specific values.

Required Config Keys

# backend/config.yaml

server:
  host: "0.0.0.0"
  port: 3000

paths:
  database: "./data/zhealth.db"
  logs: "./logs"

auth:
  jwt_secret: "${JWT_SECRET}"  # Loaded from env
  token_expiry_hours: 24

ai:
  provider: "gemini"  # gemini | openai | anthropic
  api_key: "${AI_API_KEY}"

Rules

  • Secrets via env vars — Use ${VAR_NAME} syntax for sensitive values
  • Paths are relative to the backend folder (or absolute if needed)
  • No hardcoding — If it might change across environments, put it in config

File Size Rules

Rule Action
File > 200 lines Split into smaller modules
Function > 50 lines Extract to helper or break into sub-functions
Single responsibility One file = one purpose

Example: Splitting a Large Handler

Bad: handlers/biomarkers.rs with 400 lines

Good:

handlers/
├── biomarkers/
│   ├── mod.rs           # Re-exports
│   ├── create.rs        # POST /biomarkers
│   ├── read.rs          # GET /biomarkers
│   ├── update.rs        # PUT /biomarkers/:id
│   └── delete.rs        # DELETE /biomarkers/:id

Import & Module Organization

Rust

// 1. Standard library
use std::collections::HashMap;

// 2. External crates
use axum::{Router, Json};
use serde::{Deserialize, Serialize};

// 3. Internal modules
use crate::models::User;
use crate::services::auth_service;

TypeScript

// 1. React/Framework
import { useState, useEffect } from 'react';

// 2. External packages
import axios from 'axios';

// 3. Internal components/utils
import { BiomarkerCard } from '@/components/BiomarkerCard';
import { formatDate } from '@/utils/formatDate';

Commit & Branch Naming

Type Format Example
Branch type/short-description feat/biomarker-crud, fix/auth-token-expiry
Commit type: short description feat: add biomarker create endpoint

Types: feat, fix, docs, refactor, test, chore


Code Comments

  • Avoid obvious comments — Code should be self-explanatory.
  • Document "why", not "what" — Explain non-obvious decisions.
  • Use doc comments for public APIs:
    • Rust: /// for functions, //! for modules
    • TypeScript: JSDoc /** */ for exported functions

Enforcement

  • Pre-commit hooks for linting (Clippy for Rust, ESLint for TS)
  • CI pipeline checks for file size violations
  • PR reviews must verify modularity standards