SQL query builder for Node.js supporting multiple dialects with fluent, chainable API
—
This document covers comprehensive query construction with method chaining, joins, subqueries, and advanced SQL features for building complex database queries.
The Query class provides a fluent API for building SQL statements through method chaining.
class Query {
// Core query building
select(...columns: any[]): Query;
from(...tables: any[]): Query;
where(...conditions: any[]): Query;
and(condition: any): Query;
or(condition: any): Query;
// Joins
join(table: Table): JoinQuery;
leftJoin(table: Table): JoinQuery;
// Ordering and grouping
order(...criteria: any[]): Query;
group(...columns: any[]): Query;
having(...conditions: any[]): Query;
// Limiting results
limit(count: number): Query;
offset(count: number): Query;
// Query modifiers
distinct(): Query;
distinctOn(...columns: any[]): Query;
// Data modification
insert(data: object | object[]): Query;
replace(data: object | object[]): Query;
update(data: object): Query;
delete(conditions?: any): Query;
// Advanced features
returning(...columns: any[]): Query;
onDuplicate(action: object): Query;
onConflict(action: ConflictAction): Query;
forUpdate(): Query;
forShare(): Query;
// Output methods
toQuery(): QueryResult;
toNamedQuery(name: string, dialect?: string): NamedQueryResult;
// Subquery operations (for subqueries only)
star(): Column;
exists(): BinaryExpression;
notExists(): BinaryExpression;
// Utility
parameter(value: any): Query;
}
interface QueryResult {
text: string;
values: any[];
}
interface NamedQueryResult extends QueryResult {
name: string;
}// Select all columns
const allColumnsQuery = user.select().toQuery();
console.log(allColumnsQuery.text); // SELECT "user".* FROM "user"
// Select specific columns
const specificQuery = user.select(user.id, user.name).toQuery();
console.log(specificQuery.text); // SELECT "user"."id", "user"."name" FROM "user"
// Select with aliases
const aliasQuery = user.select(
user.id,
user.name.as('full_name'),
user.email.as('contact_email')
).toQuery();// Select with computed columns
const computedQuery = user.select(
user.id,
user.name,
sql.functions.UPPER(user.name).as('upper_name'),
sql.functions.COUNT(user.id).as('user_count')
).toQuery();
// Select with constants
const constantQuery = user.select(
user.id,
user.name,
sql.constant('active').as('status')
).toQuery();// Single condition
query.where(condition: any): Query;
// Multiple conditions (AND)
query.where(condition1, condition2, ...): Query;
// Explicit AND/OR
query.and(condition: any): Query;
query.or(condition: any): Query;Usage examples:
// Simple WHERE
const simpleWhere = user
.select()
.where(user.age.gt(18))
.toQuery();
// Multiple conditions with AND
const multipleAnd = user
.select()
.where(
user.age.gt(18),
user.active.equals(true),
user.email.isNotNull()
)
.toQuery();
// Complex conditions with AND/OR
const complexWhere = user
.select()
.where(user.age.gt(18))
.and(user.active.equals(true))
.or(user.role.equals('admin'))
.toQuery();
// Grouped conditions
const groupedWhere = user
.select()
.where(
user.name.equals('John').and(user.age.gt(25))
)
.or(
user.name.equals('Jane').and(user.age.gt(30))
)
.toQuery();interface JoinQuery {
on(condition: any): Query;
using(...columns: string[]): Query;
}
// Join types
table.join(other: Table): JoinQuery; // INNER JOIN
table.leftJoin(other: Table): JoinQuery; // LEFT JOIN
table.rightJoin(other: Table): JoinQuery; // RIGHT JOIN (dialect-dependent)
table.fullJoin(other: Table): JoinQuery; // FULL JOIN (dialect-dependent)Usage examples:
// Inner join
const innerJoin = user
.select(user.name, post.title)
.from(user.join(post).on(user.id.equals(post.userId)))
.toQuery();
// Left join with multiple conditions
const leftJoin = user
.select(user.name, post.title)
.from(
user.leftJoin(post).on(
user.id.equals(post.userId).and(post.published.equals(true))
)
)
.toQuery();
// Multiple joins
const multiJoin = user
.select(user.name, post.title, category.name.as('category'))
.from(
user
.join(post).on(user.id.equals(post.userId))
.join(category).on(post.categoryId.equals(category.id))
)
.toQuery();
// Self join with aliases
const friends = user.as('friends');
const selfJoin = user
.select(user.name, friends.name.as('friend_name'))
.from(
user
.join(friendship).on(user.id.equals(friendship.userId))
.join(friends).on(friendship.friendId.equals(friends.id))
)
.toQuery();// Automatic join based on foreign key relationships
const autoJoin = user.joinTo(post);query.order(...criteria: any[]): Query;
// Order criteria can be:
// - Column with .asc or .desc
// - Column (defaults to ASC)
// - Raw SQL stringUsage examples:
// Single column ordering
const singleOrder = user
.select()
.order(user.name.asc)
.toQuery();
// Multiple column ordering
const multiOrder = user
.select()
.order(user.name.asc, user.age.desc, user.id)
.toQuery();
// Ordering with expressions
const expressionOrder = user
.select()
.order(sql.functions.LOWER(user.name).asc)
.toQuery();query.group(...columns: any[]): Query;
query.having(...conditions: any[]): Query;Usage examples:
// Group by with aggregation
const groupQuery = user
.select(user.department, sql.functions.COUNT(user.id).as('user_count'))
.group(user.department)
.toQuery();
// Group by with HAVING
const havingQuery = user
.select(user.department, sql.functions.COUNT(user.id).as('user_count'))
.group(user.department)
.having(sql.functions.COUNT(user.id).gt(5))
.toQuery();
// Multiple grouping columns
const multiGroupQuery = post
.select(
post.userId,
post.category,
sql.functions.COUNT(post.id).as('post_count')
)
.group(post.userId, post.category)
.toQuery();query.distinct(): Query;
query.distinctOn(...columns: any[]): Query; // PostgreSQL specificUsage examples:
// Distinct results
const distinctQuery = user
.select(user.department)
.distinct()
.toQuery();
// Distinct on specific columns (PostgreSQL)
const distinctOnQuery = user
.select(user.id, user.name, user.email)
.distinctOn(user.email)
.order(user.email, user.id)
.toQuery();query.limit(count: number): Query;
query.offset(count: number): Query;Usage examples:
// Pagination
const paginatedQuery = user
.select()
.order(user.id)
.limit(20)
.offset(40) // Skip first 40 records
.toQuery();
// Top N records
const topQuery = user
.select()
.order(user.created_at.desc)
.limit(10)
.toQuery();table.subQuery(alias?: string): SubQuery;
interface SubQuery {
select(...columns: any[]): SubQuery;
where(...conditions: any[]): SubQuery;
from(...tables: any[]): SubQuery;
group(...columns: any[]): SubQuery;
order(...criteria: any[]): SubQuery;
limit(count: number): SubQuery;
offset(count: number): SubQuery;
exists(): BinaryExpression;
notExists(): BinaryExpression;
star(): Column;
}Usage examples:
// Subquery in WHERE clause
const activeUsers = user.subQuery('active_users')
.select(user.id)
.where(user.active.equals(true));
const postsWithActiveUsers = post
.select()
.where(post.userId.in(activeUsers))
.toQuery();
// EXISTS subquery
const usersWithPosts = user
.select()
.where(
post.subQuery()
.select(post.id)
.where(post.userId.equals(user.id))
.exists()
)
.toQuery();
// Subquery in FROM clause
const avgAgeByDept = user.subQuery('dept_avg')
.select(
user.department,
sql.functions.AVG(user.age).as('avg_age')
)
.group(user.department);
const aboveAvgUsers = user
.select(user.name, user.age, user.department)
.from(
user.join(avgAgeByDept).on(user.department.equals(avgAgeByDept.department))
)
.where(user.age.gt(avgAgeByDept.avg_age))
.toQuery();query.returning(...columns: any[]): Query;Usage (PostgreSQL specific):
// INSERT with RETURNING
const insertReturning = user
.insert({ name: 'John', email: 'john@example.com' })
.returning(user.id, user.created_at)
.toQuery();
// UPDATE with RETURNING
const updateReturning = user
.update({ name: 'John Smith' })
.where(user.id.equals(1))
.returning(user.id, user.name, user.updated_at)
.toQuery();query.onDuplicate(action: object): Query; // MySQL specificUsage:
const onDuplicateQuery = user
.insert({ id: 1, name: 'John', email: 'john@example.com' })
.onDuplicate({ name: 'John Updated' })
.toQuery();query.onConflict(action: ConflictAction): Query; // PostgreSQL specific
interface ConflictAction {
target?: string | string[];
action?: 'NOTHING' | object;
where?: any;
}Usage:
// ON CONFLICT DO NOTHING
const conflictNothing = user
.insert({ name: 'John', email: 'john@example.com' })
.onConflict({ target: 'email', action: 'NOTHING' })
.toQuery();
// ON CONFLICT DO UPDATE
const conflictUpdate = user
.insert({ name: 'John', email: 'john@example.com' })
.onConflict({
target: 'email',
action: { name: 'John Updated' }
})
.toQuery();query.forUpdate(): Query;
query.forShare(): Query; // PostgreSQL specificUsage:
// SELECT FOR UPDATE
const forUpdateQuery = user
.select()
.where(user.id.equals(1))
.forUpdate()
.toQuery();
// SELECT FOR SHARE
const forShareQuery = user
.select()
.where(user.id.equals(1))
.forShare()
.toQuery();interface QueryResult {
text: string; // SQL query string with parameter placeholders
values: any[]; // Parameter values array
}
interface NamedQueryResult extends QueryResult {
name: string; // Query name for identification
}Usage:
// Get parameterized query
const query = user
.select()
.where(user.name.equals('John'))
.toQuery();
console.log(query.text); // SELECT "user".* FROM "user" WHERE ("user"."name" = $1)
console.log(query.values); // ['John']
// Named query
const namedQuery = user
.select()
.toNamedQuery('user.findAll');
console.log(namedQuery.name); // 'user.findAll'query.parameter(value: any): Query;Usage:
// Explicit parameter
const paramQuery = user
.select()
.where(user.created_at.gt(sql.parameter(new Date())))
.toQuery();// Define reusable conditions
const activeCondition = user.active.equals(true);
const adultCondition = user.age.gte(18);
// Compose queries
const activeAdults = user
.select()
.where(activeCondition.and(adultCondition))
.toQuery();
// Reusable joins
const userPostJoin = user.join(post).on(user.id.equals(post.userId));
const userPostsQuery = user
.select(user.name, post.title)
.from(userPostJoin)
.toQuery();// Create query builder function
function buildUserQuery(filters = {}) {
let query = user.select();
if (filters.active !== undefined) {
query = query.where(user.active.equals(filters.active));
}
if (filters.minAge) {
query = query.where(user.age.gte(filters.minAge));
}
if (filters.department) {
query = query.where(user.department.equals(filters.department));
}
return query;
}
// Use template
const adminUsers = buildUserQuery({
active: true,
minAge: 21,
department: 'admin'
}).toQuery();Install with Tessl CLI
npx tessl i tessl/npm-sql