Querying Data
Querying Data
ObjectQL uses a JSON-based Protocol for all data operations. Unlike SQL (strings) or Query Builders (chained methods), ObjectQL queries are Data Structures.
This design makes it strictly typed, easy to serialise/transport over HTTP, and safe from injection—perfect for both human developers and AI Agents.
📖 Looking for query best practices and optimization strategies?
Check out the Query Best Practices Guide for a comprehensive guide on choosing the right approach (JSON-DSL, REST, GraphQL) and performance optimization techniques.
The find() Operation
The find method recovers a list of records matching specific criteria.
const products = await app.object('product').find({
filters: {
category: 'electronics',
price: { $gt: 500 }
},
fields: ['name', 'price', 'category'],
sort: ['-price'], // Descending
skip: 0,
limit: 10
});Filters
Filters use an object-based syntax with implicit equality and operator objects.
Implicit AND:
filters: {
status: 'active',
stock: { $gt: 0 }
}
// SQL: WHERE status = 'active' AND stock > 0Explicit OR:
filters: {
$or: [
{ status: 'active' },
{ featured: true }
]
}Sorting
field: Ascending.-field: Descending.
CRUD Operations
Create (Insert)
Both create() and insert() methods are supported (aliases for the same operation):
// Using create (recommended)
const user = await app.object('user').create({
name: "Alice",
email: "alice@example.com",
role: "admin"
});
console.log(user.id); // Returns full record with generated ID
// Using insert (alias, returns ID only for backward compatibility)
const id = await app.object('user').insert({
name: "Bob",
email: "bob@example.com",
role: "user"
});
console.log(id); // Returns just the ID stringUpdate
Updates are always bulk operations targeted by filters. To update a single record, filter by ID.
// Update specific record
await app.object('user').update(
{ filters: { id: '123' } }, // Target (use 'id' not '_id')
{ doc: { status: 'active' } } // Change
);
// Bulk update
await app.object('product').update(
{ filters: { stock: 0 } },
{ doc: { status: 'out_of_stock' } }
);Delete
// Delete specific record
await app.object('user').delete({
filters: { _id: '123' }
});Relationships (Joins & Expand)
ObjectQL handles relationships distinctively. Instead of SQL JOIN keywords, we use the expand property to hydrate related records. This ensures compatibility across SQL and NoSQL drivers (where joins might be separate queries).
1. The expand Syntax
To get related data, define the relationship in expand.
// Fetch tasks and include project details
const tasks = await app.object('task').find({
fields: ['name', 'status', 'project'], // 'project' key returns the ID
expand: {
project: {
fields: ['name', 'start_date', 'owner']
}
}
});
/* Result:
{
name: "Fix Bug",
project: {
name: "Q1 Web App",
owner: "Alice"
}
}
*/2. Nested Expansion
You can nest expansions arbitrarily deep.
expand: {
project: {
fields: ['name'],
expand: {
// Expand the 'manager' field on the 'project' object
manager: {
fields: ['name', 'email']
}
}
}
}3. Filtering on Related Records
There are two ways to filter based on relationships:
A. Filter the Root (Dot Notation) Find tasks where the project's status is active. (Note: Requires a driver that supports SQL Joins)
filters: {
'project.status': 'active'
}B. Filter the Expanded List Find projects, but only include completed tasks in the expansion.
app.object('project').find({
expand: {
tasks: {
filters: { status: 'completed' }
}
}
})Aggregation
ObjectQL supports SQL-like aggregation via the aggregate() method on the repository.
const stats = await app.object('order').aggregate({
// 1. Filter first
filters: { status: 'paid' },
// 2. Group by specific fields
groupBy: ['customer_id'],
// 3. Define aggregate functions
aggregate: [
{ func: 'sum', field: 'total', alias: 'total_revenue' },
{ func: 'count', field: 'id', alias: 'order_count' },
{ func: 'max', field: 'created_at', alias: 'last_order_date' }
]
});
/* Result:
[
{ customer_id: "101", total_revenue: 5000, order_count: 3, last_order_date: "2023-12-01..." },
{ customer_id: "102", total_revenue: 1200, order_count: 1, last_order_date: "2023-11-15..." }
]
*/Supported Functions
countsumavgminmax
Why JSON?
- Transportable: The query IS the HTTP request body. No translation needed.
- Secure: Impossible to inject SQL syntax.
- Generatable: LLMs produce perfect JSON structures naturally.
Data Access
Learn how to query and access data using ObjectQL's type-safe SDK and JSON-based protocol
Modern Filter Syntax Guide
ObjectQL now supports a modern, intuitive object-based filter syntax inspired by MongoDB, Prisma, and other leading ORMs. This guide covers the new filter syntax and migration from the legacy array-based format.