Skip to main content

Overview

This guide establishes coding standards for the Go React Scaffold project. Following these guidelines ensures consistency, maintainability, and code quality across the codebase. Refer to AGENTS.md in the repository for the complete coding guidelines.

TypeScript / React (Frontend)

Imports

Organization:
// 1. React and core libraries
import React, { useState, useEffect } from 'react';

// 2. Third-party libraries
import axios from 'axios';

// 3. Local imports
import { UserProfile } from './components/UserProfile';
import { api } from './utils/api';
Group imports logically: React/core libraries first, then external packages, then local imports.
Syntax:
  • Use ES modules (import syntax)
  • No default path aliases configured (use relative paths)

Formatting

Style Rules:
// 2-space indentation
function LoginForm() {
  const [email, setEmail] = useState('');
  
  return (
    <form>
      <input type='email' value={email} />
    </form>
  );
}

// Single quotes for strings
const message = 'Hello, world!';

// Semicolons required
const count = 42;
Key rules:
  • 2-space indentation (no tabs)
  • Single quotes for strings
  • Semicolons required
  • No Prettier config (maintain consistency manually)

TypeScript Types

Strict Mode Enabled: The project uses strict TypeScript configuration:
{
  "compilerOptions": {
    "strict": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "noFallthroughCasesInSwitch": true
  }
}
Type Best Practices:
// Explicit return types for non-obvious functions
function calculateTotal(items: Item[]): number {
  return items.reduce((sum, item) => sum + item.price, 0);
}

// Interface for objects
interface User {
  id: string;
  email: string;
  createdAt: Date;
}

// Type for unions
type Status = 'pending' | 'active' | 'inactive';
Use explicit return types for functions when the return type is not immediately obvious from the implementation.

Naming Conventions

ElementConventionExample
ComponentsPascalCaseUserProfile.tsx
FunctionscamelCasegetUserData()
VariablescamelCaseuserEmail
ConstantsUPPER_SNAKE_CASEAPI_BASE_URL
Files (Components)PascalCaseLoginForm.tsx
Files (Utilities)camelCaseapiClient.ts

React Patterns

Functional Components:
// Use functional components with hooks
function UserProfile({ userId }: { userId: string }) {
  const [user, setUser] = useState<User | null>(null);
  
  useEffect(() => {
    fetchUser(userId).then(setUser);
  }, [userId]);
  
  return <div>{user?.name}</div>;
}
Best practices:
  • Use functional components with hooks (no class components)
  • Destructure props in function parameters
  • Prefer const over let
  • Use useState, useEffect, and other React hooks

TailwindCSS Usage

Utility-First Approach:
// Use Tailwind classes directly
function Button({ children }: { children: React.ReactNode }) {
  return (
    <button className='bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded'>
      {children}
    </button>
  );
}
Guidelines:
  • Utility-first approach with Tailwind classes
  • Use App.css for component-specific styles when needed
  • Use @apply sparingly (prefer utility classes)

Error Handling

Safe Property Access:
// Always check for null/undefined
if (user) {
  console.log(user.email);
}

// Use optional chaining
const email = user?.profile?.email;

// Handle fetch errors
try {
  const response = await fetch('/api/users');
  if (!response.ok) {
    throw new Error('Failed to fetch users');
  }
  const data = await response.json();
} catch (error) {
  console.error('Error fetching users:', error);
}
Always check for null/undefined before accessing properties to prevent runtime errors.

Go (Backend)

Imports

Import Organization:
package main

import (
    // 1. Standard library
    "context"
    "fmt"
    "log"
    "net/http"
    
    // 2. External packages
    "github.com/labstack/echo/v4"
    "go.mongodb.org/mongo-driver/mongo"
    
    // 3. Local packages
    "backend/auth"
    "backend/configs"
)
Guidelines:
  • Group imports: standard library, external packages, local packages
  • Use blank imports only when necessary (e.g., _ "github.com/joho/godotenv/autoload")
  • Local import path: backend/<package>

Formatting

Standard Go Formatting:
# Format code before committing
gofmt -w .

# Organize imports
goimports -w .
Run gofmt -w . before every commit to ensure consistent formatting.
Rules:
  • Standard Go formatting (gofmt)
  • Use goimports to organize imports
  • Tabs for indentation (Go standard)

Naming Conventions

ElementConventionExample
Packageslowercase, single wordauth, configs
Exported functions/typesPascalCaseConnectDB, User
Unexported functionscamelCaseprocessUserLogin
AcronymsAll capsID, URI, API
Fileslowercase, underscores okauth.go, user_model.go
Examples:
package auth

// Exported function - PascalCase
func GenerateToken(userID string) (string, error) {
    // ...
}

// Unexported function - camelCase
func validatePassword(password string) error {
    // ...
}

// Exported type - PascalCase
type User struct {
    ID    string `json:"id" bson:"_id"`
    Email string `json:"email" bson:"email"`
}

// Acronyms - all caps
const APIURL = "https://api.example.com"

Types and Structs

