Knowledge BaseBest Practices

Best practices for every stack

108 curated best practices across 8 languages, including 42 security rules mapped to OWASP Top 10 and CWE IDs. The BeforeMerge AI review engine references these practices to deliver specific, authoritative code review feedback.

Categories

Each practice is classified into one of these categories. The AI review engine selects relevant practices based on your repository's detected languages and frameworks.

Security

42 practices

Prevent vulnerabilities and protect user data

Performance

23 practices

Optimize speed, memory, and resource usage

Accessibility

3 practices

Ensure inclusive and usable interfaces

Architecture

16 practices

Maintainable structure and design patterns

Testing

7 practices

Reliable tests that catch real bugs

Error Handling

8 practices

Graceful failure and recovery

Code Quality

9 practices

Readable, maintainable, and idiomatic code

Best practices by language

Browse all practices organized by language and framework. Each practice includes a description, code examples, and references.

TypeScript (34 practices)

securityOWASP A03CWE-94, CWE-95

Never use eval() or Function() constructor

eval() and new Function() execute arbitrary strings as code, enabling code injection attacks. Use structured alternatives like JSON.parse() or a sandboxed interpreter.

Avoid
const result = eval(userInput);
Prefer
const data = JSON.parse(userInput);
securityReactOWASP A03CWE-79

Sanitize dangerouslySetInnerHTML input

Rendering unsanitized HTML via dangerouslySetInnerHTML creates XSS vulnerabilities. Always sanitize with a library like DOMPurify before rendering.

Avoid
<div dangerouslySetInnerHTML={{ __html: userContent }} />
Prefer
import DOMPurify from "dompurify";
<div dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(userContent) }} />
securityOWASP A03CWE-20

Validate all user input at system boundaries

Parse and validate user input with a schema library (Zod, Yup, io-ts) at API routes, server actions, and form handlers. Never trust client-side validation alone.

Avoid
export async function updateProfile(formData: FormData) {
  const name = formData.get("name") as string;
  await db.update({ name });
}
Prefer
const schema = z.object({ name: z.string().min(1).max(100) });

export async function updateProfile(formData: FormData) {
  const { name } = schema.parse(Object.fromEntries(formData));
  await db.update({ name });
}
securityOWASP A03CWE-89

Use parameterized queries, never string interpolation

Building SQL queries with template literals or string concatenation enables SQL injection. Use parameterized queries or an ORM query builder.

Avoid
const result = await db.query(`SELECT * FROM users WHERE id = '${userId}'`);
Prefer
const result = await db.query("SELECT * FROM users WHERE id = $1", [userId]);
securityOWASP A02, OWASP A05CWE-200, CWE-522

Never expose secrets in client-side code

API keys, database credentials, and service tokens must only be used server-side. Use NEXT_PUBLIC_ prefix only for truly public values. Never import server modules in client components.

Avoid
// In a client component
const supabase = createClient(process.env.SUPABASE_SERVICE_ROLE_KEY!);
Prefer
// In a server action
const supabase = createAdminClient(); // uses server-only env var internally
securityNext.jsOWASP A01CWE-352

Use Server Actions or CSRF tokens for mutations

Next.js Server Actions include built-in CSRF protection. If using API routes for mutations, implement CSRF token validation to prevent cross-site request forgery.

securityOWASP A01CWE-22

Validate file paths to prevent directory traversal

User-supplied file paths can escape intended directories with ../ sequences. Always resolve and validate paths against a base directory.

Avoid
const filePath = path.join(uploadDir, req.query.filename);
fs.readFileSync(filePath);
Prefer
const filePath = path.resolve(uploadDir, req.query.filename);
if (!filePath.startsWith(path.resolve(uploadDir))) {
  throw new Error("Invalid path");
}
fs.readFileSync(filePath);
securityOWASP A03CWE-1321

Guard against prototype pollution

Deep merge or Object.assign with user-controlled keys can pollute Object.prototype. Use Object.create(null) for dictionaries and validate keys against __proto__, constructor, prototype.

Avoid
function merge(target: any, source: any) {
  for (const key in source) {
    target[key] = source[key]; // allows __proto__ pollution
  }
}
Prefer
function merge(target: Record<string, unknown>, source: Record<string, unknown>) {
  const forbidden = new Set(["__proto__", "constructor", "prototype"]);
  for (const key of Object.keys(source)) {
    if (forbidden.has(key)) continue;
    target[key] = source[key];
  }
}
securityOWASP A03CWE-1333

Avoid catastrophic backtracking in regular expressions

Complex regex with nested quantifiers can cause ReDoS (Regular Expression Denial of Service). Use atomic groups, possessive quantifiers, or test with safe-regex.

Avoid
const pattern = /^(a+)+$/; // exponential backtracking
Prefer
const pattern = /^a+$/; // simple, no nested quantifiers
securityNext.jsOWASP A03, OWASP A05CWE-79

Configure Content Security Policy headers

Set CSP headers to prevent XSS, clickjacking, and other injection attacks. Use next.config.js headers or middleware to enforce CSP across your application.

performance

Avoid N+1 query patterns

Fetching related data in a loop creates N+1 queries. Use joins, batch fetches, or .in() filters to load related data in a single query.

