285 lines
7.0 KiB
Markdown
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
|
|
|