メインコンテンツまでスキップ
バージョン: 0.6.0 (Latest)

🔍 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 extract payload.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() or some(). 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 name
  • ownerIdPathInResource - Path to owner ID field in the resource document
  • resourceIdPathInPayload - 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:

  1. Extracts token from Authorization: Bearer <token> header
  2. Decrypts and verifies the JWT token signature
  3. Validates token type (user access token or app access token)
  4. Performs security checks for user tokens (fingerprint, IP, user agent)
  5. 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 strings
  • organizationIdPathInPayload - 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 payload
  • subscribedIdPathInPayload - 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 strings
  • messageTemplateIdPathInPayload - 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 subscriptions
  • channelId - Channel ID to search for
  • subscribedId - 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)

ValidatorPurposeParametersReturnsThrows
requireParamCheck parameter presencekey: stringFunction that validates request paramsError if null/missing
isUUIDValidate UUID formatkey: stringFunction that validates request paramsError if invalid UUID
isNumberValidate numeric formatkey: stringFunction that validates request paramsError if not numeric

Modern Validators

ValidatorPurposeParametersReturnsThrows
someAlternative validation paths...validators: Validator[]ValidatorError if all fail
isAuthenticatedCheck user authenticationnoneValidatorNodeblocksError (401)
isSelfCheck self-access to resourcesidentityIdPathInPayload: [T, ...T[]]ValidatorNodeblocksError (401/403)

Resource Ownership Validators

ValidatorPurposeParametersReturnsThrows
ownsChannelCheck channel ownershipresourceIdPathInPayload: [T, ...T[]]ValidatorNodeblocksError (401/403/404)
ownsMessageCheck message ownershipresourceIdPathInPayload: [T, ...T[]]ValidatorNodeblocksError (401/403/404)
ownsOrderCheck order ownershipresourceIdPathInPayload: [T, ...T[]]ValidatorNodeblocksError (401/403/404)
ownsProfileCheck profile ownershipresourceIdPathInPayload: [T, ...T[]]ValidatorNodeblocksError (401/403/404)
ownsSubscriptionCheck subscription ownershipresourceIdPathInPayload: [T, ...T[]]ValidatorNodeblocksError (401/403/404)
ownsResourceGeneric resource ownershipresource, ownerIdPathInResource, resourceIdPathInPayloadValidatorNodeblocksError (401/403/404)

Identity & Organization Validators

ValidatorPurposeParametersReturnsThrows
checkIdentityTypeCheck identity type permissionsallowedTypes: [T, ...T[]]ValidatorNodeblocksError (401/403/500)
hasOrgRoleCheck organization roleallowedRoles, organizationIdPathInPayloadValidatorNodeblocksError (401/403/404)
hasSubscriptionCheck channel subscriptionchannelIdPathInPayload, subscribedIdPathInPayload?ValidatorNodeblocksError (401/403/500)
hasOrganizationAccessToMessageTemplateCheck message template accessallowedRoles, messageTemplateIdPathInPayloadValidatorNodeblocksError (401/403/404)
getSubscriptionByChannelAndSubscriberGet subscription documentcollection, channelId, subscribedIdResultAsyncDatabase errors

Authentication Validators

ValidatorPurposeParametersReturnsThrows
verifyAuthenticationHigher-order authenticationauthenticate functionValidatorAuth error
getBearerTokenInfoBearer token authenticationnonePromise<TokenInfo>NodeblocksError (401)
getCookieTokenInfoCookie token authenticationnonePromise<TokenInfo>NodeblocksError (401)

Access Control Validators

ValidatorPurposeParametersReturnsThrows
validateResourceAccessCheck resource permissionsallowedSubjects, authenticate functionValidatorNodeblocksError (401/403)
validateChannelAccessCheck channel accessallowedSubjects, authenticate functionValidatorNodeblocksError (401/403/404)
validateMessageAccessCheck message accessallowedSubjects, authenticate functionValidatorNodeblocksError (401/403/404)
validateOrderAccessCheck order accessallowedSubjects, authenticate functionValidatorNodeblocksError (401/403/404)
validateUserProfileAccessCheck profile accessallowedSubjects, authenticate functionValidatorNodeblocksError (401/403/404)
validateOrganizationAccessCheck organization accessallowedSubjects, authenticate functionValidatorNodeblocksError (401/403/404)

Domain-Specific Validators

ValidatorPurposeParametersReturnsThrows
doesCategoryExistCheck category existsnoneValidatorNodeblocksError (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