api-v1/db/queryHelper/README.md

7.0 KiB

Query Helper - JSON-Based Database Interface

A centralized, SQL-string-free database query helper built on Knex.js that provides a safe, structured interface for all database operations.

🎯 Goals

  • No Raw SQL: All queries built using Knex query builder
  • JSON-Based: Structured JSON objects instead of SQL strings
  • Type Safe: Parameterized queries prevent SQL injection
  • Server-Only: Internal helper, not exposed to API clients
  • Production Ready: Error handling, validation, and security built-in

📦 Installation

Knex.js is already installed. The query helper uses the existing database connection from db/pool.js.

🚀 Quick Start

import { select, insert, update, deleteRecord, execute } from '../db/queryHelper/index.js';

// SELECT
const users = await select({
    table: 'users',
    where: { deleted: false },
    limit: 10
});

// INSERT
const user = await insert({
    table: 'users',
    data: { name: 'John', phone_number: '+1234567890' },
    returning: '*'
});

// UPDATE
const updated = await update({
    table: 'users',
    data: { name: 'Jane' },
    where: { id: userId },
    returning: '*'
});

// DELETE
await deleteRecord({
    table: 'users',
    where: { id: userId }
});

// TRANSACTION
await execute({
    type: 'transaction',
    handler: async (trx) => {
        await trx('users').insert(userData);
        await trx('listings').insert(listingData);
    }
});

📚 API Reference

select(options)

Retrieve records from database.

Options:

  • table (string, required): Table name (must be in whitelist)
  • columns (string[] | '', optional): Columns to select (default: '')
  • where (object, optional): WHERE conditions
  • orderBy (object, optional): { column: string, direction: 'asc'|'desc' }
  • limit (number, optional): Max records (capped at 100)
  • offset (number, optional): Skip records
  • joins (array, optional): Join configurations

Returns: Promise<object[]>

insert(options)

Insert new record(s).

Options:

  • table (string, required): Table name
  • data (object | object[], required): Data to insert
  • returning (string[] | '*', optional): Columns to return

Returns: Promise<object | object[]>

update(options)

Update existing records.

Options:

  • table (string, required): Table name
  • data (object, required): Data to update
  • where (object, required): WHERE conditions (required for safety)
  • returning (string[] | '*', optional): Columns to return

Returns: Promise<object[]>

deleteRecord(options)

Delete records.

Options:

  • table (string, required): Table name
  • where (object, required): WHERE conditions (required for safety)
  • returning (string[] | '*', optional): Columns to return

Returns: Promise<object[]>

execute(options)

Execute transactions or custom query builder logic.

Options:

  • type (string, required): 'transaction' or 'raw-builder'
  • handler (function, required): Handler function receiving knex/trx instance

Returns: Promise<any>

🔍 WHERE Clause Operators

Simple Equality

where: { status: 'active' }
// WHERE status = 'active'

Comparison Operators

where: {
    price: { op: '>=', value: 1000 },
    age: { op: '<', value: 65 }
}
// WHERE price >= 1000 AND age < 65

IN Operator

where: {
    id: { op: 'in', value: ['uuid1', 'uuid2', 'uuid3'] }
}
// WHERE id IN ('uuid1', 'uuid2', 'uuid3')

LIKE Operator

where: {
    name: { op: 'ilike', value: '%john%' }
}
// WHERE name ILIKE '%john%'

BETWEEN Operator

where: {
    age: { op: 'between', value: [18, 65] }
}
// WHERE age BETWEEN 18 AND 65

NULL Checks

where: {
    deleted_at: { op: 'isNull' }
}
// WHERE deleted_at IS NULL

🔒 Security Features

  1. Table Whitelist: Only allowed tables can be queried
  2. Parameterized Queries: All values are parameterized
  3. WHERE Required: UPDATE/DELETE require WHERE clause
  4. Limit Caps: Maximum 100 records per query
  5. No SQL Strings: Impossible to inject SQL

📋 Allowed Tables

The following tables are whitelisted:

  • users
  • listings
  • animals
  • locations
  • species
  • breeds
  • listing_media
  • conversations
  • messages
  • communication_records
  • favorites
  • user_devices
  • subscription_plans
  • otp_requests
  • auth_audit

To add more tables, update ALLOWED_TABLES in queryHelper.js.

🎓 Examples

See db/queryHelper/examples.js for comprehensive examples.

📖 Migration Guide

See db/MIGRATION_EXAMPLE.md for before/after comparisons.

🔄 API Reference

See db/QUERY_HELPER_MAPPING.md for complete method reference and examples.

⚠️ Important Notes

  1. No Raw SQL: Never use pool.query() with SQL strings
  2. Always Use WHERE: UPDATE/DELETE must include WHERE clause
  3. Table Names: Must be in whitelist (case-sensitive)
  4. Transactions: Use execute({ type: 'transaction' }) for multi-step operations
  5. Complex Queries: Use execute({ type: 'raw-builder' }) for advanced cases

🐛 Error Handling

The query helper throws descriptive errors:

  • Table 'xxx' is not allowed - Table not in whitelist
  • WHERE clause is required - Missing WHERE for UPDATE/DELETE
  • Unsupported operator: xxx - Invalid operator
  • Database errors are propagated with context

📝 Best Practices

  1. Use Transactions: For multi-step operations
  2. Soft Deletes: Use UPDATE instead of DELETE
  3. Limit Results: Always set reasonable limits
  4. Validate Input: Validate data before passing to helper
  5. Handle Errors: Wrap in try-catch blocks
  6. Use Returning: Get inserted/updated records back

🔧 Advanced Usage

Complex Joins

const results = await select({
    table: 'listings',
    columns: ['listings.*', 'animals.*'],
    joins: [
        {
            type: 'inner',
            table: 'animals',
            on: 'listings.animal_id = animals.id'
        }
    ],
    where: { 'listings.deleted': false }
});

Custom Query Builder

const results = await execute({
    type: 'raw-builder',
    handler: async (knex) => {
        return await knex('listings')
            .select('listings.*')
            .join('animals', 'listings.animal_id', 'animals.id')
            .where('listings.deleted', false)
            .where('animals.species_id', speciesId)
            .orderBy('listings.created_at', 'desc')
            .limit(20);
    }
});

📚 Documentation

  • DEVELOPER_GUIDE.md - Complete developer guide with examples, flow diagrams, and best practices
  • QUICK_REFERENCE.md - One-page quick reference card for daily use
  • API_REFERENCE.md - Complete API reference with all methods and options

📞 Support

For questions or issues, refer to:

  • DEVELOPER_GUIDE.md - Comprehensive guide with real-world examples from the codebase
  • QUICK_REFERENCE.md - Quick lookup for common patterns
  • API_REFERENCE.md - Complete method documentation