🔍 Validators
Validators are functions that perform additional validation beyond schema validation. They run before your route handlers and can check business logic, resource existence, authentication, and more.
🔍 What are Validators?
Validators are predicate functions that run in the request pipeline before handlers. They can:
- Validate business logic (e.g., "does this resource exist?")
- Check authentication (e.g., "is the user authorized?")
- Validate parameters (e.g., "is this a valid UUID?")
- Enforce constraints (e.g., "does the user have permission?")
┌─────────────┐
│ Request │
├─────────────┤
│ Schema │ ← JSON Schema validation
│ Validators │ ← Custom business logic
│ Handler │ ← Your route logic
└─────────────┘
Key characteristics:
- Async functions - Can perform database queries
- Throw on failure - Use
NodeblocksError
for consistent error responses - Composable - Chain multiple validators together
- Context-aware - Access database, request params, and context
📋 Available Validators
Generic Validators
Important: Legacy parameter validators
The following parameter validators currently use a legacy interface and will be deprecated and removed later:
requireParam
isUUID
isNumber
They do not accept the standard
RouteHandlerPayload
. You must extractpayload.params.requestParams
and pass it manually via a small wrapper:// Wrapper pattern for legacy parameter validators
({ params: { requestParams } }) => requireParam('userId')(requestParams)
({ params: { requestParams } }) => isUUID('userId')(requestParams)
({ params: { requestParams } }) => isNumber('limit')(requestParams)Because of this legacy interface, these validators cannot be passed directly to composition helpers like
compose()
orsome()
. Use standard validators or custom validators with the normal payload signature in those helpers.
requireParam(key: string)
Ensures a required parameter is present and not null.
import { validators } from '@nodeblocks/backend-sdk';
const { requireParam } = validators;
const getUserRoute = withRoute({
method: 'GET',
path: '/users/:userId',
validators: [({params: { requestParams }}) => requireParam('userId')(requestParams)],
handler: getUserHandler,
});
Behavior:
- ✅ Passes if parameter exists and is not null
- ❌ Throws error if parameter is missing or null
- Error:
Missing required parameter: userId
isUUID(key: string)
Validates that a parameter follows valid UUID v4 format.
import { validators } from '@nodeblocks/backend-sdk';
const { isUUID } = validators;
const getCategoryRoute = withRoute({
method: 'GET',
path: '/categories/:categoryId',
validators: [({params: { requestParams }}) => isUUID('categoryId')(requestParams)],
handler: getCategoryHandler,
});
Behavior:
- ✅ Passes if parameter is valid UUID v4 format
- ❌ Throws error if parameter exists but is invalid UUID
- Error:
Invalid UUID format for parameter: categoryId
- Format:
xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx
isNumber(key: string)
Validates that a parameter can be converted to a valid number.
import { validators } from '@nodeblocks/backend-sdk';
const { isNumber } = validators;
const getProductsRoute = withRoute({
method: 'GET',
path: '/products',
validators: [({params: { requestParams }}) => isNumber('limit')(requestParams)],
handler: getProductsHandler,
});
Behavior:
- ✅ Passes if parameter can be converted to number
- ❌ Throws error if parameter exists but cannot be parsed as number
- Error:
Parameter limit must be a number
some(...validators: Validator[])
Runs multiple validators and passes if at least one succeeds. Useful for conditional validation scenarios.
import { validators } from '@nodeblocks/backend-sdk';
const { some } = validators;
const flexibleUserRoute = withRoute({
method: 'GET',
path: '/users/:identifier',
validators: [
// Use standard validators here. Legacy parameter validators like
// requireParam/isUUID/isNumber cannot be used directly with `some()`.
some(isAuthenticated(), isSelf(['params', 'identifier']))
],
handler: getUserHandler,
});
Behavior:
- ✅ Passes if at least one validator succeeds
- ❌ Throws error if all validators fail
- Use case: Alternative validation paths (e.g., user can be identified by ID OR email)
isAuthenticated
Validates that the user is authenticated using the default authentication function.
import { validators } from '@nodeblocks/backend-sdk';
const { isAuthenticated } = validators;
const protectedRoute = withRoute({
method: 'GET',
path: '/protected',
validators: [isAuthenticated()],
handler: protectedHandler,
});
Behavior:
- ✅ Passes if user is authenticated
- ❌ Throws
NodeblocksError
(401) if authentication fails - Default: Uses
getBearerTokenInfo
for authentication
isSelf
Validates that the authenticated user is accessing their own identity resource.
import { validators } from '@nodeblocks/backend-sdk';
const { isSelf } = validators;
const userProfileRoute = withRoute({
method: 'GET',
path: '/users/:userId',
validators: [
isSelf(['params', 'userId'])
],
handler: getUserProfileHandler,
});
Parameters:
identityIdPathInPayload
- Path array to extract identity ID from request payload
Behavior:
- ✅ Passes if user is accessing their own resource
- ❌ Throws
NodeblocksError
(403) if identity ID doesn't match authenticated user
Resource Ownership Validators
ownsChannel
Validates that the authenticated user owns the specified chat channel.
import { validators } from '@nodeblocks/backend-sdk';
const { ownsChannel } = validators;
const channelRoute = withRoute({
method: 'PATCH',
path: '/channels/:channelId',
validators: [
ownsChannel(['params', 'channelId'])
],
handler: updateChannelHandler,
});
Parameters:
resourceIdPathInPayload
- Path array to extract channel ID from request payload
Behavior:
- ✅ Passes if user is the channel owner
- ❌ Throws
NodeblocksError
(401) for invalid tokens - ❌ Throws
NodeblocksError
(403) for unauthorized access - ❌ Throws
NodeblocksError
(404) for channel not found
ownsMessage
Validates that the authenticated user owns the specified chat message.
import { validators } from '@nodeblocks/backend-sdk';
const { ownsMessage } = validators;
const messageRoute = withRoute({
method: 'DELETE',
path: '/messages/:messageId',
validators: [
ownsMessage(['params', 'messageId'])
],
handler: deleteMessageHandler,
});
Parameters:
resourceIdPathInPayload
- Path array to extract message ID from request payload
Behavior:
- ✅ Passes if user is the message sender
- ❌ Throws
NodeblocksError
(401) for invalid tokens - ❌ Throws
NodeblocksError
(403) for unauthorized access - ❌ Throws
NodeblocksError
(404) for message not found
ownsOrder
Validates that the authenticated user owns the specified order.
import { validators } from '@nodeblocks/backend-sdk';
const { ownsOrder } = validators;
const orderRoute = withRoute({
method: 'GET',
path: '/orders/:orderId',
validators: [
ownsOrder(['params', 'orderId'])
],
handler: getOrderHandler,
});
Parameters:
resourceIdPathInPayload
- Path array to extract order ID from request payload
Behavior:
- ✅ Passes if user is the order owner
- ❌ Throws
NodeblocksError
(401) for invalid tokens - ❌ Throws
NodeblocksError
(403) for unauthorized access - ❌ Throws
NodeblocksError
(404) for order not found
ownsProfile
Validates that the authenticated user owns the specified user profile.
import { validators } from '@nodeblocks/backend-sdk';
const { ownsProfile } = validators;
const profileRoute = withRoute({
method: 'PATCH',
path: '/profiles/:profileId',
validators: [
ownsProfile(['params', 'profileId'])
],
handler: updateProfileHandler,
});
Parameters:
resourceIdPathInPayload
- Path array to extract profile ID from request payload
Behavior:
- ✅ Passes if user is the profile owner
- ❌ Throws
NodeblocksError
(401) for invalid tokens - ❌ Throws
NodeblocksError
(403) for unauthorized access - ❌ Throws
NodeblocksError
(404) for profile not found
ownsSubscription
Validates that the authenticated user owns the specified subscription.
import { validators } from '@nodeblocks/backend-sdk';
const { ownsSubscription } = validators;
const subscriptionRoute = withRoute({
method: 'DELETE',
path: '/subscriptions/:subscriptionId',
validators: [
ownsSubscription(['params', 'subscriptionId'])
],
handler: deleteSubscriptionHandler,
});
Parameters:
resourceIdPathInPayload
- Path array to extract subscription ID from request payload
Behavior:
- ✅ Passes if user is the subscription owner
- ❌ Throws
NodeblocksError
(401) for invalid tokens - ❌ Throws
NodeblocksError
(403) for unauthorized access - ❌ Throws
NodeblocksError
(404) for subscription not found
ownsResource
Generic validator for checking ownership of any resource type.
import { validators } from '@nodeblocks/backend-sdk';
const { ownsResource } = validators;
// Validate custom resource ownership
const ownsDocument = ownsResource('documents', ['createdBy']);
const documentRoute = withRoute({
method: 'DELETE',
path: '/documents/:documentId',
validators: [
ownsDocument(['params', 'documentId'])
],
handler: deleteDocumentHandler,
});
Parameters:
resource
- Database collection nameownerIdPathInResource
- Path to owner ID field in the resource documentresourceIdPathInPayload
- Path to resource ID in request payload
Behavior:
- ✅ Passes if user owns the specified resource
- ❌ Throws
NodeblocksError
(401) for invalid tokens - ❌ Throws
NodeblocksError
(403) for unauthorized access - ❌ Throws
NodeblocksError
(404) for resource not found
Authentication Validators
verifyAuthentication
Higher-order authentication validator for protecting routes.
import { validators } from '@nodeblocks/backend-sdk';
const { verifyAuthentication } = validators;
const getSecretRoute = withRoute({
method: 'GET',
path: '/secret',
validators: [verifyAuthentication(authenticateFunction)],
handler: getSecretHandler,
});
Behavior:
- ✅ Passes if authentication succeeds
- ❌ Throws error if authentication fails
- Usage: Requires authentication function to be provided
Authentication Functions
Authentication functions are functions that validate tokens and return token information. They're used by validators to check if requests are properly authenticated.
getBearerTokenInfo
(Default)
The default authentication function that validates bearer tokens from the Authorization header.
import { validators, utils } from '@nodeblocks/backend-sdk';
const { validateResourceAccess } = validators;
const { getBearerTokenInfo } = utils;
// Default usage (getBearerTokenInfo is used automatically)
const protectedRoute = withRoute({
method: 'GET',
path: '/protected',
validators: [
validateResourceAccess(['admin']) // Uses getBearerTokenInfo by default
],
handler: protectedHandler,
});
// Explicit usage
const explicitRoute = withRoute({
method: 'GET',
path: '/protected',
validators: [
validateResourceAccess(['admin'], getBearerTokenInfo)
],
handler: protectedHandler,
});
How it works:
- Extracts token from
Authorization: Bearer <token>
header - Decrypts and verifies the JWT token signature
- Validates token type (user access token or app access token)
- Performs security checks for user tokens (fingerprint, IP, user agent)
- Returns token information for authorization decisions
Supported token types:
- User Access Tokens: Contains
userId
, requires security validation - App Access Tokens: Contains
appId
, bypasses security checks
getCookieTokenInfo
Alternative authentication function for cookie-based tokens.
import { validators } from '@nodeblocks/backend-sdk';
const { getCookieTokenInfo, validateResourceAccess } = validators;
const cookieRoute = withRoute({
method: 'GET',
path: '/cookie-protected',
validators: [
validateResourceAccess(['admin'], getCookieTokenInfo)
],
handler: protectedHandler,
});
Use cases:
- Web applications with cookie-based authentication
- Different security validation rules (IP checks disabled)
- Server-side session management
Custom Authentication Functions
You can create custom authentication functions for specific use cases:
import { primitives, validators } from '@nodeblocks/backend-sdk';
const { Authenticator, RouteHandlerPayload, TokenInfo, NodeblocksError } = primitives;
const { validateResourceAccess } = validators;
const customAuth: Authenticator = async (payload: RouteHandlerPayload): Promise<TokenInfo> => {
const { context } = payload;
const { request } = context;
// Custom token extraction logic
const customToken = request.headers['x-custom-token'];
if (!customToken) {
throw new NodeblocksError(401, 'Custom token required');
}
// Custom validation logic
// ... your validation code here
// Return token information
return {
accessType: 'user',
userId: 'user-123',
type: 'access'
};
};
// Usage with custom authentication
const customRoute = withRoute({
method: 'GET',
path: '/custom-protected',
validators: [
validateResourceAccess(['admin'], customAuth)
],
handler: protectedHandler,
});
Authentication function signature:
type Authenticator = (payload: RouteHandlerPayload) => Promise<TokenInfo>;
Identity & Organization Validators
checkIdentityType
Validates that the authenticated user's identity type matches allowed types for resource access.
import { validators } from '@nodeblocks/backend-sdk';
const { checkIdentityType } = validators;
const adminRoute = withRoute({
method: 'GET',
path: '/admin/users',
validators: [
checkIdentityType(['admin', 'moderator'])
],
handler: getUsersHandler,
});
Parameters:
allowedTypes
- Array of allowed identity type keys (e.g., ['admin', 'user'])
Behavior:
- ✅ Passes if user identity type is in allowed types
- ❌ Throws
NodeblocksError
(401) for invalid tokens - ❌ Throws
NodeblocksError
(403) for unauthorized identity type - ❌ Throws
NodeblocksError
(500) for missing configuration
hasOrgRole
Validates organization member role for access control based on allowed roles and organization ID.
import { validators } from '@nodeblocks/backend-sdk';
const { hasOrgRole } = validators;
const orgAdminRoute = withRoute({
method: 'POST',
path: '/organizations/:organizationId/members',
validators: [
hasOrgRole(['admin', 'owner'], ['params', 'organizationId'])
],
handler: addOrganizationMemberHandler,
});
Parameters:
allowedRoles
- Array of allowed organization role stringsorganizationIdPathInPayload
- Path array to extract organization ID from request payload
Behavior:
- ✅ Passes if user has appropriate role in organization
- ❌ Throws
NodeblocksError
(401) for invalid tokens - ❌ Throws
NodeblocksError
(403) for unauthorized access - ❌ Throws
NodeblocksError
(404) for organization not found
hasSubscription
Validates that the identity is subscribed to the specified channel.
import { validators } from '@nodeblocks/backend-sdk';
const { hasSubscription } = validators;
const channelMessageRoute = withRoute({
method: 'POST',
path: '/channels/:channelId/messages',
validators: [
hasSubscription(['params', 'channelId'])
],
handler: sendMessageHandler,
});
Parameters:
channelIdPathInPayload
- Path array to extract channel ID from request payloadsubscribedIdPathInPayload
- Optional path array to extract subscribed identity ID
Behavior:
- ✅ Passes if user is subscribed to the channel
- ❌ Throws
NodeblocksError
(401) for invalid tokens - ❌ Throws
NodeblocksError
(403) for not subscribed - ❌ Throws
NodeblocksError
(500) for database errors
hasOrganizationAccessToMessageTemplate
Validates organization member access to chat message templates based on allowed roles.
import { validators } from '@nodeblocks/backend-sdk';
const { hasOrganizationAccessToMessageTemplate } = validators;
const templateRoute = withRoute({
method: 'PATCH',
path: '/message-templates/:messageTemplateId',
validators: [
hasOrganizationAccessToMessageTemplate(['admin'], ['params', 'messageTemplateId'])
],
handler: updateMessageTemplateHandler,
});
Parameters:
allowedRoles
- Array of allowed organization role stringsmessageTemplateIdPathInPayload
- Path array to extract message template ID
Behavior:
- ✅ Passes if user has appropriate role in template's organization
- ❌ Throws
NodeblocksError
(401) for invalid tokens - ❌ Throws
NodeblocksError
(403) for unauthorized access - ❌ Throws
NodeblocksError
(404) for template/organization not found
getSubscriptionByChannelAndSubscriber
Helper function that retrieves a subscription document by channel and subscriber from the database.
import { validators } from '@nodeblocks/backend-sdk';
const { getSubscriptionByChannelAndSubscriber } = validators;
// Direct usage for complex validation logic
const result = await getSubscriptionByChannelAndSubscriber(
db.subscriptions,
'channel123',
'user456'
);
if (result.isErr()) {
throw new NodeblocksError(500, 'Database error');
}
if (!result.value) {
throw new NodeblocksError(403, 'Not subscribed to channel');
}
Parameters:
collection
- MongoDB collection for subscriptionschannelId
- Channel ID to search forsubscribedId
- Subscriber identity ID
Returns:
ResultAsync<WithId<Document> | null, unknown>
- Subscription document or error
Domain-Specific Validators
Category Service
doesCategoryExist
Validates that a category exists in the database before performing operations.
import { validators } from '@nodeblocks/backend-sdk';
const { doesCategoryExist } = validators;
const updateCategoryRoute = withRoute({
method: 'PATCH',
path: '/categories/:categoryId',
validators: [doesCategoryExist],
handler: updateCategoryHandler,
});
Behavior:
- ✅ Passes if category exists in database
- ❌ Throws
NodeblocksError
(404) if category not found - Error:
Category does not exist
(404 status)
Access Control Validators
validateResourceAccess
Validates resource access based on allowed subjects and token information. Implements role-based access control for protected resources.
import { validators } from '@nodeblocks/backend-sdk';
const { validateResourceAccess } = validators;
const adminRoute = withRoute({
method: 'GET',
path: '/admin/users',
validators: [
validateResourceAccess(['admin'])
],
handler: getUsersHandler,
});
const selfAccessRoute = withRoute({
method: 'GET',
path: '/users/:userId',
validators: [
validateResourceAccess(['self', 'admin'])
],
handler: getUserHandler,
});
Configuration:
{
allowedSubjects: string[] // Array of allowed user types/subjects
}
Supported Subjects:
'admin'
- Administrator access'user'
- Regular user access'guest'
- Guest access'self'
- User can only access their own resources
Behavior:
- ✅ Passes if user has appropriate permissions
- ❌ Throws
NodeblocksError
(401) for invalid tokens - ❌ Throws
NodeblocksError
(403) for unauthorized access - Errors:
App token is not valid
(401)User token is not valid
(401)Identity not found
(401)User is not authorized to access this resource
(403)
validateChannelAccess
Validates channel access based on ownership and token information.
import { validators } from '@nodeblocks/backend-sdk';
const { validateChannelAccess } = validators;
const channelRoute = withRoute({
method: 'PATCH',
path: '/channels/:channelId',
validators: [
validateChannelAccess(['owner'])
],
handler: updateChannelHandler,
});
Supported Subjects:
'owner'
- Channel owner access
Behavior:
- ✅ Passes if user is channel owner
- ❌ Throws
NodeblocksError
(401) for invalid tokens - ❌ Throws
NodeblocksError
(403) for unauthorized access - ❌ Throws
NodeblocksError
(404) for channel not found - Errors:
App token is not valid
(401)User token is not valid
(401)Channel not found
(404)Channel has no owner
(403)User is not authorized to access this channel
(403)
validateMessageAccess
Validates message access based on ownership and token information.
import { validators } from '@nodeblocks/backend-sdk';
const { validateMessageAccess } = validators;
const messageRoute = withRoute({
method: 'DELETE',
path: '/messages/:messageId',
validators: [
validateMessageAccess(['owner'])
],
handler: deleteMessageHandler,
});
Supported Subjects:
'owner'
- Message sender access
Behavior:
- ✅ Passes if user is message sender
- ❌ Throws
NodeblocksError
(401) for invalid tokens - ❌ Throws
NodeblocksError
(403) for unauthorized access - ❌ Throws
NodeblocksError
(404) for message not found - Errors:
App token is not valid
(401)User token is not valid
(401)Message not found
(404)Message has no sender
(403)User is not authorized to access this message
(403)
validateOrderAccess
Validates order access based on ownership and token information.
import { validators } from '@nodeblocks/backend-sdk';
const { validateOrderAccess } = validators;
const orderRoute = withRoute({
method: 'GET',
path: '/orders/:orderId',
validators: [
validateOrderAccess(['owner'])
],
handler: getOrderHandler,
});
Supported Subjects:
'owner'
- Order owner access
Behavior:
- ✅ Passes if user is order owner
- ❌ Throws
NodeblocksError
(401) for invalid tokens - ❌ Throws
NodeblocksError
(403) for unauthorized access - ❌ Throws
NodeblocksError
(404) for order not found - Errors:
App token is not valid
(401)User token is not valid
(401)Order not found
(404)Order has no user
(403)User is not authorized to access this order
(403)
validateUserProfileAccess
Validates user profile access based on user type and ownership.
import { validators } from '@nodeblocks/backend-sdk';
const { validateUserProfileAccess } = validators;
const profileRoute = withRoute({
method: 'GET',
path: '/users/:userId/profile',
validators: [
validateUserProfileAccess(['self', 'admin'])
],
handler: getUserProfileHandler,
});
Supported Subjects:
'admin'
- Administrator access'user'
- Regular user access'guest'
- Guest access'self'
- User can access their own profile
Behavior:
- ✅ Passes if user has appropriate permissions
- ❌ Throws
NodeblocksError
(401) for invalid tokens - ❌ Throws
NodeblocksError
(403) for unauthorized access - ❌ Throws
NodeblocksError
(404) for user profile not found - Errors:
App token is not valid
(401)User token is not valid
(401)Identity not found
(401)User profile not found
(404)Identity is not authorized to access this resource
(403)
validateOrganizationAccess
Validates organization access based on membership and role.
import { validators } from '@nodeblocks/backend-sdk';
const { validateOrganizationAccess } = validators;
const orgRoute = withRoute({
method: 'GET',
path: '/organizations/:organizationId',
validators: [
validateOrganizationAccess(['admin', 'member'])
],
handler: getOrganizationHandler,
});
Supported Subjects:
'admin'
- Organization administrator'member'
- Organization member'owner'
- Organization owner
Behavior:
- ✅ Passes if user is organization member with appropriate role
- ❌ Throws
NodeblocksError
(401) for invalid tokens - ❌ Throws
NodeblocksError
(403) for unauthorized access - ❌ Throws
NodeblocksError
(404) for organization not found - Errors:
App token is not valid
(401)User token is not valid
(401)Organization not found
(404)Organization has no users
(403)User does not belong to this organization
(403)User is not authorized to access this organization
(403)
🛠️ Creating Custom Validators
Validator Function Signature
type Validator = (payload: RouteHandlerPayload) => Promise<void>;
Basic Validator Example
import { primitives } from '@nodeblocks/backend-sdk';
const { NodeblocksError } = primitives;
const validateUserExists = async (payload: RouteHandlerPayload) => {
const { context, params } = payload;
const userId = params.requestParams?.userId;
const user = await context.db.users.findOne({ id: userId });
if (!user) {
throw new NodeblocksError(404, 'User not found', 'USER_NOT_FOUND');
}
};
Advanced Validator Example
import { primitives } from '@nodeblocks/backend-sdk';
const { NodeblocksError, RouteHandlerPayload } = primitives;
const validateUserPermission = async (payload: RouteHandlerPayload) => {
const { context, params } = payload;
const { userId, organizationId } = params.requestParams;
// Check if user exists
const user = await context.db.users.findOne({ id: userId });
if (!user) {
throw new NodeblocksError(404, 'User not found', 'USER_NOT_FOUND');
}
// Check if user belongs to organization
const membership = await context.db.memberships.findOne({
userId,
organizationId,
status: 'active'
});
if (!membership) {
throw new NodeblocksError(403, 'Access denied', 'ACCESS_DENIED');
}
};
Factory Function Pattern
Create reusable validator factories:
const requireResourceExists = (collectionName: string, paramName: string) => {
return async (payload: RouteHandlerPayload) => {
const { context, params } = payload;
const resourceId = params.requestParams?.[paramName];
const resource = await context.db[collectionName].findOne({ id: resourceId });
if (!resource) {
throw new NodeblocksError(
404,
`${collectionName} not found`,
'RESOURCE_NOT_FOUND'
);
}
};
};
// Usage
const validateProductExists = requireResourceExists('products', 'productId');
const validateOrderExists = requireResourceExists('orders', 'orderId');
🔗 Using Validators in Routes
Single Validator
export const getCategoryRoute = withRoute({
method: 'GET',
path: '/categories/:categoryId',
validators: [doesCategoryExist],
handler: getCategoryHandler,
});
Multiple Validators
export const updateUserRoute = withRoute({
method: 'PATCH',
path: '/users/:userId',
validators: [
({params: { requestParams }}) => requireParam('userId')(requestParams),
({params: { requestParams }}) => isUUID('userId')(requestParams),
validateUserExists,
verifyAuthentication(authenticate)
],
handler: updateUserHandler,
});
Authentication + Authorization
export const protectedUserRoute = withRoute({
method: 'PATCH',
path: '/users/:userId',
validators: [
({params: { requestParams }}) => requireParam('userId')(requestParams),
({params: { requestParams }}) => isUUID('userId')(requestParams),
verifyAuthentication(authenticate),
validateResourceAccess(['admin', 'self'])
],
handler: updateUserHandler,
});
Conditional Validators
const validators = [
({params: { requestParams }}) => requireParam('userId')(requestParams),
({params: { requestParams }}) => isUUID('userId')(requestParams),
// Only check authentication for non-public routes
...(isPublicRoute ? [] : [verifyAuthentication(authenticate)])
];
export const getUserRoute = withRoute({
method: 'GET',
path: '/users/:userId',
validators,
handler: getUserHandler,
});
Alternative Validation Paths
export const flexibleUserRoute = withRoute({
method: 'GET',
path: '/users/:identifier',
validators: [
// Use standard validators here. Legacy parameter validators like
// requireParam/isUUID/isNumber cannot be used directly with `some()`.
some(validateHasUserId, validateHasEmail),
validateUserExists
],
handler: getUserHandler,
});
🚨 Error Handling
Using NodeblocksError
Always use NodeblocksError
for consistent error responses:
import { primitives } from '@nodeblocks/backend-sdk';
const { NodeblocksError, RouteHandlerPayload } = primitives;
const validateResource = async (payload: RouteHandlerPayload) => {
// Your validation logic
if (!isValid) {
throw new NodeblocksError(
400, // HTTP status code
'Validation failed', // Human-readable message
'VALIDATION_ERROR' // Error code for client handling
);
}
};
Error Response Format
When validators throw errors, clients receive:
{
"error": {
"message": "Category does not exist"
}
}
📋 Validator Reference
Parameter Validators (Legacy)
Validator | Purpose | Parameters | Returns | Throws |
---|---|---|---|---|
requireParam | Check parameter presence | key: string | Function that validates request params | Error if null/missing |
isUUID | Validate UUID format | key: string | Function that validates request params | Error if invalid UUID |
isNumber | Validate numeric format | key: string | Function that validates request params | Error if not numeric |
Modern Validators
Validator | Purpose | Parameters | Returns | Throws |
---|---|---|---|---|
some | Alternative validation paths | ...validators: Validator[] | Validator | Error if all fail |
isAuthenticated | Check user authentication | none | Validator | NodeblocksError (401) |
isSelf | Check self-access to resources | identityIdPathInPayload: [T, ...T[]] | Validator | NodeblocksError (401/403) |
Resource Ownership Validators
Validator | Purpose | Parameters | Returns | Throws |
---|---|---|---|---|
ownsChannel | Check channel ownership | resourceIdPathInPayload: [T, ...T[]] | Validator | NodeblocksError (401/403/404) |
ownsMessage | Check message ownership | resourceIdPathInPayload: [T, ...T[]] | Validator | NodeblocksError (401/403/404) |
ownsOrder | Check order ownership | resourceIdPathInPayload: [T, ...T[]] | Validator | NodeblocksError (401/403/404) |
ownsProfile | Check profile ownership | resourceIdPathInPayload: [T, ...T[]] | Validator | NodeblocksError (401/403/404) |
ownsSubscription | Check subscription ownership | resourceIdPathInPayload: [T, ...T[]] | Validator | NodeblocksError (401/403/404) |
ownsResource | Generic resource ownership | resource, ownerIdPathInResource, resourceIdPathInPayload | Validator | NodeblocksError (401/403/404) |
Identity & Organization Validators
Validator | Purpose | Parameters | Returns | Throws |
---|---|---|---|---|
checkIdentityType | Check identity type permissions | allowedTypes: [T, ...T[]] | Validator | NodeblocksError (401/403/500) |
hasOrgRole | Check organization role | allowedRoles, organizationIdPathInPayload | Validator | NodeblocksError (401/403/404) |
hasSubscription | Check channel subscription | channelIdPathInPayload, subscribedIdPathInPayload? | Validator | NodeblocksError (401/403/500) |
hasOrganizationAccessToMessageTemplate | Check message template access | allowedRoles, messageTemplateIdPathInPayload | Validator | NodeblocksError (401/403/404) |
getSubscriptionByChannelAndSubscriber | Get subscription document | collection, channelId, subscribedId | ResultAsync | Database errors |
Authentication Validators
Validator | Purpose | Parameters | Returns | Throws |
---|---|---|---|---|
verifyAuthentication | Higher-order authentication | authenticate function | Validator | Auth error |
getBearerTokenInfo | Bearer token authentication | none | Promise<TokenInfo> | NodeblocksError (401) |
getCookieTokenInfo | Cookie token authentication | none | Promise<TokenInfo> | NodeblocksError (401) |
Access Control Validators
Validator | Purpose | Parameters | Returns | Throws |
---|---|---|---|---|
validateResourceAccess | Check resource permissions | allowedSubjects, authenticate function | Validator | NodeblocksError (401/403) |
validateChannelAccess | Check channel access | allowedSubjects, authenticate function | Validator | NodeblocksError (401/403/404) |
validateMessageAccess | Check message access | allowedSubjects, authenticate function | Validator | NodeblocksError (401/403/404) |
validateOrderAccess | Check order access | allowedSubjects, authenticate function | Validator | NodeblocksError (401/403/404) |
validateUserProfileAccess | Check profile access | allowedSubjects, authenticate function | Validator | NodeblocksError (401/403/404) |
validateOrganizationAccess | Check organization access | allowedSubjects, authenticate function | Validator | NodeblocksError (401/403/404) |
Domain-Specific Validators
Validator | Purpose | Parameters | Returns | Throws |
---|---|---|---|---|
doesCategoryExist | Check category exists | none | Validator | NodeblocksError (404) |
📐 Best Practices
1. Fail Fast
- Validators run before handlers, so fail early for invalid requests
- Don't perform expensive operations in validators unless necessary
2. Reusable Patterns
- Create factory functions for common validation patterns
- Use domain-specific validators for business logic
3. Clear Error Messages
- Provide descriptive error messages
- Use consistent error codes for client-side handling
4. Database Efficiency
- Use database indexes for validator queries
- Consider caching for frequently accessed data
5. Composition
- Chain validators logically (required → format → business logic)
- Keep validators focused on single responsibilities
6. Access Control
- Use appropriate access control validators for protected resources
- Combine authentication and authorization validators
- Consider using
some()
for flexible validation scenarios
➡️ Next
- Learn about Route Composition for building complex routes
- Explore Error Handling for consistent error responses
- Check out Service Patterns for organizing your API