Avoid
const users = await db.from("users").select("id");
for (const u of users) {
  const posts = await db.from("posts").select("*").eq("user_id", u.id);
}
Prefer
const users = await db.from("users").select("id, posts(*)");
// Or batch: const posts = await db.from("posts").in("user_id", userIds);
performanceReact

Use React.memo and useMemo appropriately

Wrap expensive pure components with React.memo and memoize costly computations with useMemo. But avoid premature memoization — measure first.

Avoid
function ExpensiveList({ items }: { items: Item[] }) {
  const sorted = items.sort((a, b) => a.name.localeCompare(b.name));
  return <>{sorted.map(item => <ListItem key={item.id} item={item} />)}</>;
}
Prefer
const ExpensiveList = React.memo(function ExpensiveList({ items }: { items: Item[] }) {
  const sorted = useMemo(
    () => [...items].sort((a, b) => a.name.localeCompare(b.name)),
    [items]
  );
  return <>{sorted.map(item => <ListItem key={item.id} item={item} />)}</>;
});
performanceNode.js

Avoid blocking the event loop

Synchronous operations (fs.readFileSync, crypto.pbkdf2Sync, large JSON.parse) block the event loop. Use async variants or offload to a worker thread.

Avoid
const data = fs.readFileSync("/large-file.json", "utf8");
const parsed = JSON.parse(data);
Prefer
const data = await fs.promises.readFile("/large-file.json", "utf8");
const parsed = JSON.parse(data);
performance

Use dynamic imports for large dependencies

Importing heavy libraries at the top level increases bundle size and initial load time. Use dynamic import() for libraries only needed in specific code paths.

Avoid
import { marked } from "marked"; // always in bundle

export function renderMarkdown(md: string) {
  return marked(md);
}
Prefer
export async function renderMarkdown(md: string) {
  const { marked } = await import("marked");
  return marked(md);
}
performance

Always LIMIT database queries

Fetching all rows from a table without a LIMIT can return millions of rows and crash the application. Always paginate or set a reasonable limit.

Avoid
const { data } = await supabase.from("logs").select("*");
Prefer
const { data } = await supabase.from("logs").select("*").order("created_at", { ascending: false }).limit(100);
performanceReact

Use useCallback for handlers passed as props

Inline arrow functions in JSX create new references on every render, breaking React.memo on child components. Wrap handlers with useCallback when passed to memoized children.

Avoid
function Parent() {
  return <MemoChild onClick={() => doSomething()} />;
}
Prefer
function Parent() {
  const handleClick = useCallback(() => doSomething(), []);
  return <MemoChild onClick={handleClick} />;
}
performanceNext.js

Use next/image for automatic image optimization

The next/image component automatically serves optimized, responsive images with lazy loading. Using raw <img> tags misses these optimizations.

Avoid
<img src="/hero.png" alt="Hero" />
Prefer
import Image from "next/image";
<Image src="/hero.png" alt="Hero" width={800} height={400} />
performance

Parallelize independent async operations

Sequential awaits for independent operations waste time. Use Promise.all() for independent operations, or Promise.allSettled() when individual failures should not abort the batch.

Avoid
const users = await fetchUsers();
const posts = await fetchPosts();
const comments = await fetchComments();
Prefer
const [users, posts, comments] = await Promise.all([
  fetchUsers(),
  fetchPosts(),
  fetchComments(),
]);
code-qualityReact

Avoid setState in useEffect — derive state instead

Setting state inside useEffect to transform props causes unnecessary re-renders and sync bugs. Derive the value during render or use useMemo.

Avoid
const [fullName, setFullName] = useState("");
useEffect(() => {
  setFullName(first + " " + last);
}, [first, last]);
Prefer
const fullName = `${first} ${last}`;
error-handlingReact

Use Error Boundaries for component failure isolation

Without Error Boundaries, a single component crash takes down the entire tree. Wrap feature sections in Error Boundaries to contain failures gracefully.

Avoid
// No error boundary — crash propagates to root
<App>
  <Dashboard />
  <Analytics /> {/* crash here kills everything */}
</App>
Prefer
<App>
  <Dashboard />
  <ErrorBoundary fallback={<p>Analytics unavailable</p>}>
    <Analytics />
  </ErrorBoundary>
</App>
code-qualityReact

Use stable, unique keys for list rendering

Using array index as key causes incorrect reconciliation when items are reordered, added, or removed. Use a stable unique identifier from the data.

Avoid
{items.map((item, index) => <ListItem key={index} item={item} />)}
Prefer
{items.map(item => <ListItem key={item.id} item={item} />)}
code-qualityReact

Clean up side effects in useEffect

Event listeners, intervals, subscriptions, and AbortControllers must be cleaned up in the useEffect return function to prevent memory leaks.

Avoid
useEffect(() => {
  window.addEventListener("resize", handler);
}, []);
Prefer
useEffect(() => {
  window.addEventListener("resize", handler);
  return () => window.removeEventListener("resize", handler);
}, []);
code-qualityReact

Prefer controlled components or form libraries for forms

Uncontrolled inputs with ref.current.value are error-prone and hard to validate. Use controlled components with state, or a form library like react-hook-form.

