9.2 KiB
9.2 KiB
Coding Conventions
Analysis Date: 2026-03-11
Naming Patterns
Files:
- React components: PascalCase (e.g.,
AppLayout.tsx,LoginPage.tsx) - TypeScript utilities/libraries: camelCase (e.g.,
useAuth.ts,api.ts,utils.ts) - Go packages: lowercase, no underscore (e.g.,
auth,db,models,api) - Go files: descriptive lowercase (e.g.,
handlers.go,queries.go,auth.go) - Test files: None currently present (testing infrastructure not yet established)
Functions:
- React hooks:
useprefix in camelCase (e.g.,useAuth(),useBudgets()) - Go receiver methods: ExportedName style on receiver (e.g.,
(h *Handlers) Register) - Go internal functions: camelCase for unexported, PascalCase for exported (e.g.,
writeJSON(),ListCategories()) - TypeScript functions: camelCase (e.g.,
request<T>(),cn())
Variables:
- Local variables: camelCase (e.g.,
userID,displayName,carryover) - Constants: UPPER_SNAKE_CASE in Go (e.g.,
CategoryBill,userIDKey), camelCase in TypeScript (e.g.,API_BASE) - React state: camelCase for state and setter (e.g.,
[user, setUser],[loading, setLoading])
Types:
- TypeScript interfaces: PascalCase, no
Iprefix (e.g.,User,Category,Budget,BudgetItem) - TypeScript type aliases: PascalCase (e.g.,
CategoryType= 'bill' | 'variable_expense' | ...) - Go structs: PascalCase (e.g.,
User,Category,Budget) - Go type enums: PascalCase with prefixed constants (e.g.,
CategoryType,CategoryBill,CategoryIncome)
Code Style
Formatting:
- TypeScript: Vite default (2-space indentation implied by no explicit prettier config)
- Go:
gofmtstandard (1-tab indentation) - Language-specific linters enforced via
npm run lint(ESLint) andgo vet
Linting:
- Frontend: ESLint with TypeScript support (
eslint.config.js)- Extends:
@eslint/js,typescript-eslint,eslint-plugin-react-hooks,eslint-plugin-react-refresh - Targets: All
.tsand.tsxfiles - Rules enforced: React hooks rules, React refresh rules
- Extends:
- Go: Standard
go vet(no additional linter configured)
TypeScript Compiler Settings:
strict: true- Full strict type checking enablednoUnusedLocals: true- Unused variables are errorsnoUnusedParameters: true- Unused parameters are errorsnoFallthroughCasesInSwitch: true- Switch cases must explicitly break or returnnoUncheckedSideEffectImports: true- Side-effect imports must be explicit- Target: ES2022, Module: ESNext
Import Organization
TypeScript Order:
- Third-party React/UI imports (e.g.,
from 'react',from 'react-router-dom') - Component/utility imports from
@/alias - Type imports (e.g.,
type User) - CSS/global imports (e.g.,
'@/i18n')
Example from src/components/AppLayout.tsx:
import { useTranslation } from 'react-i18next'
import { Link, useLocation } from 'react-router-dom'
import { LayoutDashboard, Tags, Settings, LogOut } from 'lucide-react'
import { Sidebar, ... } from '@/components/ui/sidebar'
import { Button } from '@/components/ui/button'
import type { AuthContext } from '@/hooks/useAuth'
Go Import Order:
- Standard library imports (e.g.,
"context","encoding/json") - Third-party imports (e.g.,
"github.com/go-chi/chi/v5") - Internal imports (e.g.,
"simplefinancedash/backend/internal/...")
Example from backend/internal/api/handlers.go:
import (
"encoding/json"
"net/http"
"time"
"github.com/go-chi/chi/v5"
"github.com/google/uuid"
"simplefinancedash/backend/internal/auth"
)
Path Aliases:
- TypeScript:
@/*maps to./src/*(defined intsconfig.json) - Go: Full module paths from module name
simplefinancedash/backend
Error Handling
TypeScript Patterns:
- Silent error suppression with fallback (preferred for non-critical operations):
try { const u = await auth.me() setUser(u) } catch { setUser(null) // Fallback without logging } - Custom error class for API errors:
export class ApiError extends Error { status: number constructor(status: number, message: string) { super(message) this.name = 'ApiError' this.status = status } } - HTTP response error handling in
request<T>():if (!res.ok) { const body = await res.json().catch(() => ({})) throw new ApiError(res.status, body.error || res.statusText) }
Go Patterns:
- Wrap errors with context using
fmt.Errorf("context: %w", err)for traceability:hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) if err != nil { return "", fmt.Errorf("hashing password: %w", err) } - Return early on error in handlers:
if err != nil { writeError(w, http.StatusBadRequest, "invalid request body") return } - Silent errors in non-critical flows with zero-value fallback:
var exists bool err := pool.QueryRow(...).Scan(&exists) if err != nil { exists = false // Assume false on error }
Logging
Framework: No structured logging library. Uses log package in Go, console in TypeScript.
Patterns:
- Go:
log.Printf()for informational messages (e.g., "Server starting on :8080"),log.Fatalf()for fatal errors during startup- Located in
backend/cmd/server/main.go
- Located in
- TypeScript: No logging in production code. Errors silently caught and handled with UI fallbacks.
Comments
When to Comment:
- Go: Comments for exported types/functions (standard Go convention)
- Comment each exported function above its definition
- Used in
backend/internal/auth/auth.gofor public auth functions
- TypeScript: Minimal comments; code clarity preferred
- JSDoc comments not currently used
- Comments only for non-obvious logic or integration details
Current Usage:
- Go handler comments: "// Auth Handlers", "// Helpers" as section dividers in
api/handlers.go - TypeScript code: Inline comments absent (code is self-documenting with clear naming)
Function Design
Size:
- Go handler functions: 10-50 lines (moderate size with early returns for validation)
- Go query functions: Variable (from 5 lines for simple queries to 30+ for complex operations like
GetBudgetWithItems()) - TypeScript hooks: 20-40 lines per hook (following React patterns with useState/useEffect)
Parameters:
- Go: Receiver pattern for struct methods, context as first parameter in database functions
- Example:
func (q *Queries) CreateUser(ctx context.Context, email, passwordHash, displayName, locale string) (*models.User, error)
- Example:
- TypeScript: Props as single typed object, destructured in function signature
- Example:
export function AppLayout({ auth, children }: Props)
- Example:
- React hooks: No parameters passed to hooks; they use internal state
Return Values:
- Go: Multiple returns with error as last return (e.g.,
(*models.User, error)) - TypeScript: Single return object containing multiple pieces of state/functions
- Example:
useBudgets()returns{ list, current, loading, fetchList, selectBudget, setCurrent }
- Example:
Module Design
Exports:
- Go: Exported types/functions start with capital letter (PascalCase)
- Non-exported functions use camelCase
- Pattern: Define helpers after main exports in same file
- TypeScript: Named exports for components/utilities, default exports for page components
- Example page export:
export default function App() - Example utility export:
export class ApiError extends Error - Example hook export:
export function useAuth()
- Example page export:
Barrel Files:
- No barrel files currently used (no
index.tsre-exports insrc/lib,src/hooks, etc.) - Each utility/hook imported directly from its file
- Components from shadcn/ui imported from
@/components/ui/[component-name]
API Client Pattern
Location: frontend/src/lib/api.ts
Structure:
// 1. Helper function for all requests
async function request<T>(path: string, options?: RequestInit): Promise<T> { ... }
// 2. Custom error class
export class ApiError extends Error { ... }
// 3. Type definitions
export interface User { ... }
export interface Category { ... }
// 4. Namespace objects for endpoint groups
export const auth = {
register: (...) => request<User>(...),
login: (...) => request<User>(...),
...
}
export const categories = {
list: () => request<Category[]>(...),
create: (...) => request<Category>(...),
...
}
This pattern provides:
- Type safety with generic
request<T> - Automatic error handling and JSON parsing
- Organized endpoint grouping by resource
- Consistent authentication (cookies via
credentials: 'include')
React Component Pattern
Props Pattern:
- Define interface above component:
interface Props { ... } - Destructure in function signature:
export function Component({ prop1, prop2 }: Props) - Type children explicitly:
children: React.ReactNode
Example from AppLayout.tsx:
interface Props {
auth: AuthContext
children: React.ReactNode
}
export function AppLayout({ auth, children }: Props) { ... }
Hook Usage:
- Always call hooks at top level of component
- Use
useCallbackfor memoized functions passed to child components - Combine related state with custom hooks (e.g.,
useAuth(),useBudgets())
Convention analysis: 2026-03-11