---
name: coding-standards
description: Clear coding rules for TypeScript, JavaScript, React, Node.js, APIs, tests, and code reviews. Use when you write, review, or improve code.
origin: ECC
---

# Coding Standards

> Original work by ECC. Keep this credit when you share or change this skill.

Use these rules for TypeScript, JavaScript, React, and Node.js work.

## When to Use This Skill

Use this skill when you:

- Start a project or module
- Write or review code
- Fix hard-to-read code
- Set naming or style rules
- Set up lint, format, or type checks
- Help a new person join a project

## First Steps

Before you change code:

1. Read the project rules.
2. Check nearby code for local style.
3. Find the files and tests that may change.
4. Make the smallest safe change.
5. Run the right checks.

Project rules come first. Do not change the whole code style unless the user asks.

## Core Rules

### Make Code Easy to Read

- Use clear names.
- Keep functions small and focused.
- Use the same style across nearby files.
- Write comments for reasons, limits, or risks.
- Do not use comments to repeat the code.

```typescript
// Good
const searchQuery = 'election'
const isSignedIn = true
const totalRevenue = 1000

// Bad
const q = 'election'
const flag = true
const x = 1000
```

Short names are fine for small, common scopes.

```typescript
for (let i = 0; i < items.length; i += 1) {
  console.log(items[i])
}
```

### Keep It Simple

- Use the simplest safe fix.
- Do not add layers without a real need.
- Do not tune speed before you find a slow part.
- Pick clear code over clever code.

### Avoid Harmful Copying

Move shared code into one place when it holds the same rule.

Do not join code only because it looks alike. Two parts may need to change for different reasons. A small repeat can be safer than the wrong shared helper.

### Build Only What Is Needed

- Do not add features for a guessed future need.
- Do not add settings with no current use.
- Add more parts only when the task needs them.

## TypeScript and JavaScript

### Names

Use:

- `camelCase` for values and functions
- `PascalCase` for classes, types, and React parts
- `UPPER_SNAKE_CASE` for true fixed values
- Names that show what a Boolean means, such as `isOpen` or `hasAccess`

Name functions with an action.

```typescript
async function fetchMarket(marketId: string) {}
function calculateTotal(prices: number[]) {}
function isValidEmail(email: string): boolean {
  return email.includes('@')
}
```

Avoid vague names.

```typescript
async function market(id: string) {}
function process(data: unknown) {}
```

### Types

- Use exact types.
- Use `unknown` for data you have not checked.
- Avoid `any`.
- Check data at system edges, such as API input and file input.
- Use one name style for fields inside the app.

```typescript
interface Market {
  id: string
  name: string
  status: 'active' | 'resolved' | 'closed'
  createdAt: Date
}

function getMarket(id: string): Promise<Market> {
  throw new Error('Not implemented')
}
```

API JSON cannot hold a `Date` value. Use a date string at the API edge, then parse it if the app needs a `Date`.

```typescript
interface MarketJson {
  id: string
  createdAt: string
}
```

Do not hide a type error with a cast unless you have checked the value first.

### Data Changes

Prefer new values when data is shared or used as state.

```typescript
const updatedUser = {
  ...user,
  name: 'New Name'
}

const updatedItems = [...items, newItem]
```

Do not change React state or shared input in place.

```typescript
user.name = 'New Name'
items.push(newItem)
```

Local change is fine when the value is owned by one small scope and cannot leak out.

```typescript
const results: string[] = []

for (const item of items) {
  results.push(item.name)
}
```

### Functions

- Give each function one clear job.
- Return early for bad or empty cases.
- Keep side effects easy to find.
- Pass needed data as input.
- Do not read hidden global state when an input will work.

```typescript
function getDisplayName(user?: User): string {
  if (!user) return 'Guest'
  if (!user.name.trim()) return 'Guest'

  return user.name
}
```

Handle empty lists, missing values, and bad input when they can occur.

### Errors

- Check failed work.
- Add useful context.
- Keep the first error as the cause when possible.
- Do not show secrets or private data.
- Do not catch an error only to ignore it.
- Return safe messages to users.
- Log useful details on the server.

```typescript
async function fetchData(url: string): Promise<unknown> {
  try {
    const response = await fetch(url)

    if (!response.ok) {
      throw new Error(`Request failed with status ${response.status}`)
    }

    return await response.json()
  } catch (error) {
    throw new Error('Could not fetch data', { cause: error })
  }
}
```

If the caller can handle the first error well, let it pass through. Do not wrap every error.

### Async Work

Run work at the same time only when each task is independent.

```typescript
const [users, markets, stats] = await Promise.all([
  fetchUsers(),
  fetchMarkets(),
  fetchStats()
])
```

Run work in order when one task needs the last result.

```typescript
const user = await createUser(input)
await sendWelcomeEmail(user.email)
```

Also:

- Use `Promise.allSettled` when one failed task must not stop the rest.
- Add a time limit or cancel signal for long network work.
- Limit group size when many tasks could flood a service.
- Do not leave a promise without `await`, `return`, or clear error handling.

## React

### Components

- Use typed props.
- Keep parts small.
- Give each part one main job.
- Use plain HTML parts when they fit.
- Support keyboard use and clear labels.

```tsx
interface ButtonProps {
  children: React.ReactNode
  onClick: () => void
  disabled?: boolean
  variant?: 'primary' | 'secondary'
}

export function Button({
  children,
  onClick,
  disabled = false,
  variant = 'primary'
}: ButtonProps) {
  return (
    <button
      type="button"
      onClick={onClick}
      disabled={disabled}
      className={`btn btn-${variant}`}
    >
      {children}
    </button>
  )
}
```

### State

Use a function when the new state uses the old state.

```tsx
const [count, setCount] = useState(0)

setCount(previousCount => previousCount + 1)
```

