Skip to main content

Querying Data

Learn how to read data from your database using Ductape's query API. This guide covers filtering, sorting, pagination, relationships, and advanced query patterns.

Quick Example

const users = await ductape.database.query({
table: 'users',
where: { status: 'active' },
orderBy: { column: 'created_at', order: 'DESC' },
limit: 10,
});

console.log('Active users:', users.data);
console.log('Total count:', users.count);

Basic Queries

Simple Query

Fetch all records from a table:

const result = await ductape.database.query({
table: 'users',
});

Query with Connection Parameters

If you haven't called connect(), specify the connection:

const result = await ductape.database.query({
env: 'prd',
product: 'my-app',
database: 'users-db',
table: 'users',
});

Select Specific Columns

Fetch only the columns you need:

const result = await ductape.database.query({
table: 'users',
select: ['id', 'email', 'name', 'created_at'],
});

Filtering with WHERE

Simple Equality

const result = await ductape.database.query({
table: 'users',
where: {
status: 'active',
role: 'admin',
},
});

Comparison Operators

Use $-prefixed operators for advanced filtering:

const result = await ductape.database.query({
table: 'products',
where: {
price: { $GT: 10 }, // Greater than
stock: { $GTE: 5 }, // Greater than or equal
discount: { $LT: 50 }, // Less than
rating: { $LTE: 4.5 }, // Less than or equal
status: { $NE: 'deleted' }, // Not equal
},
});

Available Operators

OperatorDescriptionExample
$GTGreater than{ age: { $GT: 18 } }
$GTEGreater than or equal{ age: { $GTE: 18 } }
$LTLess than{ price: { $LT: 100 } }
$LTELess than or equal{ price: { $LTE: 100 } }
$NE or $NOTNot equal{ status: { $NE: 'deleted' } }
$INIn array{ status: { $IN: ['active', 'pending'] } }
$NOT_INNot in array{ status: { $NOT_IN: ['deleted', 'banned'] } }
$LIKEPattern match{ email: { $LIKE: '%@gmail.com' } }
$IS_NULLIs null{ deleted_at: { $IS_NULL: true } }
$IS_NOT_NULLIs not null{ verified_at: { $IS_NOT_NULL: true } }
$BETWEENBetween values{ created_at: { $BETWEEN: [start, end] } }

IN Operator

Match any value in an array:

const result = await ductape.database.query({
table: 'orders',
where: {
status: { $IN: ['pending', 'processing', 'shipped'] },
},
});

Pattern Matching (LIKE)

const result = await ductape.database.query({
table: 'users',
where: {
email: { $LIKE: '%@gmail.com' },
name: { $LIKE: 'John%' },
},
});

NULL Checks

const result = await ductape.database.query({
table: 'users',
where: {
deleted_at: { $IS_NULL: true }, // Not deleted
verified_at: { $IS_NOT_NULL: true }, // Verified
},
});

BETWEEN

const result = await ductape.database.query({
table: 'orders',
where: {
created_at: {
$BETWEEN: [new Date('2024-01-01'), new Date('2024-12-31')],
},
total: { $BETWEEN: [100, 500] },
},
});

Logical Operators

AND Conditions

All conditions must match:

const result = await ductape.database.query({
table: 'products',
where: {
$AND: {
price: { $GTE: 10, $LTE: 100 },
category: { $IN: ['electronics', 'gadgets'] },
stock: { $GT: 0 },
deleted_at: { $IS_NULL: true },
},
},
});

OR Conditions

Any condition can match:

const result = await ductape.database.query({
table: 'users',
where: {
$OR: {
role: 'admin',
is_superuser: true,
},
},
});

Nested AND/OR

Combine AND and OR for complex queries:

const result = await ductape.database.query({
table: 'orders',
where: {
$AND: {
total: { $GT: 100 },
status: { $IN: ['pending', 'processing'] },
$OR: {
priority: 'high',
express_shipping: true,
},
},
},
});

Sorting

Single Column Sort

import { SortOrder } from '@ductape/sdk';

const result = await ductape.database.query({
table: 'users',
orderBy: { column: 'created_at', order: SortOrder.DESC },
});

Multiple Column Sort

const result = await ductape.database.query({
table: 'products',
orderBy: [
{ column: 'category', order: 'ASC' },
{ column: 'price', order: 'DESC' },
],
});

Pagination

Limit and Offset

const page = 1;
const pageSize = 20;

const result = await ductape.database.query({
table: 'users',
limit: pageSize,
offset: (page - 1) * pageSize,
});

console.log('Page data:', result.data);
console.log('Total records:', result.count);
console.log('Total pages:', Math.ceil(result.count / pageSize));

Relationships (Include)

Fetch related data in a single query using the include option.

Many-to-One Relationship

Fetch a record with its related parent:

const result = await ductape.database.query({
table: 'users',
where: { id: 1 },
include: {
relation: 'profile',
type: 'many-to-one',
foreignKey: 'profile_id',
primaryKey: 'id',
select: ['bio', 'avatar_url'],
},
});

// Result:
// {
// id: 1,
// name: 'John Doe',
// email: 'john@example.com',
// profile: { bio: '...', avatar_url: '...' }
// }

