SearchOptions Interface
The SearchOptions interface defines the structure for query parameters used in search and pagination operations throughout the Mifty framework.
Interface Definition
interface SearchOptions {
page?: number;
pageSize?: number;
orderBy?: Record<string, 'asc' | 'desc'>;
where?: any;
include?: any;
}
Properties
page?: number
The page number for pagination (1-based indexing).
Type: number (optional)
Default: 1
Description: Specifies which page of results to retrieve. Pages are numbered starting from 1.
Example:
const options: SearchOptions = {
page: 3, // Get the 3rd page
pageSize: 10
};
pageSize?: number
The number of records to return per page.
Type: number (optional)
Default: 10
Description: Specifies the maximum number of records to return in a single page. Most implementations enforce a maximum limit (e.g., 100).
Example:
const options: SearchOptions = {
page: 1,
pageSize: 25 // Return up to 25 records
};
orderBy?: Record<string, 'asc' | 'desc'>
Sorting criteria for the results.
Type: Record<string, 'asc' | 'desc'> (optional)
Description: Defines how to sort the results. Keys are field names, values are sort directions.
Examples:
Single Field Sorting:
const options: SearchOptions = {
orderBy: { createdAt: 'desc' } // Sort by creation date, newest first
};
Multiple Field Sorting:
const options: SearchOptions = {
orderBy: {
status: 'asc', // First by status ascending
createdAt: 'desc' // Then by creation date descending
}
};
Nested Field Sorting:
const options: SearchOptions = {
orderBy: {
'profile.lastName': 'asc', // Sort by profile's last name
'profile.firstName': 'asc' // Then by first name
}
};
where?: any
Filter criteria for the query.
Type: any (optional)
Description: Defines the conditions that records must meet to be included in the results. The structure depends on your ORM (typically Prisma query format).
Examples:
Simple Filtering:
const options: SearchOptions = {
where: {
active: true,
role: 'user'
}
};
Complex Filtering:
const options: SearchOptions = {
where: {
OR: [
{ name: { contains: 'john', mode: 'insensitive' } },
{ email: { contains: 'john', mode: 'insensitive' } }
],
AND: [
{ active: true },
{ createdAt: { gte: new Date('2024-01-01') } }
]
}
};
Nested Filtering:
const options: SearchOptions = {
where: {
profile: {
age: { gte: 18 },
country: 'US'
}
}
};
include?: any
Related data to include in the results.
Type: any (optional)
Description: Specifies which related entities should be included in the query results. Helps avoid N+1 query problems.
Examples:
Simple Inclusion:
const options: SearchOptions = {
include: {
profile: true, // Include user's profile
posts: true // Include user's posts
}
};
Nested Inclusion:
const options: SearchOptions = {
include: {
profile: true,
posts: {
include: {
comments: {
include: {
author: true
}
}
}
}
}
};
Selective Inclusion:
const options: SearchOptions = {
include: {
profile: {
select: {
firstName: true,
lastName: true,
avatar: true
}
}
}
};
Usage Examples
Basic Pagination
import { SearchOptions } from '@mifty/core/interfaces';
const options: SearchOptions = {
page: 1,
pageSize: 10,
orderBy: { createdAt: 'desc' }
};
const users = await userService.findWithPagination(options);
Advanced Filtering and Sorting
const options: SearchOptions = {
page: 2,
pageSize: 20,
where: {
active: true,
role: { in: ['user', 'moderator'] },
createdAt: {
gte: new Date('2024-01-01'),
lte: new Date('2024-12-31')
}
},
orderBy: {
'profile.lastName': 'asc',
'profile.firstName': 'asc'
},
include: {
profile: true,
posts: {
where: { published: true },
orderBy: { createdAt: 'desc' },
take: 5
}
}
};
const result = await userService.findWithPagination(options);
Search with Text Query
const searchOptions: SearchOptions = {
page: 1,
pageSize: 15,
where: {
OR: [
{ name: { contains: searchTerm, mode: 'insensitive' } },
{ email: { contains: searchTerm, mode: 'insensitive' } },
{ profile: {
firstName: { contains: searchTerm, mode: 'insensitive' }
}}
],
active: true
},
include: { profile: true },
orderBy: { updatedAt: 'desc' }
};
const searchResults = await userService.findWithPagination(searchOptions);
API Integration
Query Parameter Parsing
import { Request } from 'express';
import { SearchOptions } from '@mifty/core/interfaces';
function parseSearchOptions(req: Request): SearchOptions {
const options: SearchOptions = {};
// Parse pagination
if (req.query.page) {
options.page = parseInt(req.query.page as string);
}
if (req.query.pageSize) {
options.pageSize = parseInt(req.query.pageSize as string);
}
// Parse JSON parameters
if (req.query.where) {
options.where = JSON.parse(req.query.where as string);
}
if (req.query.orderBy) {
options.orderBy = JSON.parse(req.query.orderBy as string);
}
if (req.query.include) {
options.include = JSON.parse(req.query.include as string);
}
return options;
}
// Usage in controller
async function getUsers(req: Request, res: Response) {
const options = parseSearchOptions(req);
const result = await userService.findWithPagination(options);
return PaginatedResponse.paginated(result.data, result.page, result.pageSize, result.total).send(res);
}
URL Examples
# Basic pagination
GET /users?page=2&pageSize=20
# With filtering
GET /users?page=1&pageSize=10&where={"active":true,"role":"user"}
# With sorting
GET /users?orderBy={"createdAt":"desc","name":"asc"}
# With includes
GET /users?include={"profile":true,"posts":true}
# Combined
GET /users?page=1&pageSize=15&where={"active":true}&orderBy={"createdAt":"desc"}&include={"profile":true}
Validation
Zod Schema Example
import { z } from 'zod';
const SearchOptionsSchema = z.object({
page: z.number().int().positive().optional(),
pageSize: z.number().int().positive().max(100).optional(),
orderBy: z.record(z.enum(['asc', 'desc'])).optional(),
where: z.record(z.any()).optional(),
include: z.record(z.any()).optional()
});
// Usage
function validateSearchOptions(options: unknown): SearchOptions {
return SearchOptionsSchema.parse(options);
}
Request Validation
import { RequestOptionBuilder } from '@mifty/core/utils';
async function getUsersEndpoint(req: Request, res: Response) {
try {
const options = RequestOptionBuilder.buildSearchOptions(req, {
paginationSchema: z.object({
page: z.number().int().positive(),
pageSize: z.number().int().positive().max(50)
}),
whereSchema: z.object({
active: z.boolean().optional(),
role: z.string().optional(),
createdAt: z.object({
gte: z.date().optional(),
lte: z.date().optional()
}).optional()
}).optional(),
orderBySchema: z.record(z.enum(['asc', 'desc'])).optional(),
includeSchema: z.object({
profile: z.boolean().optional(),
posts: z.boolean().optional()
}).optional()
});
const result = await userService.findWithPagination(options);
return PaginatedResponse.paginated(result.data, result.page, result.pageSize, result.total).send(res);
} catch (error) {
// Handle validation errors
throw new BadRequestException('Invalid search parameters');
}
}
Performance Considerations
Indexing
Ensure database indexes exist for commonly filtered and sorted fields:
-- For filtering
CREATE INDEX idx_users_active ON users(active);
CREATE INDEX idx_users_role ON users(role);
CREATE INDEX idx_users_created_at ON users(created_at);
-- For sorting
CREATE INDEX idx_users_name ON users(name);
CREATE INDEX idx_users_updated_at ON users(updated_at);
-- Composite indexes for common filter combinations
CREATE INDEX idx_users_active_role ON users(active, role);
Query Optimization
// Good: Selective includes
const options: SearchOptions = {
include: {
profile: {
select: {
firstName: true,
lastName: true,
avatar: true
}
}
}
};
// Avoid: Including too much data
const options: SearchOptions = {
include: {
profile: true,
posts: true,
comments: true,
followers: true,
following: true
}
};
Pagination Limits
const MAX_PAGE_SIZE = 100;
const DEFAULT_PAGE_SIZE = 10;
function sanitizeSearchOptions(options: SearchOptions): SearchOptions {
return {
...options,
page: Math.max(1, options.page || 1),
pageSize: Math.min(MAX_PAGE_SIZE, Math.max(1, options.pageSize || DEFAULT_PAGE_SIZE))
};
}
Best Practices
1. Default Values
Always provide sensible defaults:
function applyDefaults(options: SearchOptions): Required<SearchOptions> {
return {
page: options.page || 1,
pageSize: options.pageSize || 10,
orderBy: options.orderBy || { createdAt: 'desc' },
where: options.where || {},
include: options.include || {}
};
}
2. Type Safety
Use typed interfaces for specific entities:
interface UserSearchOptions extends SearchOptions {
where?: {
active?: boolean;
role?: string;
email?: { contains: string; mode: 'insensitive' };
createdAt?: { gte?: Date; lte?: Date };
};
include?: {
profile?: boolean;
posts?: boolean;
};
}
3. Validation
Always validate search options:
function validateSearchOptions(options: SearchOptions): void {
if (options.page && options.page < 1) {
throw new ValidationException('Page must be greater than 0');
}
if (options.pageSize && (options.pageSize < 1 || options.pageSize > 100)) {
throw new ValidationException('Page size must be between 1 and 100');
}
}
Related
- PaginatedResult - Result structure for paginated queries
- BaseRepository - Repository implementation using SearchOptions
- BaseService - Service layer using SearchOptions
- RequestOptionBuilder - Utility for parsing query parameters