Do not store a value in state if it can be found from current props or state.

```tsx
const fullName = `${firstName} ${lastName}`
```

### Hooks

- Call hooks only at the top level.
- Do not call hooks inside a loop, test, or nested function.
- List all used values in effect input lists.
- Clean up timers, events, and requests.

```tsx
export function useDebounce<T>(value: T, delayMs: number): T {
  const [debouncedValue, setDebouncedValue] = useState(value)

  useEffect(() => {
    const timerId = window.setTimeout(() => {
      setDebouncedValue(value)
    }, delayMs)

    return () => window.clearTimeout(timerId)
  }, [value, delayMs])

  return debouncedValue
}
```

### Render States

Show loading, error, empty, and ready states when each can happen.

```tsx
if (isLoading) return <Spinner />
if (error) return <ErrorMessage error={error} />
if (!data?.length) return <EmptyState />

return <DataList data={data} />
```

For lists:

- Use stable keys from the data.
- Do not use the list index as a key when items can move.
- Do not change props during render.

## API Rules

### Routes

Use nouns for routes and HTTP methods for actions.

```text
GET    /api/markets
GET    /api/markets/:id
POST   /api/markets
PUT    /api/markets/:id
PATCH  /api/markets/:id
DELETE /api/markets/:id

GET /api/markets?status=active&limit=10&offset=0
```

Use:

- `GET` to read
- `POST` to create
- `PUT` to replace
- `PATCH` to change part of a value
- `DELETE` to remove

Return the right status code. Common codes are `200`, `201`, `204`, `400`, `401`, `403`, `404`, `409`, and `500`.

### Response Shape

Use one response shape across the API.

```typescript
interface ApiResponse<T> {
  success: boolean
  data?: T
  error?: {
    code: string
    message: string
    details?: unknown
  }
  meta?: {
    total: number
    page: number
    limit: number
  }
}
```

Do not send stack traces, secret values, or raw database errors to the client.

### Input Checks

Check route values, query values, headers, and body data. Treat all outside data as unsafe.

Also handle bad JSON before schema checks.

```typescript
import { z } from 'zod'

const CreateMarketSchema = z.object({
  name: z.string().trim().min(1).max(200),
  description: z.string().trim().min(1).max(2000),
  endDate: z.string().datetime(),
  categories: z.array(z.string().trim().min(1)).min(1)
})

export async function POST(request: Request) {
  let body: unknown

  try {
    body = await request.json()
  } catch {
    return Response.json(
      {
        success: false,
        error: {
          code: 'BAD_JSON',
          message: 'The request body is not valid JSON'
        }
      },
      { status: 400 }
    )
  }

  const result = CreateMarketSchema.safeParse(body)

  if (!result.success) {
    return Response.json(
      {
        success: false,
        error: {
          code: 'BAD_INPUT',
          message: 'The request data is not valid',
          details: result.error.flatten()
        }
      },
      { status: 400 }
    )
  }

  const market = await createMarket(result.data)

  return Response.json(
    { success: true, data: market },
    { status: 201 }
  )
}
```

Check access on the server. Hiding a button in React is not an access check.

## Files

Follow the project layout first. A common layout is:

```text
src/
├── app/
│   ├── api/
│   └── markets/
├── components/
│   ├── ui/
│   ├── forms/
│   └── layouts/
├── hooks/
├── lib/
│   ├── api/
│   ├── utils/
│   └── constants/
├── types/
└── styles/
```

Common file names:

```text
components/Button.tsx
hooks/useAuth.ts
lib/formatDate.ts
types/market.ts
```

Keep tests near the code or in the project test folder. Follow the style already in use.

## Tests

Add or update tests for changed behavior.

Test:

- The normal case
- Empty or missing input
- Bad input
- Error paths
- Limit values
- Access rules when they apply

Do not test private steps. Test what callers can see.

A bug fix should have a test that fails before the fix and passes after it.

## Tools and Checks

Use the tools the project already has.

Before you finish, run the checks that fit the change:

```text
format
lint
type check
tests
build
```

Do not claim a check passed if you did not run it. If a check cannot run, say why.

Do not fix unrelated warnings unless the user asks.

## Concrete Example

Task: Add a search helper for markets.

```typescript
interface Market {
  id: string
  name: string
}

export function findMarkets(
  markets: readonly Market[],
  query: string
): Market[] {
  const cleanQuery = query.trim().toLowerCase()

  if (!cleanQuery) return []

  return markets.filter(market =>
    market.name.toLowerCase().includes(cleanQuery)
  )
}
```

```typescript
import { describe, expect, it } from 'vitest'
import { findMarkets } from './findMarkets'

const markets = [
  { id: '1', name: 'City Budget' },
  { id: '2', name: 'Rain Tomorrow' }
]

describe('findMarkets', () => {
  it('finds a market without case limits', () => {
    expect(findMarkets(markets, 'CITY')).toEqual([markets[0]])
  })

  it('trims the query', () => {
    expect(findMarkets(markets, ' rain ')).toEqual([markets[1]])
  })

  it('returns an empty list for an empty query', () => {
    expect(findMarkets(markets, '   ')).toEqual([])
  })

  it('does not change the input list', () => {
    findMarkets(markets, 'city')
    expect(markets).toHaveLength(2)
  })
})
```

This example uses clear names, exact types, no shared data change, and tests for edge cases.

## Final Review

Before you finish, check that:

- Names are clear.
- Types are exact.
- Outside data is checked.
- Errors are handled once and at the right level.
- Shared data is not changed in place.
- Async work is safe.
- React hooks follow hook rules.
- Loading, error, and empty states are covered.
- Tests cover the changed behavior.
- No secret or private data is shown.
- The code fits the local project style.