Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/egeuysall/ryva-archive/llms.txt

Use this file to discover all available pages before exploring further.

Ryva enforces strict code style guidelines to ensure consistency and maintainability across the codebase.

Overview

  • Automated Enforcement - Pre-commit hooks and CI/CD validate code style
  • Language-Specific Tools - golangci-lint for Go, ESLint & Prettier for TypeScript
  • Zero Tolerance - PRs with style violations cannot be merged
All code must pass linting before being committed. Pre-commit hooks will reject improperly formatted code.

Go (Backend API)

Style Guide

We follow the official Effective Go guidelines.

Formatting

1

Format Before Committing

cd apps/api
make format
2

Run Linter

make lint

Go Linting Configuration

Ryva uses golangci-lint with extensive checks enabled:
- errcheck       # Check for unchecked errors
- govet          # Go vet analysis
- ineffassign    # Detect ineffectual assignments
- staticcheck    # Staticcheck analysis
- unused         # Check for unused code
- errorlint      # Error wrapping checks
- errname        # Error naming conventions
- bodyclose      # HTTP response body closing
- noctx          # HTTP requests without context
- goconst        # Repeated strings that could be constants
- gocyclo        # Cyclomatic complexity
- whitespace     # Whitespace issues
- gosec          # Security issues
- gocritic       # Go code critic
- nilerr         # Nil error returns
- nilnil         # Returning nil, nil
- dupl           # Code duplication
- gomodguard     # Module usage restrictions
- revive         # Fast, configurable linter
- misspell       # Spelling mistakes
- unconvert      # Unnecessary type conversions
- unparam        # Unused function parameters
- nakedret       # Naked returns
- prealloc       # Slice preallocation

Go Best Practices

Error Handling

Never ignore errors. Always return and handle them properly.
func getUser(id string) (*User, error) {
    user, err := db.Query("SELECT * FROM users WHERE id = ?", id)
    if err != nil {
        return nil, fmt.Errorf("failed to get user: %w", err)
    }
    return user, nil
}

Context Usage

Always use context.Context for database operations, HTTP requests, and cancellable operations.
// ✅ Correct
func (s *UserService) GetUser(ctx context.Context, id string) (*User, error) {
    var user User
    err := s.db.QueryRowContext(ctx, "SELECT * FROM users WHERE id = ?", id).Scan(&user)
    if err != nil {
        return nil, fmt.Errorf("failed to get user: %w", err)
    }
    return &user, nil
}

Handler Structure

Keep handlers thin. Move business logic to services.
// ✅ Correct - Thin handler
func (h *UserHandler) CreateUser(w http.ResponseWriter, r *http.Request) {
    var req CreateUserRequest
    if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
        http.Error(w, "invalid request", http.StatusBadRequest)
        return
    }

    // Validate input
    if err := req.Validate(); err != nil {
        http.Error(w, err.Error(), http.StatusBadRequest)
        return
    }

    // Delegate to service
    user, err := h.userService.CreateUser(r.Context(), &req)
    if err != nil {
        http.Error(w, "failed to create user", http.StatusInternalServerError)
        return
    }

    respondJSON(w, http.StatusCreated, user)
}

Naming Conventions

// Use camelCase for local variables
userID := "123"
userEmail := "user@example.com"

// Use PascalCase for exported names
type UserService struct {}
func (s *UserService) CreateUser() {}

Comments

// ✅ Package comments should describe the package purpose
// Package auth provides authentication and authorization functionality.
package auth

// ✅ Exported functions should have comments
// CreateUser creates a new user in the database.
// It returns an error if the email is already taken.
func CreateUser(ctx context.Context, user *User) error {
    // Implementation
}

// ✅ Complex logic should be explained
func processPayment(amount float64) error {
    // Calculate fee based on payment tier
    // Tier 1 (0-100): 2%
    // Tier 2 (101-1000): 1.5%
    // Tier 3 (1001+): 1%
    fee := calculateFee(amount)
    // ...
}

Code Organization

// ✅ Correct - Clean architecture
// handler → service → repository

// handlers/user_handler.go
type UserHandler struct {
    userService *service.UserService
}

// services/user_service.go
type UserService struct {
    userRepo repository.UserRepository
}

// repository/user_repository.go
type UserRepository interface {
    Create(ctx context.Context, user *User) error
    GetByID(ctx context.Context, id string) (*User, error)
}

Linter Settings

Key settings from .golangci.yml:
  • Cyclomatic complexity: Max 15
  • Duplicate threshold: 100 lines
  • Naked returns: Max 30 lines
  • String constants: Min 3 characters, 3 occurrences

