Query DSL
The FLXBL Query DSL is a JSON-based query language that supports MongoDB-style operators and graph traversal. Query complex data without writing SQL or Cypher.
Basic Query Structure
// POST /api/v1/dynamic/Product/query
{
"where": {
"price": { "$lte": 100 },
"inStock": { "$eq": true }
},
"orderBy": "price",
"orderDirection": "ASC",
"limit": 20
}Query options:
| Option | Type | Description |
|---|---|---|
where | object | Filter conditions |
traverse | array | Graph relationship traversal |
orderBy | string | Field to sort by |
orderDirection | "ASC" | "DESC" | Sort direction |
limit | number | Maximum results to return |
offset | number | Number of results to skip |
includeCount | boolean | When true, returns { data: [...], totalCount: number } for pagination |
Comparison Operators
// Comparison operators
{
"where": {
"price": { "$eq": 99.99 }, // Equal
"price": { "$neq": 99.99 }, // Not equal
"price": { "$gt": 50 }, // Greater than
"price": { "$gte": 50 }, // Greater than or equal
"price": { "$lt": 100 }, // Less than
"price": { "$lte": 100 }, // Less than or equal
"status": { "$in": ["active", "pending"] }, // In array
"status": { "$notIn": ["archived", "deleted"] } // Not in array
}
}| Operator | Description | Example |
|---|---|---|
$eq | Equal to | { "status": { "$eq": "active" } } |
$neq | Not equal to | { "status": { "$neq": "deleted" } } |
$gt | Greater than | { "price": { "$gt": 50 } } |
$gte | Greater than or equal | { "price": { "$gte": 50 } } |
$lt | Less than | { "price": { "$lt": 100 } } |
$lte | Less than or equal | { "price": { "$lte": 100 } } |
$in | Value in array | { "status": { "$in": ["a", "b"] } } |
$notIn | Value not in array | { "status": { "$notIn": ["x", "y"] } } |
Array Operators
For array fields, use $contains to check if the array includes a specific value:
// Array operators (for array fields)
{
"where": {
"tags": { "$contains": "featured" } // Check if array contains value
}
}| Operator | Description | Example |
|---|---|---|
$contains | Array contains value | { "tags": { "$contains": "featured" } } |
String Operators
For string fields, FLXBL provides case-insensitive matching operators:
// String operators (case-insensitive)
{
"where": {
"name": { "$contains": "widget" }, // Substring match
"email": { "$startsWith": "admin" }, // Prefix match
"email": { "$endsWith": "@example.com" } // Suffix match
}
}| Operator | Description | Example |
|---|---|---|
$contains | Substring match (case-insensitive) | { "name": { "$contains": "widget" } } |
$startsWith | Prefix match (case-insensitive) | { "email": { "$startsWith": "admin" } } |
$endsWith | Suffix match (case-insensitive) | { "email": { "$endsWith": "@example.com" } } |
Note: The $contains operator works differently based on field type:
for array fields, it checks if the array includes the value;
for string fields, it performs a case-insensitive substring search.
Null Operators
Check for null or non-null field values:
// Null operators
{
"where": {
"deletedAt": { "$isNull": true }, // Field is null
"assignedTo": { "$isNull": false } // Field is not null
}
}| Operator | Description | Example |
|---|---|---|
$isNull | Check if field is null (true) or not null (false) | { "deletedAt": { "$isNull": true } } |
Logical Operators
Combine multiple conditions with $and and $or:
// Logical operators
{
"where": {
"$and": [
{ "price": { "$gte": 10 } },
{ "price": { "$lte": 100 } }
]
}
}
// OR condition
{
"where": {
"$or": [
{ "status": { "$eq": "featured" } },
{ "price": { "$lt": 20 } }
]
}
}
// Combining AND and OR
{
"where": {
"$and": [
{ "inStock": { "$eq": true } },
{
"$or": [
{ "category": { "$eq": "electronics" } },
{ "category": { "$eq": "accessories" } }
]
}
]
}
}Graph Traversal
The traverse option lets you follow relationships in the graph.
This is one of FLXBL's most powerful features. Use it to filter entities based on
related data, or to include related entities in your response with include: true.
The examples below show traverse for filtering - finding entities based on their relationships without including the related data in the response:
// Traverse for filtering - find products in Electronics category
// (related entities are NOT included in response)
{
"where": {
"name": { "$eq": "Wireless Headphones" }
},
"traverse": [{
"relationship": "BELONGS_TO",
"direction": "out",
"where": {
"name": { "$eq": "Electronics" }
}
}]
}
// Multi-hop filtering
{
"where": {
"email": { "$eq": "alice@example.com" }
},
"traverse": [
{
"relationship": "PURCHASED",
"direction": "out"
},
{
"relationship": "BELONGS_TO",
"direction": "out",
"where": {
"name": { "$eq": "Electronics" }
}
}
]
}Traverse options:
| Option | Type | Default | Description |
|---|---|---|---|
relationship | string | required | Name of the relationship to follow |
direction | "in" | "out" | "both" | "out" | Direction to traverse |
where | object | - | Filter conditions for related entities |
include | boolean | false | When true, includes related entities in response (traverse projection) |
limit | number | 100 | Maximum related entities to return when include: true |
offset | number | 0 | Number of related entities to skip when include: true |
orderBy | string | - | Field to sort included related entities by |
orderDirection | "ASC" | "DESC" | "ASC" | Sort direction for included related entities |
traverse | array | - | Nested traverse steps for multi-hop queries |
Traverse for Filtering vs Traverse Projection
The traverse option works in two modes depending on whether you set include: true:
- Traverse for Filtering (default,
include: false): Filters root entities based on related entity conditions. The related entities are NOT included in the response - only the root entities that match the traverse conditions are returned. - Traverse Projection (
include: true): Fetches related entities and nests them in the response. Related entities appear under a field named after the target entity type (pluralized).
Traverse Projection Example
To include related entities in your response, set include: true in your traverse step.
You can also paginate and sort the included entities:
// Traverse projection - include related entities in response
// Use include: true to fetch related data
{
"traverse": [{
"relationship": "WROTE",
"direction": "out",
"include": true,
"limit": 10,
"orderBy": "createdAt",
"orderDirection": "DESC"
}],
"limit": 20
}The response includes related entities nested under the target entity name:
// Response with traverse projection
// Related entities are nested under the target entity name (pluralized)
[
{
"id": "user_123",
"name": "John Doe",
"email": "john@example.com",
"posts": [
{
"id": "post_456",
"title": "My First Post",
"published": true,
"createdAt": "2024-01-15T10:00:00Z",
"_relationship": {
"type": "WROTE",
"createdAt": "2024-01-15T10:00:00Z"
}
},
{
"id": "post_789",
"title": "Another Post",
"published": true,
"createdAt": "2024-01-10T10:00:00Z",
"_relationship": {
"type": "WROTE",
"createdAt": "2024-01-10T10:00:00Z"
}
}
],
"createdAt": "2024-01-01T00:00:00Z",
"updatedAt": "2024-01-15T10:00:00Z"
}
] Note: Each related entity includes a _relationship object
with metadata about the relationship (type, timestamps, and any relationship properties).
Multi-hop Traverse Projection
You can nest traverse steps to fetch multiple levels of related entities:
// Multi-hop traverse projection
// Get users with their posts, and each post with its comments
{
"traverse": [{
"relationship": "WROTE",
"direction": "out",
"include": true,
"limit": 5,
"traverse": [{
"relationship": "HAS_COMMENT",
"direction": "out",
"include": true,
"limit": 10
}]
}],
"limit": 10
}Pagination
// Pagination and sorting
{
"where": {
"status": { "$eq": "active" }
},
"orderBy": "createdAt",
"orderDirection": "DESC",
"limit": 10,
"offset": 20
}Counting Results
For pagination UIs that need to display total pages or result counts, use includeCount: true
to get the total count alongside your paginated results:
// Get total count with paginated results
// Use includeCount: true for pagination UIs that need total pages
{
"where": {
"status": { "$eq": "active" }
},
"limit": 10,
"offset": 0,
"includeCount": true
}
// Response format with includeCount: true
{
"data": [
{ "id": "1", "name": "Product 1", ... },
{ "id": "2", "name": "Product 2", ... }
],
"totalCount": 157
}
The totalCount represents the total number of entities matching your filter criteria,
regardless of limit and offset. This is useful for calculating total pages
in pagination UIs.
Complete Example
Here's a complete example that combines multiple operators with graph traversal:
// Complete query example
curl -X POST https://api.flxbl.dev/api/v1/dynamic/Product/query \
-H "Authorization: Bearer flxbl_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"where": {
"$and": [
{ "price": { "$gte": 10, "$lte": 500 } },
{ "inStock": { "$eq": true } },
{
"$or": [
{ "tags": { "$contains": "featured" } },
{ "tags": { "$contains": "sale" } }
]
}
]
},
"traverse": [{
"relationship": "BELONGS_TO",
"direction": "out",
"where": {
"slug": { "$in": ["electronics", "accessories"] }
}
}],
"orderBy": "price",
"orderDirection": "ASC",
"limit": 20
}'TypeScript Client
Use the generated TypeScript client for type-safe queries:
// TypeScript client usage
import { createFlxblClient } from './schemas';
const client = createFlxblClient({
baseUrl: 'https://api.flxbl.dev',
apiKey: 'flxbl_your_api_key'
});
// Query with type safety
const products = await client.query('Product', {
where: {
price: { $gte: 10, $lte: 100 },
inStock: { $eq: true }
},
traverse: [{
relationship: 'BELONGS_TO',
direction: 'out',
where: { slug: { $eq: 'electronics' } }
}],
orderBy: 'price',
orderDirection: 'ASC',
limit: 20
});
console.log(products); // Typed as Product[]Best Practices
- Limit results: Always set a reasonable limit to avoid returning too much data
- Optimize traversals: Start with the most selective condition first
- Cache when possible: Use Redis caching for frequently repeated queries
- Test with real data: Query performance varies with data size
Next Steps
- API Reference - Complete endpoint documentation
- Schema Design - Design schemas for better queries
- MCP Integration - Generate typed clients