Coding Conventions
Development standards for the CROW codebase. These rules ensure consistency, readability, and maintainability across all services.
Philosophy
Optimize for clarity over cleverness. Code should be self-explanatory, flat, and predictable. If you need a comment to explain what code does, the code needs to be rewritten.
Rules Summary
- No comments in production code
- Max 1 level of indentation
- FP first, avoid mutation
- 20-40 line functions
- Long descriptive names
- TypeScript strict mode, zero errors
as neverfor OpenAPI route type narrowing
TypeScript Standards
1. No Comments
Code must be self-documenting. Comments are a code smell.
Instead of commenting, use descriptive variable and function names, extract complex logic into well-named functions, use guard clauses and early returns.
Exception: Public API documentation for exported interfaces consumed by external parties.
// BAD
function calc(x: number, y: number): number {
// Calculate the discount based on order total
const d = x > 100 ? 0.2 : 0.1
return y - (y * d)
}
// GOOD
function calculateDiscountedPrice(orderTotal: number, itemPrice: number): number {
const discountRate = orderTotal > 100 ? 0.2 : 0.1
const discountAmount = itemPrice * discountRate
return itemPrice - discountAmount
}
2. Maximum 1 Level of Indentation
Functions must not exceed 1 level of indentation (excluding the function body itself). Deep nesting multiplies cognitive load.
Techniques: guard clauses, early returns, array methods (map, filter, flatMap, reduce), lookup tables.
// BAD
function processUser(user: User | null): string {
if (user) {
if (user.isActive) {
if (user.hasPermission('admin')) {
return 'Admin user processed'
}
}
}
return 'No user found'
}
// GOOD
function processUser(user: User | null): string {
if (!user) return 'No user found'
if (!user.isActive) return 'Inactive user'
if (user.hasPermission('admin')) return 'Admin user processed'
return 'Regular user processed'
}
3. Functional Programming First
Default to FP patterns. OOP is a last resort.
Prefer pure functions, immutable data structures, function composition, data transformations over mutation.
Use OOP only for Durable Objects (required by Cloudflare), managing complex lifecycle/state, or implementing required library interfaces.
4. Function Size: 20-40 Lines Maximum
Small functions are easier to understand, test, and maintain. If a function is longer, it has multiple responsibilities. Extract helper functions and apply single responsibility.
5. Long Descriptive Names
Optimize for reading, not typing.
- Use full words, no abbreviations
- Booleans: prefix with
is,has,should,can - Functions: verb prefixes (
get,fetch,create,update,delete,calculate,transform,validate) - Avoid vague names:
data,info,manager,util,handler
6. TypeScript Strict Mode
All services use strict TypeScript compilation with zero errors. Enable strict mode in tsconfig.json.
7. as never for OpenAPI Route Type Narrowing
When using @hono/zod-openapi, early return responses (error paths) may not match the route's success type. Use as never to satisfy the type checker:
app.openapi(GetOrganizationRoute, async c => {
const callerOrgId = c.req.header('X-Organization-Id')
if (!callerOrgId || callerOrgId !== id) {
return c.json({ error: 'Forbidden' }, 403) as never
}
// ... success path
})
This is the standard pattern across all CROW services for BOLA checks and validation errors in OpenAPI routes.
Architecture Patterns
Layer-Based Organization
src/
config/ # Configuration and validation
db/ # Schema and migrations
middleware/ # Hono middleware (auth, jwt, cors)
routes/ # Route definitions (OpenAPI schemas)
services/ # Business logic
utils/ # Shared utilities (formatters, error handlers)
types.ts # Type definitions and Environment interface
index.ts # App entrypoint and route registration
Error Handling
- ZodError details are never leaked to clients (sanitized to generic "Invalid request parameters")
- Errors are logged once at the boundary
handleErrorResponseutility provides consistent error formatting
Validation
- Request validation via Zod schemas defined in OpenAPI routes
- Email validation: RFC 5321 (max 254 chars), HTML tag rejection in names
- URL validation: SSRF protection blocks private/loopback/link-local ranges and internal CROW hostnames
Commit Messages
Conventional Commits via commitlint:
<type>: <subject>
Types: feat, fix, docs, style, refactor, test, chore
Enforcement
- Code reviews (all PRs require approval)
- ESLint (
@antfu/eslint-config) - Prettier for formatting
- Pre-commit hooks via Husky + lint-staged
- CI pipeline checks