One-to-Many Relationship

Fetch a record with its related children:

const result = await ductape.database.query({
table: 'users',
where: { id: 1 },
include: {
relation: 'posts',
type: 'one-to-many',
foreignKey: 'user_id',
primaryKey: 'id',
select: ['id', 'title', 'created_at'],
where: { published: true },
orderBy: { column: 'created_at', order: 'DESC' },
limit: 10,
},
});

// Result:
// {
// id: 1,
// name: 'John Doe',
// posts: [
// { id: 1, title: 'First Post', created_at: '...' },
// { id: 2, title: 'Second Post', created_at: '...' },
// ]
// }

Many-to-Many Relationship

Fetch records through a junction table:

const result = await ductape.database.query({
table: 'users',
where: { id: 1 },
include: {
relation: 'roles',
type: 'many-to-many',
through: 'user_roles',
throughForeignKey: 'user_id',
throughRelatedKey: 'role_id',
select: ['name', 'permissions'],
},
});

// Result:
// {
// id: 1,
// name: 'John Doe',
// roles: [
// { name: 'admin', permissions: ['read', 'write', 'delete'] },
// { name: 'editor', permissions: ['read', 'write'] },
// ]
// }

Multiple Includes

Include multiple relationships in one query:

const result = await ductape.database.query({
table: 'users',
where: { id: 1 },
include: [
{
relation: 'profile',
type: 'many-to-one',
select: ['bio', 'avatar_url'],
},
{
relation: 'posts',
type: 'one-to-many',
foreignKey: 'author_id',
where: { published: true },
limit: 5,
},
{
relation: 'comments',
type: 'one-to-many',
foreignKey: 'user_id',
select: ['content', 'created_at'],
limit: 10,
},
],
});

Nested Includes

Load deeply nested relationships:

const result = await ductape.database.query({
table: 'users',
where: { id: 1 },
include: {
relation: 'posts',
type: 'one-to-many',
select: ['id', 'title'],
include: {
relation: 'comments',
type: 'one-to-many',
foreignKey: 'post_id',
select: ['content', 'author_name'],
limit: 3,
},
},
});

// Result:
// {
// id: 1,
// name: 'John Doe',
// posts: [
// {
// id: 1,
// title: 'First Post',
// comments: [
// { content: 'Great post!', author_name: 'Jane' },
// { content: 'Thanks!', author_name: 'Bob' },
// ]
// }
// ]
// }

Raw Queries

For complex queries that can't be expressed with the query builder:

PostgreSQL

const result = await ductape.database.raw({
query: 'SELECT * FROM users WHERE created_at > $1 AND status = $2',
params: [new Date('2024-01-01'), 'active'],
});

console.log('Rows:', result.data);
console.log('Fields:', result.fields);

MySQL

const result = await ductape.database.raw({
query: 'SELECT * FROM users WHERE created_at > ? AND status = ?',
params: [new Date('2024-01-01'), 'active'],
});

MongoDB

const result = await ductape.database.raw({
query: {
status: 'active',
created_at: { $gte: new Date('2024-01-01') },
},
collection: 'users',
});

Query Result Structure

All query operations return a consistent result structure:

interface IQueryResult<T> {
data: T[]; // Array of matching records
count: number; // Total count (useful for pagination)
fields?: string[]; // Column names (for raw queries)
}

Query Options Reference

OptionTypeDescription
tablestringTable or collection name
selectstring[]Columns to return
whereobjectFilter conditions
orderByobject | object[]Sort configuration
limitnumberMaximum records to return
offsetnumberRecords to skip
includeobject | object[]Relationship includes
envstringEnvironment (if not connected)
productstringProduct tag (if not connected)
databasestringDatabase tag (if not connected)

Examples by Use Case

Find One Record

const user = await ductape.database.query({
table: 'users',
where: { id: userId },
limit: 1,
});

const singleUser = user.data[0];

Search with Pagination

async function searchUsers(query: string, page: number, pageSize: number) {
const result = await ductape.database.query({
table: 'users',
where: {
$OR: {
name: { $LIKE: `%${query}%` },
email: { $LIKE: `%${query}%` },
},
},
orderBy: { column: 'name', order: 'ASC' },
limit: pageSize,
offset: (page - 1) * pageSize,
});

return {
users: result.data,
total: result.count,
page,
pageSize,
totalPages: Math.ceil(result.count / pageSize),
};
}

Recent Records

const recentOrders = await ductape.database.query({
table: 'orders',
where: {
created_at: { $GTE: new Date(Date.now() - 24 * 60 * 60 * 1000) },
},
orderBy: { column: 'created_at', order: 'DESC' },
limit: 50,
});

Filtered Dashboard Data

const dashboardData = await ductape.database.query({
table: 'orders',
select: ['id', 'customer_name', 'total', 'status', 'created_at'],
where: {
$AND: {
status: { $IN: ['pending', 'processing'] },
total: { $GTE: 100 },
created_at: { $GTE: new Date('2024-01-01') },
},
},
orderBy: [
{ column: 'status', order: 'ASC' },
{ column: 'created_at', order: 'DESC' },
],
limit: 100,
});

Next Steps

See Also