@rabstack/rab-query-decorators
v0.8.0
Published
Query decorators for rab-api that integrate rab-query with schema validation
Readme
@rabstack/rab-query-decorators
Query decorators that seamlessly integrate @rabstack/rab-api with @rabstack/rab-query.
Provides the @Query decorator with built-in query parameter parsing, validation, and type safety.
Features
- Seamless Integration: Connects rab-api routing with rab-query parsing
- Type-Safe: Full TypeScript support with generic types
- Joi Validation: Optional schema validation for query parameters
- Zero Configuration: Works out of the box with sensible defaults
Installation
npm install @rabstack/rab-query-decorators @rabstack/rab-api @rabstack/rab-query joiQuick Start
import { Query } from '@rabstack/rab-query-decorators';
import { AtomApiGet } from '@rabstack/rab-api';
import Joi from 'joi';
@Query('/users', {
querySchema: Joi.object({
name: Joi.string().optional(),
email: Joi.string().email().optional(),
age: Joi.number().optional(),
}),
permission: 'canListUsers',
})
export class ListUsersController implements AtomApiGet {
async execute(request) {
// request.query is automatically parsed and validated
const { page, pageSize, orderBy, ...filters } = request.query;
return this.userService.findMany({
where: filters,
take: pageSize,
skip: (page - 1) * pageSize,
orderBy,
});
}
}API Reference
Query(path, options?)
Decorator for GET routes with integrated query parameter parsing.
Parameters:
path(string): The route pathoptions(ControllerRouteDefinitionOptions): Optional configurationquerySchema(Joi.ObjectSchema): Joi schema for validationpermission(string): Permission required to access this route- ... other rab-api options
Returns: ClassDecorator
buildQueryValidationSchema(baseSchema, options?)
Builds a Joi validation schema for query parameters.
Parameters:
baseSchema(Joi.ObjectSchema): Your data model schemaoptions(object): Optional configurationmaxPageSize(number): Maximum allowed page size (default: 1000)
Returns: Joi.ObjectSchema
validateQuery(query, baseSchema, options?)
Validates and transforms query parameters.
Parameters:
query(any): Raw query objectbaseSchema(Joi.ObjectSchema): Your data model schemaoptions(object): Optional configuration
Returns: { value: T, error?: Joi.ValidationError }
Usage Examples
Basic Usage (No Validation)
import { Query } from '@rabstack/rab-query-decorators';
@Query('/products')
export class ListProductsController implements AtomApiGet {
async execute(request) {
// Query parameters are automatically parsed
// URL: /products?name__contains=phone&price__gte=100
console.log(request.query);
// {
// name: { contains: 'phone', mode: 'insensitive' },
// price: { gte: 100 },
// page: 1,
// pageSize: 10
// }
return this.productService.findMany({ where: request.query });
}
}With Joi Validation
import { Query } from '@rabstack/rab-query-decorators';
import Joi from 'joi';
const userQuerySchema = Joi.object({
name: Joi.string().max(100),
email: Joi.string().email(),
age: Joi.number().min(0).max(150),
status: Joi.string().valid('active', 'inactive', 'pending'),
createdAt: Joi.date(),
});
@Query('/users', {
querySchema: userQuerySchema,
permission: 'users:read',
})
export class SearchUsersController implements AtomApiGet {
async execute(request) {
// Validated and type-safe query
return this.userService.search(request.query);
}
}Nested Object Schema
import { Query } from '@rabstack/rab-query-decorators';
import Joi from 'joi';
const orderQuerySchema = Joi.object({
customer: Joi.object({
name: Joi.string(),
email: Joi.string().email(),
}),
product: Joi.object({
name: Joi.string(),
category: Joi.string(),
price: Joi.number(),
}),
status: Joi.string().valid('pending', 'shipped', 'delivered'),
});
@Query('/orders', { querySchema: orderQuerySchema })
export class ListOrdersController implements AtomApiGet {
async execute(request) {
// URL: /orders?customer.name__contains=John&product.price__gte=100
// Parses to: { customer: { name: { contains: 'John' } }, product: { price: { gte: 100 } } }
return this.orderService.findMany({ where: request.query });
}
}Custom Page Size Limit
import { Query, buildQueryValidationSchema } from '@rabstack/rab-query-decorators';
import Joi from 'joi';
const schema = buildQueryValidationSchema(
Joi.object({
name: Joi.string(),
}),
{ maxPageSize: 50 } // Limit to 50 items per page
);
@Query('/items', { querySchema: schema })
export class ListItemsController implements AtomApiGet {
async execute(request) {
// pageSize is capped at 50
return this.itemService.findMany(request.query);
}
}Manual Validation
If you need more control, use validateQuery directly:
import { validateQuery } from '@rabstack/rab-query-decorators';
import Joi from 'joi';
const schema = Joi.object({
name: Joi.string(),
age: Joi.number(),
});
const { value, error } = validateQuery(req.query, schema);
if (error) {
throw new ValidationError(error.message);
}
// Use validated query
const results = await db.findMany({ where: value });Supported Query Features
All features from @rabstack/rab-query are automatically supported:
Operators
- Comparison:
equals,not,in,notIn,lt,lte,gt,gte - String:
contains,startsWith,endsWith,search - Array:
has,hasSome,hasEvery,isEmpty - Null:
isSet
Logical Operators
OR- Match any conditionAND- Match all conditionsNOT- Negate conditions
Pagination
page- Page number (default: 1)pageSize- Items per page (default: 10, max: 1000)
Sorting
orderBy- Sort by fields (supports nested fields)
Query String Format
# Basic operator
?name__contains=John
# Nested fields
?user.profile.age__gte=18
# Logical operators
?age__gt__or=18&age__lt__or=65
# Arrays
?status__in=active,pending,approved
# Pagination
?page=2&pageSize=20
# Sorting
?orderBy=name__asc,createdAt__descIntegration with rab-api
This package decouples @rabstack/rab-api from @rabstack/rab-query.
Before (tightly coupled):
// rab-api had direct dependency on rab-query
import { Query } from '@rabstack/rab-api';After (decoupled):
// rab-api is independent
// rab-query is independent
// query-decorators connects them
import { Query } from '@rabstack/rab-query-decorators';TypeScript Support
Full type safety with generic types:
import { RabQueryParams } from '@rabstack/rab-query-decorators';
interface User {
name: string;
email: string;
age: number;
}
@Query('/users')
export class ListUsersController implements AtomApiGet {
async execute(request: { query: RabQueryParams<User> }) {
// request.query is fully typed
return this.userService.findMany(request.query);
}
}Best Practices
- Always use querySchema: Validate user input to prevent invalid queries
- Set maxPageSize: Protect your API from excessive page sizes
- Use TypeScript: Leverage type safety for better developer experience
- Document your API: Share supported query parameters with API consumers
Dependencies
This package requires:
@rabstack/rab-api(peer dependency)@rabstack/rab-query(peer dependency)joi(peer dependency)reflect-metadata(peer dependency)
Related Packages
@rabstack/rab-api- REST API framework@rabstack/rab-query- Query string parser
License
MIT
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