Avoid
const ref = useRef<HTMLInputElement>(null);
const handleSubmit = () => {
  const value = ref.current?.value;
};
Prefer
const [value, setValue] = useState("");
<input value={value} onChange={e => setValue(e.target.value)} />
code-qualityReact

Use Suspense for async data loading patterns

Suspense provides a declarative loading pattern that avoids waterfall renders and manual loading state management.

architectureNext.js

Use Server Components by default

Server Components reduce client bundle size and enable direct database access. Only add 'use client' when you need interactivity (event handlers, hooks, browser APIs).

Avoid
"use client";
// This component only renders data — no interactivity
export default function UserList({ users }) {
  return <ul>{users.map(u => <li key={u.id}>{u.name}</li>)}</ul>;
}
Prefer
// No "use client" needed — this is a Server Component
export default async function UserList() {
  const users = await db.from("users").select("*");
  return <ul>{users.map(u => <li key={u.id}>{u.name}</li>)}</ul>;
}
code-qualityNext.js

Use the Metadata API for SEO

Export a metadata object or generateMetadata function from page/layout files instead of using <head> tags manually. This enables streaming and proper deduplication.

Avoid
export default function Page() {
  return (
    <>
      <head><title>My Page</title></head>
      <main>...</main>
    </>
  );
}
Prefer
export const metadata: Metadata = {
  title: "My Page",
  description: "Page description for SEO",
};

export default function Page() {
  return <main>...</main>;
}
performanceNext.js

Fetch data on the server when possible

Client-side data fetching with useEffect adds waterfall latency and loading spinners. In the App Router, fetch data in Server Components or Server Actions.

Avoid
"use client";
const [data, setData] = useState(null);
useEffect(() => {
  fetch("/api/data").then(r => r.json()).then(setData);
}, []);
Prefer
// Server Component — data is fetched at request time
export default async function Page() {
  const data = await getData();
  return <DataView data={data} />;
}
securityNext.jsOWASP A03CWE-20

Validate request body in API route handlers

API route handlers (route.ts) receive raw request data. Always parse and validate the body with a schema before processing.

Avoid
export async function POST(req: Request) {
  const body = await req.json();
  await db.insert(body); // unvalidated
}
Prefer
export async function POST(req: Request) {
  const body = schema.parse(await req.json());
  await db.insert(body);
}
error-handlingNext.js

Add loading.tsx and error.tsx to route segments

Missing loading and error files cause raw spinners or unhandled crashes. Add loading.tsx for streaming UI and error.tsx for graceful error recovery in each major route segment.

performanceNext.js

Use parallel data fetching to avoid waterfalls

Sequential awaits in Server Components create request waterfalls. Use Promise.all or parallel route segments to load independent data concurrently.

Avoid
export default async function Page() {
  const user = await getUser();
  const posts = await getPosts(); // waits for user first
  const comments = await getComments(); // waits for posts
}
Prefer
export default async function Page() {
  const [user, posts, comments] = await Promise.all([
    getUser(),
    getPosts(),
    getComments(),
  ]);
}
architectureNode.js

Handle process signals for graceful shutdown

Long-running services should handle SIGTERM and SIGINT to close database connections, finish in-flight requests, and flush logs before exiting.

Prefer
process.on("SIGTERM", async () => {
  await server.close();
  await db.end();
  process.exit(0);
});
code-qualityNode.js

Use structured logging instead of console.log

Structured JSON logging (pino, winston) enables filtering, searching, and alerting in production. Raw console.log is unstructured and hard to parse at scale.

Avoid
console.log("User " + userId + " logged in");
Prefer
log.info("User logged in", { userId, source: "auth" });
architectureNode.js

Validate environment variables at startup

Missing or malformed env vars cause runtime crashes in production. Validate all required env vars at application startup with a schema.

Avoid
const apiKey = process.env.API_KEY!; // crashes at runtime if missing
Prefer
const env = z.object({
  API_KEY: z.string().min(1),
  DATABASE_URL: z.string().url(),
}).parse(process.env);
error-handlingNode.js

Use AbortController for cancellable fetch requests

Long-running fetch requests without timeouts can hang indefinitely. Use AbortController with a timeout to cancel stale requests.

Avoid
const res = await fetch(url); // no timeout
Prefer
const controller = new AbortController();
setTimeout(() => controller.abort(), 5000);
const res = await fetch(url, { signal: controller.signal });

Python (15 practices)

securityOWASP A08CWE-502

Never use pickle for untrusted data

pickle.loads() executes arbitrary Python code during deserialization. Use JSON, MessagePack, or Protocol Buffers for untrusted data.

Avoid
import pickle
data = pickle.loads(user_input)
Prefer
import json
data = json.loads(user_input)
securityOWASP A03CWE-89

Use parameterized SQL queries

String formatting in SQL queries enables SQL injection. Use parameterized queries with placeholders.

Avoid
cursor.execute(f"SELECT * FROM users WHERE id = '{user_id}'")
Prefer
cursor.execute("SELECT * FROM users WHERE id = %s", (user_id,))
securityOWASP A01CWE-22

Validate file paths to prevent traversal

User-supplied paths can escape the intended directory with ../ sequences. Use pathlib to resolve and validate paths.

