api-v1/db/queryHelper/README.md

285 lines
7.0 KiB
Markdown

# 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
```javascript
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
```javascript
where: { status: 'active' }
// WHERE status = 'active'
```
### Comparison Operators
```javascript
where: {
price: { op: '>=', value: 1000 },
age: { op: '<', value: 65 }
}
// WHERE price >= 1000 AND age < 65
```
### IN Operator
```javascript
where: {
id: { op: 'in', value: ['uuid1', 'uuid2', 'uuid3'] }
}
// WHERE id IN ('uuid1', 'uuid2', 'uuid3')
```
### LIKE Operator
```javascript
where: {
name: { op: 'ilike', value: '%john%' }
}
// WHERE name ILIKE '%john%'
```
### BETWEEN Operator
```javascript
where: {
age: { op: 'between', value: [18, 65] }
}
// WHERE age BETWEEN 18 AND 65
```
### NULL Checks
```javascript
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
```javascript
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
```javascript
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