14 KiB
RestHeadSpec Headers Documentation
RestHeadSpec provides a comprehensive header-based REST API where all query options are passed via HTTP headers instead of request body. This document describes all supported headers and their usage.
Overview
RestHeadSpec uses HTTP headers for:
- Field selection
- Filtering and searching
- Joins and relationship loading
- Sorting and pagination
- Advanced query features
- Response formatting
- Transaction control
Header Naming Convention
All headers support optional identifiers at the end to allow multiple instances of the same header type. This is useful when you need to specify multiple related filters or options.
Examples:
# Standard header
x-preload: employees
# Headers with identifiers (both work the same)
x-preload-main: employees
x-preload-secondary: department
x-preload-1: projects
The system uses strings.HasPrefix() to match headers, so any suffix after the header name is ignored for matching purposes. This allows you to:
- Add descriptive identifiers:
x-sort-primary,x-sort-fallback - Add numeric identifiers:
x-fieldfilter-status-1,x-fieldfilter-status-2 - Organize related headers:
x-preload-employee-data,x-preload-department-info
Header Categories
1. Field Selection
x-select-fields
Specify which columns to include in the response.
Format: Comma-separated list of column names
x-select-fields: id,name,email,created_at
x-not-select-fields
Specify which columns to exclude from the response.
Format: Comma-separated list of column names
x-not-select-fields: password,internal_notes
x-clean-json
Remove null and empty fields from the response.
Format: Boolean (true/false)
x-clean-json: true
2. Filtering & Search
x-fieldfilter-{colname}
Exact match filter on a specific column.
Format: x-fieldfilter-{columnName}: {value}
x-fieldfilter-status: active
x-fieldfilter-department_id: dept123
x-searchfilter-{colname}
Fuzzy search (ILIKE) on a specific column.
Format: x-searchfilter-{columnName}: {searchTerm}
x-searchfilter-name: john
x-searchfilter-description: website
This will match any records where the column contains the search term (case-insensitive).
x-searchop-{operator}-{colname}
Search with specific operators (AND logic).
Supported Operators:
contains- Contains substring (case-insensitive)beginswith/startswith- Starts with (case-insensitive)endswith- Ends with (case-insensitive)equals/eq- Exact matchnotequals/neq/ne- Not equalgreaterthan/gt- Greater thanlessthan/lt- Less thangreaterthanorequal/gte/ge- Greater than or equallessthanorequal/lte/le- Less than or equalbetween- Between two values, exclusive (> val1 AND < val2) - format:value1,value2betweeninclusive- Between two values, inclusive (>= val1 AND <= val2) - format:value1,value2in- In a list of values - format:value1,value2,value3empty/isnull/null- Is NULL or empty stringnotempty/isnotnull/notnull- Is NOT NULL and not empty string
Type-Aware Features:
- Text searches use case-insensitive matching (ILIKE with citext cast)
- Numeric comparisons work with integers, floats, and decimals
- Date/time comparisons handle timestamps correctly
- JSON field support for structured data
Examples:
# Text search (case-insensitive)
x-searchop-contains-name: smith
# Numeric comparison
x-searchop-gt-age: 25
x-searchop-gte-salary: 50000
# Date range (exclusive)
x-searchop-between-created_at: 2024-01-01,2024-12-31
# Date range (inclusive)
x-searchop-betweeninclusive-birth_date: 1990-01-01,2000-12-31
# List matching
x-searchop-in-status: active,pending,review
# NULL checks
x-searchop-empty-deleted_at: true
x-searchop-notempty-email: true
x-searchor-{operator}-{colname}
Same as x-searchop but with OR logic instead of AND.
x-searchor-eq-status: active
x-searchor-eq-status: pending
x-searchand-{operator}-{colname}
Explicit AND logic (same as x-searchop).
x-searchand-gte-age: 18
x-searchand-lte-age: 65
x-searchcols
Specify columns for "all" search operations.
Format: Comma-separated list
x-searchcols: name,email,description
x-custom-sql-w
Raw SQL WHERE clause with AND condition.
Format: SQL WHERE clause (without the WHERE keyword)
x-custom-sql-w: status = 'active' AND created_at > '2024-01-01'
⚠️ Warning: Use with caution - ensure proper SQL injection prevention.
x-custom-sql-or
Raw SQL WHERE clause with OR condition.
Format: SQL WHERE clause
x-custom-sql-or: status = 'archived' OR is_deleted = true
3. Joins & Relations
x-preload
Preload related tables using the ORM's preload functionality.
Format: RelationName:field1,field2 or RelationName
Multiple relations can be specified using multiple headers or by separating with |
Examples:
# Preload all fields from employees relation
x-preload: employees
# Preload specific fields from employees
x-preload: employees:id,first_name,last_name,email
# Multiple preloads using pipe separator
x-preload: employees:id,name|department:id,name
# Multiple preloads using separate headers with identifiers
x-preload-1: employees:id,first_name,last_name
x-preload-2: department:id,name
x-preload-related: projects:id,name,status
x-expand
LEFT JOIN related tables and expand results inline.
Format: Same as x-preload
x-expand: department:id,name,code
Note: Currently, expand falls back to preload behavior. Full JOIN expansion is planned for future implementation.
x-custom-sql-join
Raw SQL JOIN statement.
Format: SQL JOIN clause
x-custom-sql-join: LEFT JOIN departments d ON d.id = employees.department_id
⚠️ Note: Not yet fully implemented.
4. Sorting & Pagination
x-sort
Sort results by one or more columns.
Format: Comma-separated list with optional + (ASC) or - (DESC) prefix
# Single column ascending (default)
x-sort: name
# Single column descending
x-sort: -created_at
# Multiple columns
x-sort: +department,- created_at,name
# Equivalent to: ORDER BY department ASC, created_at DESC, name ASC
x-limit
Limit the number of records returned.
Format: Integer
x-limit: 50
x-offset
Skip a number of records (offset-based pagination).
Format: Integer
x-offset: 100
x-cursor-forward
Cursor-based pagination (forward).
Format: Cursor string
x-cursor-forward: eyJpZCI6MTIzfQ==
⚠️ Note: Not yet fully implemented.
x-cursor-backward
Cursor-based pagination (backward).
Format: Cursor string
x-cursor-backward: eyJpZCI6MTIzfQ==
⚠️ Note: Not yet fully implemented.
5. Advanced Features
x-advsql-{colname}
Advanced SQL expression for a specific column.
Format: x-advsql-{columnName}: {SQLExpression}
x-advsql-full_name: CONCAT(first_name, ' ', last_name)
x-advsql-age_years: EXTRACT(YEAR FROM AGE(birth_date))
⚠️ Note: Not yet fully implemented in query execution.
x-cql-sel-{colname}
Computed Query Language - custom SQL expressions aliased as columns.
Format: x-cql-sel-{aliasName}: {SQLExpression}
x-cql-sel-employee_count: COUNT(employees.id)
x-cql-sel-total_revenue: SUM(orders.amount)
⚠️ Note: Not yet fully implemented in query execution.
x-distinct
Apply DISTINCT to the query.
Format: Boolean (true/false)
x-distinct: true
⚠️ Note: Implementation depends on ORM adapter support.
x-skipcount
Skip counting total records (performance optimization).
Format: Boolean (true/false)
x-skipcount: true
When enabled, the total count will be -1 in the response metadata.
x-skipcache
Bypass query cache (if caching is implemented).
Format: Boolean (true/false)
x-skipcache: true
x-fetch-rownumber
Get the row number of a specific record in the result set.
Format: Record identifier
x-fetch-rownumber: record123
⚠️ Note: Not yet implemented.
x-pkrow
Similar to x-fetch-rownumber - get row number by primary key.
Format: Primary key value
x-pkrow: 123
⚠️ Note: Not yet implemented.
6. Response Format
x-simpleapi
Return simple format (just the data array).
Format: Presence of header activates it
x-simpleapi: true
Response Format:
[
{ "id": 1, "name": "John" },
{ "id": 2, "name": "Jane" }
]
x-detailapi
Return detailed format with metadata (default).
Format: Presence of header activates it
x-detailapi: true
Response Format:
{
"success": true,
"data": [...],
"metadata": {
"total": 100,
"filtered": 100,
"limit": 50,
"offset": 0
}
}
x-syncfusion
Format response for Syncfusion UI components.
Format: Presence of header activates it
x-syncfusion: true
Response Format:
{
"result": [...],
"count": 100
}
7. Transaction Control
x-transaction-atomic
Use atomic transactions for write operations.
Format: Boolean (true/false)
x-transaction-atomic: true
Ensures that all write operations in the request succeed or fail together.
Base64 Encoding
Headers support base64 encoding for complex values. Use one of these prefixes:
ZIP_- Base64 encoded value__- Base64 encoded value (double underscore)
Example:
# Plain value
x-custom-sql-w: status = 'active'
# Base64 encoded (same value)
x-custom-sql-w: ZIP_c3RhdHVzID0gJ2FjdGl2ZSc=
Complete Examples
Example 1: Basic Query
GET /api/employees HTTP/1.1
Host: example.com
x-select-fields: id,first_name,last_name,email,department_id
x-preload: department:id,name
x-searchfilter-name: john
x-searchop-gte-created_at: 2024-01-01
x-sort: -created_at,+last_name
x-limit: 50
x-offset: 0
x-skipcount: false
x-detailapi: true
Example 2: Complex Query with Multiple Filters and Preloads
GET /api/employees HTTP/1.1
Host: example.com
x-select-fields-main: id,first_name,last_name,email,department_id,manager_id
x-preload-1: department:id,name,code
x-preload-2: manager:id,first_name,last_name
x-preload-3: projects:id,name,status
x-fieldfilter-status-1: active
x-searchop-gte-created_at-filter1: 2024-01-01
x-searchop-lt-created_at-filter2: 2024-12-31
x-searchfilter-name-query: smith
x-sort-primary: -created_at
x-sort-secondary: +last_name
x-limit-page: 100
x-offset-page: 0
x-detailapi: true
Note: The identifiers after the header names (like -main, -1, -filter1, etc.) are optional and help organize multiple headers of the same type. Both approaches work:
# Without identifiers
x-preload: employees
x-preload: department
# With identifiers (more organized)
x-preload-1: employees
x-preload-2: department
Response:
{
"success": true,
"data": [
{
"id": "emp1",
"first_name": "John",
"last_name": "Doe",
"email": "john@example.com",
"department_id": "dept1",
"department": {
"id": "dept1",
"name": "Engineering"
}
}
],
"metadata": {
"total": 1,
"filtered": 1,
"limit": 50,
"offset": 0
}
}
HTTP Method Mapping
GET /{schema}/{entity}- List all recordsGET /{schema}/{entity}/{id}- Get single recordPOST /{schema}/{entity}- Create record(s)PUT /{schema}/{entity}/{id}- Update recordPATCH /{schema}/{entity}/{id}- Partial updateDELETE /{schema}/{entity}/{id}- Delete recordGET /{schema}/{entity}/metadata- Get table metadata
Implementation Status
✅ Implemented:
- Field selection (select/omit columns)
- Filtering (field filters, search filters, operators)
- Preloading relations
- Sorting and pagination
- Skip count optimization
- Response format options
- Base64 decoding
⚠️ Partially Implemented:
- Expand (currently falls back to preload)
- DISTINCT (depends on ORM adapter)
🚧 Planned:
- Advanced SQL expressions (advsql, cql-sel)
- Custom SQL joins
- Cursor pagination
- Row number fetching
- Full expand with JOIN
- Query caching control
Security Considerations
-
SQL Injection: Custom SQL headers (
x-custom-sql-*) should be properly sanitized or restricted to trusted users only. -
Query Complexity: Consider implementing query complexity limits to prevent resource exhaustion.
-
Authentication: Implement proper authentication and authorization checks before processing requests.
-
Rate Limiting: Apply rate limiting to prevent abuse.
-
Field Restrictions: Consider implementing field-level permissions to restrict access to sensitive columns.
Performance Tips
- Use
x-skipcount: truefor large datasets when you don't need the total count - Select only needed columns with
x-select-fields - Use preload wisely - only load relations you need
- Implement proper database indexes for filtered and sorted columns
- Consider pagination for large result sets
Migration from ResolveSpec
RestHeadSpec is an alternative to ResolveSpec that uses headers instead of request body for options:
ResolveSpec (body-based):
POST /api/departments
{
"operation": "read",
"options": {
"preload": [{"relation": "employees"}],
"filters": [{"column": "status", "operator": "eq", "value": "active"}],
"limit": 50
}
}
RestHeadSpec (header-based):
GET /api/departments
x-preload: employees
x-fieldfilter-status: active
x-limit: 50
Both implementations share the same core handler logic and database adapters.