Avoid
filepath = os.path.join(upload_dir, request.args["filename"])
with open(filepath) as f:
    return f.read()
Prefer
filepath = (Path(upload_dir) / request.args["filename"]).resolve()
if not str(filepath).startswith(str(Path(upload_dir).resolve())):
    raise ValueError("Invalid path")
with open(filepath) as f:
    return f.read()
securityOWASP A03CWE-78

Avoid shell=True in subprocess calls

subprocess with shell=True passes the command through the shell, enabling command injection. Use a list of arguments instead.

Avoid
subprocess.run(f"convert {user_filename}", shell=True)
Prefer
subprocess.run(["convert", user_filename], shell=False)
securityOWASP A02CWE-330, CWE-338

Use secrets module for cryptographic randomness

The random module is not cryptographically secure. Use secrets for tokens, passwords, and security-sensitive random values.

Avoid
import random
token = ''.join(random.choices(string.ascii_letters, k=32))
Prefer
import secrets
token = secrets.token_urlsafe(32)
securityOWASP A08CWE-502

Use yaml.safe_load instead of yaml.load

yaml.load() can execute arbitrary Python objects. Always use yaml.safe_load() for untrusted YAML input.

Avoid
import yaml
data = yaml.load(user_input)
Prefer
import yaml
data = yaml.safe_load(user_input)
performance

Use generators for large datasets

Loading entire datasets into memory causes OOM errors. Use generators and itertools for lazy evaluation of large sequences.

Avoid
results = [transform(item) for item in get_all_records()]  # loads all into memory
Prefer
def process_records():
    for item in get_all_records():
        yield transform(item)
architecture

Avoid global mutable state

Global mutable variables cause concurrency bugs and make testing difficult. Use dependency injection, function parameters, or context managers.

Avoid
db_connection = None  # global mutable

def get_users():
    return db_connection.query("SELECT * FROM users")
Prefer
def get_users(db: Connection) -> list[User]:
    return db.query("SELECT * FROM users")
code-quality

Use type hints for function signatures

Type hints improve readability, enable static analysis with mypy, and serve as documentation. Annotate function parameters and return types.

Avoid
def process_order(order, user, discount=None):
    pass
Prefer
def process_order(order: Order, user: User, discount: Decimal | None = None) -> OrderResult:
    pass
code-quality

Use context managers for resource cleanup

Files, database connections, and locks must be properly closed. Context managers (with statements) ensure cleanup even when exceptions occur.

Avoid
f = open("data.csv")
data = f.read()
f.close()  # skipped if exception occurs
Prefer
with open("data.csv") as f:
    data = f.read()
testing

Use pytest fixtures for test setup

Fixtures provide reusable, composable test setup and teardown. Use fixtures instead of setUp/tearDown methods or repeated inline setup.

Prefer
@pytest.fixture
def db():
    conn = create_test_db()
    yield conn
    conn.close()

def test_create_user(db):
    user = create_user(db, "Alice")
    assert user.name == "Alice"
securityDjangoOWASP A01CWE-352

Never disable CSRF protection

Django's CSRF middleware prevents cross-site request forgery. Never use @csrf_exempt on views that modify data unless they use token-based auth (e.g., API endpoints with JWT).

Avoid
@csrf_exempt
def update_profile(request):
    # No CSRF protection!
Prefer
def update_profile(request):
    # CSRF token validated automatically by middleware
securityDjangoOWASP A03CWE-20

Always validate serializer input before saving

Call .is_valid(raise_exception=True) on DRF serializers before accessing .validated_data or calling .save().

Avoid
serializer = UserSerializer(data=request.data)
serializer.save()  # no validation!
Prefer
serializer = UserSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
serializer.save()
securityFlaskOWASP A05CWE-489

Never run Flask with debug=True in production

Flask debug mode exposes an interactive debugger that allows arbitrary code execution. Ensure DEBUG is False in production.

Avoid
app.run(debug=True)  # NEVER in production
Prefer
app.run(debug=os.getenv("FLASK_DEBUG", "false").lower() == "true")

Go (10 practices)

securityOWASP A03CWE-20

Validate all inputs at handler boundaries

HTTP handlers should validate and parse input immediately. Use a validation library or custom validation before processing.

Avoid
func handler(w http.ResponseWriter, r *http.Request) {
    id := r.URL.Query().Get("id")
    user, _ := db.GetUser(id) // unvalidated
}
Prefer
func handler(w http.ResponseWriter, r *http.Request) {
    id := r.URL.Query().Get("id")
    if _, err := uuid.Parse(id); err != nil {
        http.Error(w, "invalid id", http.StatusBadRequest)
        return
    }
    user, err := db.GetUser(id)
}
securityOWASP A02CWE-330, CWE-338

Use crypto/rand, not math/rand for security-sensitive values

math/rand produces predictable sequences. Use crypto/rand for tokens, keys, nonces, and any security-sensitive random values.

Avoid
import "math/rand"
token := fmt.Sprintf("%d", rand.Int())
Prefer
import "crypto/rand"
b := make([]byte, 32)
crypto_rand.Read(b)
token := base64.URLEncoding.EncodeToString(b)
error-handling