Struct Tags:
type User struct {
    ID        primitive.ObjectID `json:"id" bson:"_id,omitempty"`
    Email     string             `json:"email" bson:"email"`
    Password  string             `json:"-" bson:"password"` // Never expose in JSON
    CreatedAt time.Time          `json:"created_at" bson:"created_at"`
}
Guidelines:
  • Use structs with tags for JSON/BSON/validation
  • Tag format: `json:"field" bson:"field"`
  • Prefer explicit types over interface{} or any

Error Handling

Always Check Errors:
// Check every error
user, err := getUserByEmail(email)
if err != nil {
    return err
}

// Wrap errors with context
if err := db.Insert(user); err != nil {
    return fmt.Errorf("failed to insert user: %w", err)
}
Error Handling Rules:
  • Always check errors: if err != nil { return err }
  • Wrap errors with context: fmt.Errorf("context: %w", err)
  • Use log.Fatalf() only in main or init, not in libraries
  • Return errors to caller - don’t log and swallow
Never ignore errors. Always check and handle them appropriately.

Project Structure

Package Organization:
backend/
├── main.go              # Application entry point
├── auth/                # Auth package
│   ├── auth.go         # JWT token handling
│   ├── controller.go   # Business logic
│   ├── routes.go       # HTTP handlers
│   └── configs.go      # Configuration
├── users/
│   └── model.go        # User data model
└── configs/
    ├── db.go           # Database connection
    └── env.go          # Environment variables
Structure Rules:
  • One package per directory
  • Separate concerns:
    • Handlers in routes.go
    • Business logic in controller.go
    • Models in model.go
  • Main package in main.go at root

Database (MongoDB)

Context Usage:
// Use context with timeout (preferred)
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()

result, err := collection.FindOne(ctx, filter).Decode(&user)
if err != nil {
    return fmt.Errorf("failed to find user: %w", err)
}

// context.TODO() is used in the current codebase
// Update to context.WithTimeout() for production
MongoDB Best Practices:
  • Use context with timeouts (prefer context.WithTimeout() over context.TODO())
  • Handle connection errors at startup
  • Use bson.M{} for queries
  • Close connections properly with deferred disconnect
Query Safety:
// Safe: Use typed queries
filter := bson.M{"email": email}
result := collection.FindOne(ctx, filter)

// Unsafe: Don't interpolate user input directly
// filter := bson.M{"$where": userInput} // ❌ NoSQL injection risk

HTTP (Echo Framework)

Handler Pattern:
// Handler signature
func LoginHandler(c echo.Context) error {
    // 1. Validate input
    var loginReq LoginRequest
    if err := c.Bind(&loginReq); err != nil {
        return c.JSON(http.StatusBadRequest, map[string]string{
            "error": "Invalid request",
        })
    }
    
    // 2. Process request
    token, err := authenticateUser(loginReq.Email, loginReq.Password)
    if err != nil {
        return c.JSON(http.StatusUnauthorized, map[string]string{
            "error": "Invalid credentials",
        })
    }
    
    // 3. Return JSON response
    return c.JSON(http.StatusOK, map[string]string{
        "token": token,
    })
}
Echo Guidelines:
  • Use middleware for CORS, logging, recovery
  • Handler signature: func(c echo.Context) error
  • Return JSON responses: c.JSON(status, data)
  • Validate inputs before processing

Testing (Future Implementation)

When adding tests: Go Tests:
// File: auth_test.go
package auth

import "testing"

func TestGenerateToken(t *testing.T) {
    token, err := GenerateToken("user123")
    if err != nil {
        t.Errorf("GenerateToken failed: %v", err)
    }
    if token == "" {
        t.Error("Expected non-empty token")
    }
}
Run tests:
# Run all tests
go test ./... -v

# Run specific test
go test ./auth -run TestGenerateToken -v
TypeScript Tests:
// Use Vitest or Jest
import { describe, it, expect } from 'vitest';

describe('UserProfile', () => {
  it('renders user name', () => {
    // Test implementation
  });
});

Security Standards

Never commit or log sensitive data:
  • Never log or expose secrets, API keys, or passwords
  • Hash passwords with bcrypt (cost 14 is used)
  • Validate all user inputs
  • Use HTTPS in production
  • Set secure session cookies
  • Sanitize MongoDB queries to prevent injection

Pre-Commit Checklist

Before committing code: Backend:
cd backend
gofmt -w .           # Format code
make test            # Run tests
Frontend:
cd frontend
npm run lint         # Run ESLint
Both:
  • Code formatted properly
  • Tests pass
  • No linter errors
  • No .env files committed
  • Commit message follows convention

Quick Reference

Frontend

  • Indentation: 2 spaces
  • Quotes: Single quotes
  • Semicolons: Required
  • Components: PascalCase
  • Functions: camelCase

Backend

  • Formatting: gofmt -w .
  • Imports: Standard, external, local
  • Exported: PascalCase
  • Unexported: camelCase
  • Error handling: Always check

Next Steps

Contributing

Learn how to contribute to the project

Project Structure

Understand the codebase organization

Build docs developers (and LLMs) love