TypeScript/React (Frontend)

Style Guide

We follow TypeScript best practices and React conventions.

Formatting

1

Format Before Committing

cd apps/web
pnpm format
2

Run Linter

pnpm lint
3

Type Check

pnpm type-check

Prettier Configuration

{
  "semi": true,
  "trailingComma": "es5",
  "singleQuote": true,
  "printWidth": 100,
  "tabWidth": 2,
  "useTabs": false,
  "arrowParens": "avoid"
}

TypeScript Best Practices

Type Safety

Never use any type. Use proper types or unknown if type is truly unknown.
interface User {
  id: string;
  name: string;
  email: string;
  role: 'admin' | 'user' | 'guest';
}

function getUser(id: string): Promise<User> {
  return fetch(`/api/users/${id}`).then(res => res.json());
}

Component Props

Always type component props with interfaces.
// ✅ Correct
interface UserProfileProps {
  user: User;
  onUpdate?: (user: User) => void;
  isEditable?: boolean;
}

export function UserProfile({ user, onUpdate, isEditable = false }: UserProfileProps) {
  // Implementation
}

Server vs Client Components

Server Components by default. Only use Client Components when needed.
// No 'use client' directive
export function UserList() {
  // Can directly fetch data
  const users = await getUsers();
  
  return (
    <div>
      {users.map(user => (
        <div key={user.id}>{user.name}</div>
      ))}
    </div>
  );
}

Hooks

// ✅ Custom hooks should start with 'use'
import { useState, useEffect } from 'react';

interface UseUserDataResult {
  user: User | null;
  isLoading: boolean;
  error: Error | null;
}

export function useUserData(userId: string): UseUserDataResult {
  const [user, setUser] = useState<User | null>(null);
  const [isLoading, setIsLoading] = useState(true);
  const [error, setError] = useState<Error | null>(null);

  useEffect(() => {
    fetchUser(userId)
      .then(setUser)
      .catch(setError)
      .finally(() => setIsLoading(false));
  }, [userId]);

  return { user, isLoading, error };
}

State Management

import { useQuery } from '@tanstack/react-query';

export function useUser(userId: string) {
  return useQuery({
    queryKey: ['user', userId],
    queryFn: () => fetchUser(userId),
  });
}

Naming Conventions

// PascalCase for components
export function UserProfile() {}
export function NavigationMenu() {}

File Organization

components/
├── UserProfile/
│   ├── UserProfile.tsx       # Main component
│   ├── UserProfile.test.tsx  # Tests
│   ├── UserProfile.types.ts  # Type definitions
│   └── index.ts              # Exports

Import Order

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

// 2. External libraries
import { useQuery } from '@tanstack/react-query';
import { z } from 'zod';

// 3. Internal modules
import { fetchUser } from '@/lib/api';
import { useAuth } from '@/hooks/useAuth';

// 4. Components
import { Button } from '@/components/ui/Button';
import { UserAvatar } from '@/components/UserAvatar';

// 5. Types
import type { User } from '@/types/user';

// 6. Styles (if applicable)
import styles from './UserProfile.module.css';

General Principles

Code Quality Standards

  1. Readability First - Code is read more than written
  2. DRY Principle - Don’t Repeat Yourself
  3. KISS Principle - Keep It Simple, Stupid
  4. YAGNI Principle - You Aren’t Gonna Need It
  5. Single Responsibility - One function, one purpose
  6. Explicit over Implicit - Clear code over clever code
  7. Fail Fast - Validate early and return errors immediately
  8. Immutability - Prefer immutable data structures

Pre-commit Hooks

Ryva uses pre-commit hooks to enforce code quality:
1

Install Pre-commit

make pre-commit-install
2

Automatic Checks

On every commit, the following run automatically:
  • Code formatting validation
  • Linting
  • Commit message validation (Conventional Commits)
  • Type checking
3

Fix Issues

If hooks fail, fix the issues and commit again:
# Fix formatting
make format

# Fix linting issues
make lint

# Try commit again
git commit -m "feat(api): add new feature"
You cannot bypass pre-commit hooks. Fix all issues before committing.

CI/CD Checks

All PRs must pass:
  • ✅ ESLint (Web)
  • ✅ golangci-lint (API)
  • ✅ Prettier formatting (Web)
  • ✅ gofmt formatting (API)
  • ✅ TypeScript type checking (Web)
  • ✅ Go vet analysis (API)
  • ✅ Security scans (gosec, npm audit)
Run all checks locally before pushing:
make lint
make test
make format

Build docs developers (and LLMs) love