Never ignore errors — always handle or propagate

Ignoring errors with _ hides failures that can lead to data corruption, security bypasses, or silent data loss. Always check and handle every error.

Avoid
result, _ := db.Query("SELECT * FROM users")
// error silently ignored
Prefer
result, err := db.Query("SELECT * FROM users")
if err != nil {
    return fmt.Errorf("query users: %w", err)
}
securityOWASP A03CWE-89

Use parameterized queries with database/sql

String formatting in SQL queries creates injection vulnerabilities. Use placeholder arguments with db.Query or db.Exec.

Avoid
db.Query(fmt.Sprintf("SELECT * FROM users WHERE name = '%s'", name))
Prefer
db.Query("SELECT * FROM users WHERE name = $1", name)
performance

Prevent goroutine leaks with context cancellation

Goroutines that block forever on channels or I/O without cancellation leak memory. Use context.WithCancel or context.WithTimeout to ensure cleanup.

Avoid
go func() {
    for {
        data := <-ch // blocks forever if ch is never closed
        process(data)
    }
}()
Prefer
go func() {
    for {
        select {
        case data := <-ch:
            process(data)
        case <-ctx.Done():
            return
        }
    }
}()
architecture

Use context for cancellation and timeouts

Pass context.Context as the first parameter to functions that do I/O or spawn goroutines. This enables graceful cancellation and timeout propagation.

Avoid
func FetchData(url string) ([]byte, error) {
    resp, err := http.Get(url)
}
Prefer
func FetchData(ctx context.Context, url string) ([]byte, error) {
    req, _ := http.NewRequestWithContext(ctx, "GET", url, nil)
    resp, err := http.DefaultClient.Do(req)
}
architecture

Accept interfaces, return structs

Accepting interfaces makes functions flexible and testable. Returning concrete structs gives callers access to all methods without type assertions.

Avoid
func NewService() ServiceInterface {
    return &service{}
}
Prefer
type Reader interface {
    Read(ctx context.Context, id string) (Record, error)
}

func NewService(db Reader) *Service {
    return &Service{db: db}
}
error-handling

Wrap errors with context using fmt.Errorf %w

Bare error returns lose context about where failures occurred. Wrap errors with fmt.Errorf and %w to preserve the error chain while adding context.

Avoid
user, err := db.GetUser(id)
if err != nil {
    return err // no context
}
Prefer
user, err := db.GetUser(id)
if err != nil {
    return fmt.Errorf("get user %s: %w", id, err)
}
performance

Use sync.Pool for frequently allocated objects

Allocating and GC-ing many short-lived objects causes CPU pressure. sync.Pool recycles objects to reduce allocation overhead in hot paths.

Prefer
var bufPool = sync.Pool{
    New: func() any { return new(bytes.Buffer) },
}

func process() {
    buf := bufPool.Get().(*bytes.Buffer)
    defer bufPool.Put(buf)
    buf.Reset()
}
testing

Use table-driven tests

Table-driven tests make it easy to add cases and see all scenarios at a glance. Use t.Run for subtest naming and parallel execution.

Prefer
tests := []struct {
    name  string
    input string
    want  int
}{
    {"empty", "", 0},
    {"hello", "hello", 5},
}
for _, tt := range tests {
    t.Run(tt.name, func(t *testing.T) {
        got := Len(tt.input)
        if got != tt.want {
            t.Errorf("Len(%q) = %d, want %d", tt.input, got, tt.want)
        }
    })
}

Rust (5 practices)

security

Minimize and document unsafe blocks

Unsafe code bypasses Rust's safety guarantees. Isolate unsafe code into small, well-documented functions with safety invariants documented in comments.

Avoid
unsafe {
  // 50 lines of complex logic
  let ptr = some_ffi_call();
  // ... more unsafe operations
}
Prefer
/// # Safety
/// `ptr` must be a valid, aligned pointer to an initialized `Foo`.
unsafe fn deref_foo(ptr: *const Foo) -> &Foo {
    &*ptr
}
performance

Avoid unnecessary clones — use references and borrowing

Cloning large data structures is expensive. Use references (&T) and borrowing to share data without copying. Clone only when ownership transfer is genuinely needed.

Avoid
fn process(data: Vec<String>) {
    let copy = data.clone(); // unnecessary clone
    for item in &copy { ... }
}
Prefer
fn process(data: &[String]) {
    for item in data { ... }
}
error-handling

Use Result<T, E> with thiserror for typed errors

Use the type system for error handling instead of panicking. Define error enums with thiserror for clean, descriptive error types.

