- 
                Notifications
    
You must be signed in to change notification settings  - Fork 6
 
Development Guide
The goal of this guide is to keep our workflow efficient, our codebase clean, and our communication clear. While every project evolves, we aim to maintain shared practices that scale with the team and the codebase.
- β Follow this guide closely. It avoids confusion and helps maintain long-term code health.
 - β Unsure how or where to do something? Ask 
@mahid797or bring it up in a discussion. - π Want to suggest changes? Great! Propose updates in a PR or team discussion thread.
 
To understand where code should live, see:
Project Structure Guide
For local setup and scripts, see the Setup Guide (Internal):
Setup Guide
For architecture/onboarding docs, see:
docs/architecture.md,docs/getting-started.md.
Consistency and clarity are critical to the long-term success of the LangRoute codebase. The following standards ensure that all contributors follow a common baseline when writing or reviewing code.
- 
Use meaningful, descriptive names
- Be descriptive: use full words (
getUserById,resetPasswordForm) instead of short forms (tmp,val,x). - Use PascalCase for components, camelCase for variables/functions.
 
 - Be descriptive: use full words (
 - 
Keep Files Small & Focused
- Aim to keep most files under 200 lines and focused on a single concern.
 - If a file grows too large or mixes concerns, split it into logical parts.
 
 - 
Avoid duplication
- Always check if a utility, hook, or component already exists before writing new.
 - Donβt fork code unless necessary β small changes should be made via props or config.
 
 - 
Use Prettier & ESLint
- 
The repo includes a
.prettierrcandeslint.config.mjsto enforce formatting and linting. - 
Husky + lint-staged runs ESLint and Prettier automatically on staged files to auto-fix code.
 - 
If your pre-commit hook fails, fix issues before retrying. But for urgent hotfixes, you can bypass with
git commit --no-verify.β οΈ Only use--no-verifyin case of emergency β and notify the reviewer when doing so. 
 - 
 - 
Add comments when necessary
- Avoid commenting on obvious operations (e.g. 
const x = y + 1 // adds one). - Use inline comments sparingly, and only for non-trivial logic, e.g., regex patterns, caching behavior, token handling.
 - Use JSDoc-style annotations for complex utility or service functions.
 
 - Avoid commenting on obvious operations (e.g. 
 - 
Structure logic before styling
- Focus on component logic and flow first. Tailwind styling and UI tweaks should come later in the PR lifecycle to avoid rework.
 
 
- 
LangRoute uses full
strictmode- LangRoute runs with full 
strictmode enabled intsconfig.json. - Avoid 
any,as unknown, or!unless properly justified (e.g. Zod parsing edge cases). 
 - LangRoute runs with full 
 - 
Infer types from validation schemas
- Use Zod schemas with 
z.infer<typeof schema>to ensure client/server type sync. - Place schemas in 
src/lib/validation/and import where needed. 
 - Use Zod schemas with 
 - 
Shared types go in the right place
- Use 
src/lib/models/for DTOs, domain types, and interfaces used across frontend + backend. - Use 
src/types/global.d.tsfor global ambient types (e.g. extendingNextAuth, env vars). 
 - Use 
 - 
Avoid redundant or overly generic types
- Donβt declare unnecessary 
type Props = {}or wrap native types (e.g.,type StringAlias = string) unless semantically helpful. 
 - Donβt declare unnecessary 
 
See the
Project Structure Guidefor where to place components or hooks.
This section outlines best practices for writing maintainable, consistent frontend code in LangRoute. All frontend logic should prioritize reusability, performance, and developer clarity.
- 
Use PascalCase for all component files
- Example: 
Button.tsx,UsageTable.tsx, notbutton.tsxorusage-table.jsx. 
 - Example: 
 - 
Style exclusively with Tailwind CSS
- 
Avoid external CSS files, styled-components, or custom classnames unless justified.
 - 
Use Tailwindβs utility classes and built-in responsive variants for layout and spacing.
 - 
For repeated or semantically meaningful style groups, you may extract Tailwind styles using
@applyinsideglobals.cssor module-level CSS. For example:/* globals.css */ .main-card { @apply flex min-h-screen items-center justify-center bg-gray-100; }
And use in JSX:
<div className="main-card"></div>
π« Avoid
@applyfor one-off use cases or when it hides important layout logic behind vague classnames like.boxor.styled-div. 
 - 
 - 
Use the
cn()helper for conditional styling- Located at 
@lib/utils. This helps cleanly compose dynamic class names. 
 - Located at 
 - 
Shadcn primitives must remain pristine
- 
Do not modify files under 
src/shadcn-ui/**. - Create wrappers/derivatives under **
src/app/(client)/components/**and compose from shadcn primitives. 
 - 
Do not modify files under 
 - 
Co-locate feature-specific components
- Components tied to a page/route should live near their usage β only promote to global 
components/if used in multiple, unrelated contexts. 
 - Components tied to a page/route should live near their usage β only promote to global 
 - 
Error Handling
- In most cases, rely on try/catch in hooks or fetch logic to handle errors gracefully.
 - Display user-friendly error messages (e.g., using a toast) whenever an API request fails.
 - If needed, Reactβs error boundaries can be used for critical failures.
 
 
- 
Never perform data fetching inside components
- Always create a hook (in 
src/app/(client)/hooks/data) for any async logic (e.g. fetching usage logs, creating API keys). 
 - Always create a hook (in 
 - 
Name hooks with the
useXpattern- Examples: 
useCreateKey(),useDeleteUser(),useUsageByPeriod(). 
 - Examples: 
 - 
Use TanStack Query for all async state
- 
Queries: use descriptive keys like
['usage', period]. - 
Mutations: always invalidate relevant queries after success.
 - 
Store query keys in
src/lib/queryKeys.tsfor reusability and consistency. - 
Always import keys from
queryKeys.ts; avoid ad-hoc array literals in components or hooks.Group keys by domain:
keys.auth.getSession,keys.usage.byPeriod(period).// Example queryKeys.ts export const queryKeys = { auth: { base: ['auth'] as const, getSession: (userId: string) => ['auth', userId, 'session'] as const, }, usage: { base: ['usage'] as const, }, profile: { base: ['profile'] as const, getDetails: (userId: string) => ['profile', userId, 'details'] as const, }, };
 
 - 
 - 
Avoid direct
axiosorfetchusage- Wrap all requests inside TanStack hooks for consistent error/loading handling.
 
 - 
Use
src/app/(client)/hooks/formsfor React Hook Form logic- Compose Zod schemas with RHFβs resolver.
 - Export typed form hook + schema from a single file to keep forms self-contained.
 
 
Refer to the
Project Structure Guidefor service placement rules.
This section covers how to write clean, testable, and extensible backend logic β especially in services and API routes.
- 
All logic must go through services
- Donβt call 
prisma,redis, or other side-effects directly inroute.tshandlers. - API routes should only parse input β call service β return response.
 
 - Donβt call 
 - 
Keep services pure and reusable
- No access to 
req,res, or cookies inside services. - Services should accept data as input and return typed results or throw errors.
 - Services should be callable from both API handlers and future jobs/workers.
 
 - No access to 
 - 
Split large services by domain
- Example: move 
createKey()tosrc/app/(server)/services/apiKey/service.tsif the file is getting large. 
 - Example: move 
 
- 
Use
try/catchin services if failure is expected- Throw Zod errors, Prisma validation errors, or custom errors as needed.
 - Never swallow errors silently β log or re-throw with context.
 
 - 
Return consistent error responses from routes
- Use the same shape for error responses across routes.
 - Avoid returning 
nullor ambiguous responses β prefer{ error, message }patterns. 
 - 
Frontend should handle errors gracefully
- If a route fails, show a toast or error state, not just console logs or crashes.
 
 
- 
Validate all request input using Zod
- Parse 
req.body,req.query, andreq.paramsbefore calling services. - Use shared schemas from 
src/lib/validationwhere possible. 
 - Parse 
 - 
Infer types using
z.infer<typeof schema>- Avoid duplicating types manually. This ensures client + server always agree.
 
 - 
Fail fast if validation fails
- Send early 400 responses instead of passing bad input deeper into the system.
 
 
See the
Project Structure Guidefor route placement and structure.
This section describes how to write clean, consistent API route handlers that integrate well with LangRoute's service-based architecture.
- 
API handlers live in
src/app/(server)/api/{resource}/route.ts - 
Each handler must follow this pattern:
- Validate input with Zod
 - Call a service function with the validated data
 - Return a typed response
 
import { z } from 'zod'; import { createKey } from '@services/apiKey/service'; import { CreateApiKeySchema } from '@lib/validation/apiKey.schemas'; export async function POST(req: Request) { const body = await req.json(); const input = CreateApiKeySchema.parse(body); const key = await createKey(input); return Response.json(key, { status: 201 }); }
 
- 
Use standard REST verbs:
- 
GETfor fetching - 
POSTfor creating - 
PUTorPATCHfor updating - 
DELETEfor removal 
 - 
 - 
Group routes by resource:
/api/keys/api/usage/api/auth/verify
 - 
Avoid nesting routes unless absolutely necessary (e.g.,
/users/{id}/keys) 
- Use Zod in every route to guard input.
 - Validate 
body,params, andsearchParamsexplicitly. - Always return early (
400 Bad Request) on invalid input. 
- 
Use consistent error shapes like:
return Response.json({ error: 'Invalid API key' }, { status: 401 });
 - 
Services should throw typed errors that routes can catch and format.
 - 
Normalize error responses to a consistent shape and use appropriate HTTP statuses (
400/401/403/404/422/500, etc.). - 
User-safe payloads only β avoid leaking stack traces or internal codes in responses.
 
See the
Project Structure Guidefor file locations.
This section focuses on how to write helpers, validators, and shared config β not where they live.
- 
Must be pure β no side effects (e.g., no logging, no fetch, no global mutation)
 - 
Must be small and focused β one clear task per function
 - 
Common examples:
- 
cn()β for composing classNames - 
capitalize()β for formatting strings - 
formatCost()β for cost display 
 - 
 
Do not create helpers unless a clear use case exists in multiple places.
- Validation schemas should be colocated with the feature only if not reused
 - For shared schemas (used in both frontend and backend), define them in 
src/lib/validationand export from a barrel index - Use 
.parse()only in routes,.safeParse()in client code if error handling is needed 
- Singleton client β do not instantiate manually
 - Import only in server-side code
 - Avoid calling inside loops or conditionals
 
- 
Singleton connection, auto-initialized
 - 
Used for:
- Pub/sub log broadcasting
 - Token bucket rate limiting (planned)
 - Budget tracking (planned)
 
 - 
Avoid putting custom Redis logic inside route handlers β use a service
 
- 
Used for:
- Route matchers (
routesConfig.ts) - Default rate limits, cost thresholds
 - Static provider metadata
 
 - Route matchers (
 - 
Prefer hardcoded values in config over repeating constants across routes/services
 
- 
Use for:
- DTOs returned from services
 - Enums, unions, discriminated types
 - Shared type contracts between API and UI
 
 - 
Must be logic-free
 - 
All types must be exported from
models/index.tsfor consistent importing// Example export interface ApiKey { id: string; name: string; createdAt: string; }
 
π‘ Pro Tip: If your helper touches DB, Redis, or input validation, it should probably live in a service β not in
lib/.
Managing sensitive information and environment-dependent behavior is essential in a self-hosted, configurable app like LangRoute. This section explains how we handle .env files and YAML config.
- 
All environment variables are defined in
env/.env.example - 
To run locally, copy it to
env/.env.localand populate the required fields - 
Never commit actual
.envfiles β only track.env.examplein version control. - 
Required env vars typically include:
NEXT_PUBLIC_URLDATABASE_URLREDIS_URLNEXTAUTH_SECRET- Provider keys (e.g. 
OPENAI_API_KEY,ANTHROPIC_API_KEY, etc.) 
 
Add new variables to
.env.exampleimmediately if you introduce them in a PR.
LangRoute will eventually support seeding data from a config.yaml file.
- 
Purpose:
- Seed models, providers, rate limits, and cost tables on first boot
 - Allow GitOps-friendly config overrides
 
 - 
Example file:
src/lib/config/sample-config-yaml-file-for-models(in repo source) 
The actual seeding and parsing logic will be introduced in a future phase β but the structure is already defined.
β οΈ This feature is not yet implemented. Do not rely on config.yaml for any runtime config.
A consistent workflow across issues, branches, and pull requests helps us collaborate effectively and reduce confusion.
- 
Every task, fix, or feature must begin with a GitHub issue
 - 
Issue format:
- β Clear title
 - β Markdown-formatted description
 - β Checklist of tasks
 - β
 Label (
Frontend,Backend,Refactor, etc.) - β Priority (see below)
 
 
Use the
Project Structure Guideto determine what kind of work you're doing before creating an issue.
| Label | Meaning | 
|---|---|
π₯ Critical
 | 
Blocks core flows, breaks app, or is deadline-bound | 
β‘ Important
 | 
Needed for next milestone or major feature | 
π± Nice-to-Have
 | 
Non-blocking quality-of-life or performance improvement | 
π οΈ Backlog
 | 
Future tasks, not part of current sprint | 
βοΈ Icebox
 | 
Unconfirmed or low-priority ideas | 
Handling Priorities
- Critical issues always come firstβaddress them before everything else.
 - If you finish your assigned tasks or are unsure what to tackle next, check for Critical or Important items.
 - For issues that initially lack clarity or need discussion, add a Needs Review or Question label to them and collaborate with the team to refine their scope.
 
- To Do: Newly created issues that havenβt been started.
 - In Progress: Actively being worked on.
 - In Review: A pull request (PR) has been created and is awaiting review.
 - Done: Once the code has been reviewed and deployed successfully
 
- Always branch off of 
dev. - 
Never branch from 
masterunless explicitly told. - 
Do not commit or push directly to 
devormaster. - Always work on a feature/fix branch and submit a pull request (PR).
 
Always rebase your branch with the latest
devbefore creating a PR to avoid merge conflicts.
| Type | Prefix | Example | 
|---|---|---|
| Feature | feature/ | 
feature/key-creation-flow | 
| Bug Fix | fix/ | 
fix/login-token-refresh | 
| Refactor | refactor/ | 
refactor/redis-abstraction | 
| Chore | chore/ | 
chore/env-doc-updates | 
- 
Format:
type(scope): messagefeat(auth): add forgot password routefix(proxy): correct stream pipe timingrefactor(hooks): rename useDocumentAnalytics
 - 
Possible types:
feat,fix,refactor,chore,docs,style 
- PR Requirements:
- β
 Target: 
dev - β
 Link the related issue using 
Closes #123 - β Include a short summary of what changed and why
 - β Attach screenshots if the UI changed
 - β Add a reviewer
 - β Do not self-merge unless explicitly allowed
 
 - β
 Target: 
 - Open a PR as soon as a slice of functionality is complete. Keep PRs small and focused.
 
Expect CodeRabbit to review; follow feedback aligned with this guide and best practices.
- β Consistency > Preference β follow established patterns even if youβd personally do it differently.
 - β Reuse before re-implementing β check the repo for existing hooks, components, services, or types.
 - β
 Keep docs updated β If you add a new helper, folder, or pattern, document it here or in 
Project Structure Guide - β Communicate openly β Use issues, PR comments, and Discord (if applicable) to clarify or align decisions.
 - β Respect boundaries β UI shouldnβt import backend logic; services shouldnβt leak into components.
 - β
 Shadcn reminder β never modify 
src/shadcn-ui/**; create wrappers insrc/app/(client)/components/**. 
Last updated: 2025-09-25