Skip to main content
Version: 0.5.0 (Previous)

🔍 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(validateHasUserId, validateHasEmail)
],
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)

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>;

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

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
someAlternative validation paths...validators: Validator[]ValidatorError if all fail
doesCategoryExistCheck category existsnoneValidatorNodeblocksError (404)
verifyAuthenticationValidate authenticationauthenticate functionValidatorAuth error
validateResourceAccessCheck resource permissionsallowedSubjects: string[], authenticate functionValidatorNodeblocksError (401/403)
validateChannelAccessCheck channel ownershipallowedSubjects: string[], authenticate functionValidatorNodeblocksError (401/403/404)
validateMessageAccessCheck message ownershipallowedSubjects: string[], authenticate functionValidatorNodeblocksError (401/403/404)
validateOrderAccessCheck order ownershipallowedSubjects: string[], authenticate functionValidatorNodeblocksError (401/403/404)
validateUserProfileAccessCheck user profile accessallowedSubjects: string[], authenticate functionValidatorNodeblocksError (401/403/404)
validateOrganizationAccessCheck organization accessallowedSubjects: string[], authenticate functionValidatorNodeblocksError (401/403/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