orderBy is a method for sorting query results by specific columns. You can sort in ascending (ASC) or descending (DESC) order, and can sort by multiple columns.
Basic Usage
Ascending (ASC)
const users = await puri . table ( "users" )
. select ({ id: "users.id" , name: "users.name" })
. orderBy ( "users.name" , "asc" );
// ORDER BY users.name ASC
Descending (DESC)
const posts = await puri . table ( "posts" )
. select ({ id: "posts.id" , title: "posts.title" , created_at: "posts.created_at" })
. orderBy ( "posts.created_at" , "desc" );
// ORDER BY posts.created_at DESC
Multiple Column Sorting
You can sort by multiple columns by chaining orderBy.
const users = await puri . table ( "users" )
. select ({ id: "users.id" , role: "users.role" , name: "users.name" })
. orderBy ( "users.role" , "asc" ) // First: role ascending
. orderBy ( "users.name" , "asc" ); // Second: name ascending
// ORDER BY users.role ASC, users.name ASC
Sort Priority:
First orderBy - Primary sort
Second orderBy - Secondary sort (when primary is same)
Third orderBy - Tertiary sort (when secondary is also same)
Practical Examples
Latest First
Alphabetical
Complex Sorting
Numeric Sorting
NULL Handling
// Latest posts first
const posts = await puri . table ( "posts" )
. select ({
id: "posts.id" ,
title: "posts.title" ,
created_at: "posts.created_at"
})
. orderBy ( "posts.created_at" , "desc" )
. limit ( 20 );
// Most recently updated first
const users = await puri . table ( "users" )
. select ({
id: "users.id" ,
name: "users.name" ,
updated_at: "users.updated_at"
})
. orderBy ( "users.updated_at" , "desc" );
// Names in alphabetical order
const users = await puri . table ( "users" )
. select ({
id: "users.id" ,
name: "users.name"
})
. orderBy ( "users.name" , "asc" );
// Titles in reverse alphabetical order
const products = await puri . table ( "products" )
. select ({
id: "products.id" ,
name: "products.name"
})
. orderBy ( "products.name" , "desc" );
// Priority: status → created date
const orders = await puri . table ( "orders" )
. select ({
id: "orders.id" ,
status: "orders.status" ,
created_at: "orders.created_at"
})
. orderBy ( "orders.status" , "asc" ) // pending → processing → completed
. orderBy ( "orders.created_at" , "desc" ); // Latest first
// Priority: featured → rating → sales
const products = await puri . table ( "products" )
. select ({
id: "products.id" ,
name: "products.name" ,
featured: "products.featured" ,
rating: "products.rating" ,
sales: "products.sales"
})
. orderBy ( "products.featured" , "desc" ) // Featured first
. orderBy ( "products.rating" , "desc" ) // High ratings
. orderBy ( "products.sales" , "desc" ); // High sales
// Lowest price first
const products = await puri . table ( "products" )
. select ({
id: "products.id" ,
name: "products.name" ,
price: "products.price"
})
. where ( "products.published" , true )
. orderBy ( "products.price" , "asc" );
// Highest views first
const posts = await puri . table ( "posts" )
. select ({
id: "posts.id" ,
title: "posts.title" ,
views: "posts.views"
})
. orderBy ( "posts.views" , "desc" )
. limit ( 10 );
// NULL at the end
const users = await puri . table ( "users" )
. select ({
id: "users.id" ,
name: "users.name" ,
last_login: "users.last_login"
})
. orderBy ( "users.last_login" , "desc" ); // NULL automatically at the end
// NULL first
const posts = await puri . table ( "posts" )
. select ({
id: "posts.id" ,
title: "posts.title" ,
deleted_at: "posts.deleted_at"
})
. orderBy ( "posts.deleted_at" , "asc" ); // NULL comes first
Sorting Joined Tables
You can also sort by columns from joined tables.
const posts = await puri . table ( "posts" )
. join ( "users" , "posts.user_id" , "users.id" )
. select ({
post_id: "posts.id" ,
title: "posts.title" ,
author_name: "users.name"
})
. orderBy ( "users.name" , "asc" ) // By author name
. orderBy ( "posts.created_at" , "desc" ); // Latest first for same author
// ORDER BY users.name ASC, posts.created_at DESC
Sorting by SELECT Alias
You can also sort by aliases specified in SELECT.
const posts = await puri . table ( "posts" )
. select ({
id: "posts.id" ,
title: "posts.title" ,
view_count: "posts.views" // Alias specified
})
. orderBy ( "view_count" , "desc" ); // Using alias
// SELECT posts.views as view_count
// ORDER BY view_count DESC
Sorting Aggregate Functions
You can sort by aggregate function results with GROUP BY.
const userStats = await puri . table ( "posts" )
. select ({
user_id: "posts.user_id" ,
post_count: Puri . count ()
})
. groupBy ( "posts.user_id" )
. orderBy ( "post_count" , "desc" ) // Most posts first
. limit ( 10 );
// SELECT user_id, COUNT(*) as post_count
// FROM posts
// GROUP BY user_id
// ORDER BY post_count DESC
// LIMIT 10
Sort Directions
ASC (Ascending)
Numbers: small → large
Strings: A → Z
Dates: past → future
NULL: last
. orderBy ( "users.age" , "asc" )
// 18, 25, 30, 35, 40, NULL
DESC (Descending)
Numbers: large → small
Strings: Z → A
Dates: future → past
NULL: last
. orderBy ( "users.age" , "desc" )
// 40, 35, 30, 25, 18, NULL
Default is ASC . If direction is not specified, it sorts in ascending order.
1. Use Indexes
// ✅ Good: Sort by indexed column
. orderBy ( "users.created_at" , "desc" ) // Index needed on created_at
// ❌ Bad: Sort by non-indexed column (slow)
. orderBy ( "users.bio" , "asc" )
Recommended Indexes:
CREATE INDEX idx_users_created_at ON users(created_at DESC );
CREATE INDEX idx_posts_published_created ON posts(published, created_at DESC );
2. Use with LIMIT
// ✅ Good: Sort only top N
. orderBy ( "posts.views" , "desc" )
. limit ( 10 ) // Only top 10 instead of sorting all
// ⚠️ Caution: Without LIMIT, sorts all rows (slow)
. orderBy ( "posts.views" , "desc" )
3. Composite Indexes
When sorting by multiple columns, composite indexes are needed.
// For this query
. orderBy ( "products.category" , "asc" )
. orderBy ( "products.price" , "asc" )
// This index is needed
CREATE INDEX idx_products_category_price ON products ( category , price );
Cautions
1. Sort Order Matters
// Order of orderBy matters
// ✅ Correct: status → date
. orderBy ( "orders.status" , "asc" )
. orderBy ( "orders.created_at" , "desc" )
// ❌ Different from intention: date → status
. orderBy ( "orders.created_at" , "desc" )
. orderBy ( "orders.status" , "asc" )
2. NULL Values
// NULL sorting may differ between PostgreSQL and MySQL
// PostgreSQL: NULL LAST (default)
. orderBy ( "users.last_login" , "desc" )
// To specify explicitly, use raw SQL
. whereRaw ( "ORDER BY users.last_login DESC NULLS FIRST" )
3. Case Sensitivity
// ⚠️ Caution: Case-sensitive sorting
. orderBy ( "users.name" , "asc" )
// "Alice", "Bob", "alice", "bob"
// To ignore case
. orderBy ( puri . raw ( "LOWER(users.name)" ), "asc" )
// "Alice", "alice", "Bob", "bob"
// ❌ Bad: Sort entire large dataset
const allUsers = await puri . table ( "users" )
. orderBy ( "users.created_at" , "desc" ); // Sort millions of rows
// ✅ Good: Limit with LIMIT
const recentUsers = await puri . table ( "users" )
. orderBy ( "users.created_at" , "desc" )
. limit ( 100 ); // Only top 100
Type Safety
orderBy validates columns in a type-safe manner.
// ✅ Valid column
await puri . table ( "users" )
. orderBy ( "users.created_at" , "desc" );
// ❌ Type error: Non-existent column
await puri . table ( "users" )
. orderBy ( "users.unknown_field" , "desc" );
// ❌ Type error: Invalid direction
await puri . table ( "users" )
. orderBy ( "users.name" , "invalid" ); // Only "asc" or "desc" allowed
Next Steps