Avoid
fn parse_config(path: &str) -> Config {
    let data = std::fs::read_to_string(path).unwrap(); // panics
    serde_json::from_str(&data).unwrap() // panics
}
Prefer
#[derive(thiserror::Error, Debug)]
enum ConfigError {
    #[error("failed to read config: {0}")]
    Io(#[from] std::io::Error),
    #[error("invalid config format: {0}")]
    Parse(#[from] serde_json::Error),
}

fn parse_config(path: &str) -> Result<Config, ConfigError> {
    let data = std::fs::read_to_string(path)?;
    Ok(serde_json::from_str(&data)?)
}
architecture

Use newtype pattern for domain types

Wrapping primitive types in newtypes prevents mixing up values with the same underlying type (e.g., UserId vs OrderId) and enables custom validation.

Prefer
struct UserId(uuid::Uuid);
struct OrderId(uuid::Uuid);

// These types cannot be accidentally mixed up
fn get_user_orders(user_id: UserId) -> Vec<OrderId> { ... }
performance

Prefer iterator chains over manual loops

Iterator chains are zero-cost abstractions that enable the compiler to optimize better than manual loops with mutable accumulators.

Avoid
let mut result = Vec::new();
for item in &items {
    if item.active {
        result.push(item.name.to_uppercase());
    }
}
Prefer
let result: Vec<_> = items.iter()
    .filter(|item| item.active)
    .map(|item| item.name.to_uppercase())
    .collect();

Ruby (4 practices)

securityRailsOWASP A08CWE-915

Use strong parameters to prevent mass assignment

Passing raw params to create/update allows attackers to set any attribute. Use strong parameters to whitelist allowed fields.

Avoid
User.create(params[:user]) # all fields allowed
Prefer
User.create(user_params)

def user_params
  params.require(:user).permit(:name, :email)
end
securityRailsOWASP A03CWE-89

Never interpolate user input in ActiveRecord queries

String interpolation in where clauses bypasses ActiveRecord's parameterization. Use hash conditions or array conditions.

Avoid
User.where("name = '#{params[:name]}'")
Prefer
User.where(name: params[:name])
# or
User.where("name = ?", params[:name])
performanceRails

Use eager loading to prevent N+1 queries

Accessing associations in loops triggers a query per record. Use includes, preload, or eager_load to batch-load associations.

Avoid
@posts = Post.all
# In view: post.comments.count triggers N+1
Prefer
@posts = Post.includes(:comments).all
securityRailsOWASP A01CWE-352

Keep protect_from_forgery enabled

Rails CSRF protection is enabled by default. Never skip it for form-based endpoints. API-only controllers should use token-based auth instead.

Java (4 practices)

securityOWASP A08CWE-502

Avoid Java native deserialization of untrusted data

ObjectInputStream.readObject() can execute arbitrary code. Use JSON, Protocol Buffers, or a safe deserialization library for untrusted input.

securityOWASP A03CWE-89

Use PreparedStatement, never string concatenation in SQL

Building SQL with string concatenation enables injection. Use PreparedStatement with parameterized queries.

Avoid
Statement stmt = conn.createStatement();
stmt.executeQuery("SELECT * FROM users WHERE id = '" + userId + "'");
Prefer
PreparedStatement stmt = conn.prepareStatement("SELECT * FROM users WHERE id = ?");
stmt.setString(1, userId);
architecture

Prefer immutable objects

Immutable objects are thread-safe, easier to reason about, and prevent unintended side effects. Use records (Java 16+) or final fields with no setters.

Avoid
public class User {
    public String name;
    public String email;
}
Prefer
public record User(String name, String email) {}
error-handling

Never catch generic Exception or Throwable

Catching Exception swallows unexpected errors including NullPointerException and OutOfMemoryError. Catch specific exception types and handle them appropriately.

Avoid
try {
    processOrder();
} catch (Exception e) {
    // swallows everything
}
Prefer
try {
    processOrder();
} catch (PaymentException e) {
    handlePaymentFailure(e);
} catch (InventoryException e) {
    handleOutOfStock(e);
}

SQL / Database (15 practices)

securityOWASP A03CWE-89

Always use parameterized queries

SQL injection is the most exploited web vulnerability. Never concatenate user input into SQL strings. Use parameterized queries, prepared statements, or ORM query builders.

securitySupabaseOWASP A01CWE-284

Enable Row Level Security on all tables

Tables without RLS policies are accessible to any authenticated user via the Supabase client. Enable RLS and define explicit policies for each table.

Avoid
CREATE TABLE documents (
  id uuid PRIMARY KEY,
  content text
);
-- No RLS enabled!
Prefer
CREATE TABLE documents (
  id uuid PRIMARY KEY,
  content text,
  org_id uuid NOT NULL
);
ALTER TABLE documents ENABLE ROW LEVEL SECURITY;
CREATE POLICY "org_access" ON documents
  USING (org_id = (SELECT org_id FROM profiles WHERE id = auth.uid()));
securityOWASP A01CWE-250

Use least-privilege database roles

Application database users should have only the permissions they need. Never connect with superuser or admin credentials from application code.

performance

Add indexes on foreign key columns

Foreign keys without indexes cause full table scans on joins and cascading deletes. Add an index on every foreign key column.

Avoid
CREATE TABLE posts (
  id uuid PRIMARY KEY,
  user_id uuid REFERENCES users(id)
);
-- No index on user_id!
Prefer
CREATE TABLE posts (
  id uuid PRIMARY KEY,
  user_id uuid REFERENCES users(id)
);
CREATE INDEX idx_posts_user_id ON posts(user_id);
performance

Avoid SELECT * in production queries

SELECT * fetches all columns including large text/blob fields and prevents index-only scans. Select only the columns you need.

Avoid
SELECT * FROM users WHERE id = $1;
Prefer
SELECT id, name, email FROM users WHERE id = $1;
performance

Use EXPLAIN ANALYZE on slow queries

Before optimizing, understand the query plan. EXPLAIN ANALYZE shows actual execution times, row counts, and whether indexes are used.

Prefer
EXPLAIN ANALYZE SELECT * FROM orders WHERE status = 'pending' AND created_at > now() - interval '7 days';
performance

Use cursor-based pagination for large datasets

OFFSET-based pagination becomes slow on large tables because the database must skip N rows. Use keyset (cursor) pagination with WHERE + LIMIT.

Avoid
SELECT * FROM products ORDER BY created_at LIMIT 20 OFFSET 10000;
Prefer
SELECT * FROM products
WHERE created_at < $cursor
ORDER BY created_at DESC
LIMIT 20;
performance

Use batch inserts instead of row-by-row

Inserting rows one at a time in a loop creates massive overhead from round trips and transaction commits. Batch inserts into a single statement.

Avoid
for item in items:
    cursor.execute("INSERT INTO logs (msg) VALUES (%s)", (item,))
Prefer
cursor.executemany(
    "INSERT INTO logs (msg) VALUES (%s)",
    [(item,) for item in items]
)
architectureSupabase

Use RPC functions for atomic multi-table operations

Multiple sequential Supabase queries are not atomic. Wrap multi-table operations in a PostgreSQL function and call it via .rpc() for transactional safety.

Avoid
await supabase.from("orders").insert(order);
await supabase.from("inventory").update(inv); // fails: order exists without inventory update
Prefer
// PostgreSQL function handles both in a transaction
await supabase.rpc("create_order_with_inventory", {
  order_data: order,
  inventory_update: inv,
});
error-handlingSupabase

Avoid .single() on queries that may return multiple rows

The .single() modifier throws an error if the query returns more than one row. Use .maybeSingle() for optional lookups and .limit(1) when you want just the first match.

Avoid
const { data } = await supabase
  .from("users")
  .select("*")
  .eq("email", email)
  .single(); // throws if duplicates exist
Prefer
const { data } = await supabase
  .from("users")
  .select("*")
  .eq("email", email)
  .maybeSingle(); // returns null if not found, no error on missing
securitySupabase

Never trust client-side RLS for admin operations

RLS policies protect against unauthorized access, but admin mutations (e.g., org-wide updates) should use the service_role client to bypass RLS safely on the server.

Avoid
// Client component doing admin operation through RLS
const supabase = createClient();
await supabase.from("org_settings").update({ plan: "enterprise" });
Prefer
// Server action using admin client
const admin = createAdminClient();
await admin.from("org_settings").update({ plan: "enterprise" }).eq("org_id", orgId);
performance

Use composite indexes for multi-column WHERE clauses

Queries filtering on multiple columns benefit from a single composite index rather than separate single-column indexes.

Prefer
CREATE INDEX idx_orders_status_date ON orders(status, created_at DESC);
architecture

Write backward-compatible migrations

Migrations that rename or drop columns break running application instances during deployment. Use a two-phase approach: add new column, migrate data, then remove old column in a later release.

performance

Use connection pooling for application databases

Opening a new connection per query is expensive. Use a connection pool (PgBouncer, built-in pool) to reuse connections efficiently.

architecture

Include created_at and updated_at on every table

Timestamps enable debugging, auditing, and cache invalidation. Use database defaults and triggers to ensure they are always populated.

Prefer
CREATE TABLE items (
  id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
  name text NOT NULL,
  created_at timestamptz DEFAULT now(),
  updated_at timestamptz DEFAULT now()
);

CREATE TRIGGER items_updated_at
  BEFORE UPDATE ON items
  FOR EACH ROW EXECUTE FUNCTION moddatetime(updated_at);

General (21 practices)

securityOWASP A01CWE-284, CWE-862

Enforce access control on every endpoint

Every API endpoint and server action must verify authentication and authorization before processing. Missing checks allow privilege escalation and unauthorized data access.

securityOWASP A02CWE-327, CWE-328

Use strong, modern cryptographic algorithms

MD5 and SHA1 are broken for security purposes. Use SHA-256+, bcrypt/scrypt/argon2 for passwords, and AES-256-GCM for encryption.

securityOWASP A03CWE-20, CWE-74

Treat all user input as untrusted

Input from HTTP parameters, headers, cookies, file uploads, and third-party APIs must be validated and sanitized before use in queries, commands, or rendering.

securityOWASP A07CWE-307

Implement rate limiting on authentication endpoints

Without rate limiting, authentication endpoints are vulnerable to brute force and credential stuffing attacks. Implement progressive delays or lockouts.

securityOWASP A09CWE-778

Log security events and monitor for anomalies

Log authentication attempts, authorization failures, input validation failures, and admin actions. Never log secrets, passwords, or PII.

securityOWASP A10CWE-918

Validate URLs before server-side fetching

When the server fetches a user-supplied URL, validate the scheme (allow only https), resolve the hostname, and block private/internal IP ranges to prevent SSRF.

securityOWASP A05CWE-798

Never hardcode secrets in source code

API keys, passwords, and tokens in source code get committed to version control and are visible in CI logs. Use environment variables or a secrets manager.

securityOWASP A06

Audit dependencies for known vulnerabilities

Run npm audit, pip-audit, or govulncheck regularly. Pin dependency versions and review changelogs before upgrading major versions.

architecture

Use dependency injection for testability

Hardcoded dependencies make code untestable and inflexible. Pass dependencies as constructor/function parameters so they can be mocked in tests and swapped in different environments.

Avoid
class OrderService {
  private db = new PostgresDB(); // hardcoded
  async getOrder(id: string) { ... }
}
Prefer
class OrderService {
  constructor(private db: Database) {} // injected
  async getOrder(id: string) { ... }
}
architecture

Separate business logic from infrastructure

Business rules should not depend on HTTP frameworks, databases, or file systems. Keep core logic in pure functions and use adapters for I/O.

architecture

Single Responsibility — one reason to change per module

A module that handles parsing, validation, business logic, and database access has too many reasons to change. Split into focused modules with clear boundaries.

architecture

Open-Closed Principle — extend behavior without modifying code

Use composition, strategy patterns, and plugin architectures to add new behavior without changing existing code. This reduces regression risk.

architecture

Keep files under 500 lines

Files over 500 lines are hard to navigate, review, and test. Extract related functionality into separate modules when files grow beyond this threshold.

testing

Test behavior, not implementation details

Tests that assert on internal state or mock every dependency break on refactoring. Test the public interface and observable behavior.

Avoid
// Tests implementation details
expect(service._internalCache.size).toBe(5);
expect(mockDb.query).toHaveBeenCalledTimes(3);
Prefer
// Tests observable behavior
const result = await service.getTopProducts(5);
expect(result).toHaveLength(5);
expect(result[0].name).toBe("Widget");
testing

Mock at system boundaries, not everywhere

Only mock external dependencies (databases, APIs, file systems). Let internal modules collaborate naturally to catch integration issues.

testing

Structure tests with Arrange-Act-Assert

Each test should have three clear sections: set up preconditions (Arrange), execute the action (Act), and verify the outcome (Assert). This makes tests readable and maintainable.

Prefer
test("creates user with hashed password", async () => {
  // Arrange
  const input = { email: "test@example.com", password: "secret123" };

  // Act
  const user = await createUser(input);

  // Assert
  expect(user.email).toBe("test@example.com");
  expect(user.password_hash).not.toBe("secret123");
});
testing

Make tests deterministic — no flaky tests

Tests that depend on timing, network calls, random values, or shared state produce intermittent failures. Use fixed seeds, fake clocks, and isolated test databases.

testing

Cover error paths, not just happy paths

Most bugs live in error handling, edge cases, and boundary conditions. Write tests for invalid input, network failures, empty collections, and concurrent access.

accessibility

Provide text alternatives for all non-text content

Images need alt text, icons need aria-labels, and interactive elements need accessible names. Screen readers cannot interpret visual content without text alternatives.

Avoid
<button><TrashIcon /></button>
<img src="chart.png" />
Prefer
<button aria-label="Delete item"><TrashIcon /></button>
<img src="chart.png" alt="Sales chart showing 20% growth in Q3" />
accessibility

Ensure full keyboard navigation

All interactive elements must be reachable and operable with keyboard alone. Use semantic HTML elements (button, a, input) which have built-in keyboard support.

Avoid
<div onClick={handleClick} className="button-like">Click me</div>
Prefer
<button onClick={handleClick}>Click me</button>
accessibility

Maintain WCAG color contrast ratios

Text must have at least 4.5:1 contrast ratio against its background (3:1 for large text). Use tools like the WebAIM contrast checker to verify.

OWASP Top 10 (2021) mapping

Security practices in this knowledge base are mapped to the OWASP Top 10 categories. The AI review engine uses these mappings to classify findings and provide authoritative references.

A02: Cryptographic Failures

Weak or missing encryption for data in transit and at rest, use of deprecated algorithms, and improper key management.

OWASP reference

A04: Insecure Design

Missing or ineffective control design, failure to use secure design patterns, and missing threat modeling.

OWASP reference

A05: Security Misconfiguration

Missing security hardening, improperly configured permissions, unnecessary features enabled, default credentials, and verbose error handling.

OWASP reference

A06: Vulnerable and Outdated Components

Using components with known vulnerabilities, unsupported software, and failure to track and update dependencies.

OWASP reference

A07: Identification and Authentication Failures

Weak authentication mechanisms, credential stuffing, brute force attacks, session fixation, and missing multi-factor authentication.

OWASP reference

A08: Software and Data Integrity Failures

Code and infrastructure that does not protect against integrity violations, including insecure CI/CD pipelines, auto-update without verification, and insecure deserialization.

OWASP reference

A09: Security Logging and Monitoring Failures

Insufficient logging, monitoring, and alerting that prevents detection of active breaches and incident response.

OWASP reference

A10: Server-Side Request Forgery (SSRF)

Application fetches a remote resource without validating the user-supplied URL, allowing attackers to coerce the application to send requests to unexpected destinations.

OWASP reference

Let AI review with best practices

The BeforeMerge AI engine references this knowledge base during every review, giving your team specific, authoritative feedback instead of generic advice.