ZQL Fundamentals
ZQL is Zero's query language for reading data from your database.
Inspired by SQL, ZQL is expressed in TypeScript with heavy use of the builder pattern. If you have used Drizzle or Kysely, ZQL will feel familiar.
What Makes ZQL Different
Unlike queries in classic databases, the result of a ZQL query is a view that updates automatically and efficiently as the underlying data changes. You can call a query's materialize()
method to get a view, but more typically you run queries via some framework-specific bindings. For example see useQuery
for React or SolidJS.
ZQL queries are composed of one or more clauses that are chained together into a query.
Basic Query Structure
ZQL queries start by selecting a table. There is no way to select a subset of columns; ZQL queries always return the entire row (modulo column permissions).
const z = new Zero(...);
// Returns a query that selects all rows and columns from the issue table.
z.query.issue;
This is a design tradeoff that allows Zero to better reuse the row locally for future queries. This also makes it easier to share types between different parts of the code.
You can then chain additional clauses to refine your query:
// Get the 10 most recently created issues
z.query.issue.orderBy('created', 'desc').limit(10);
// Get a specific issue by ID
z.query.issue.where('id', 42).one();
Data Immutability
TypeScript Integration
ZQL is designed to work seamlessly with TypeScript. Column names, table names, and data types are all inferred from your Zero Schema, providing full type safety and IntelliSense support.
// TypeScript knows 'priority' is a valid column and suggests available values
z.query.issue.where('priority', 'high');
// TypeScript will error if you use an invalid column name
z.query.issue.where('invalidColumn', 'value'); // ❌ Type error
// Return types are fully typed based on your schema
const issues: readonly IssueRow[] = await z.query.issue.run();
Running Queries
There are several ways to execute ZQL queries depending on your use case:
Reactive Queries (Recommended)
For UI components, use framework-specific hooks that automatically update when data changes:
// React
const [issues] = useQuery(z.query.issue.orderBy('created', 'desc'));
// SolidJS
const issues = createQuery(() => z.query.issue.orderBy('created', 'desc'));
One-time Queries
For non-reactive use cases, use the run()
method:
// Get current data available on client
const issues = await z.query.issue.run();
// Wait for server response to ensure completeness
const issues = await z.query.issue.run({type: 'complete'});
// Shorthand for run() with client data only
const issues = await z.query.issue;
Materialized Views
For advanced use cases, you can create a materialized view directly:
const view = z.query.issue.materialize();
view.addListener((issues, result) => {
console.log('Issues updated:', issues);
});
// Don't forget to clean up
view.destroy();
Query Composition
ZQL queries are composable, meaning you can build them up incrementally:
// Start with a base query
let query = z.query.issue;
// Add conditions based on user input
if (priority) {
query = query.where('priority', priority);
}
if (assignee) {
query = query.where('assignee', assignee);
}
// Add ordering and limits
query = query.orderBy('created', 'desc').limit(50);
// Execute the composed query
const [issues] = useQuery(query);
Next Steps
Now that you understand the fundamentals of ZQL, you can explore more specific topics:
- Query Clauses - Learn about ordering, limiting, and paging
- Filtering Data - Master the
where
clause and comparison operators - Relationships - Query related data across tables
- Query Lifecycle - Understand performance and caching behavior