๐งฉ Block
Blocks are pure business logic functions that contain the core application logic for specific operations. They are designed to be reusable, testable, and composable building blocks that separate business logic from payload handling and routing concerns.
๐ What is a Block?โ
A block is a pure function that takes only the data it needs (no payload context) and returns a Result type for proper error handling. Blocks are used within handlers to perform business logic operations, making handlers thin wrappers that handle payload context and call blocks for the actual work.
โโโโโโโโโโโโโโโ
โ Handler โ Receives payload, extracts data
โโโโโโโโโโโโโโโค
โ Block โท โ Pure function with business logic
โ โ getUserById(db, userId)
โโโโโโโโโโโโโโโ
Key take-aways:
- Pure functions โ no payload context, only required parameters (great for testing!).
- Result types โ always return
Result<T, Error>for predictable error handling. - Reusable โ same block can be used across different handlers and services.
- Testable โ easy to unit test without mocking complex payload structures.
๐ Design Principlesโ
โข Separation of concerns โ blocks contain business logic, handlers handle payload context.
โข Pure functions โ blocks take only what they need, no hidden dependencies.
โข Error handling โ blocks return Result types for explicit error handling.
โข Composability โ blocks can be composed together to build complex operations.
๐งโ๐ป Using Blocks in Handlersโ
Blocks are typically used within handlers using the applyPayloadArgs utility to lift payload context and pass only required data to blocks:
import { ok, err, Result } from 'neverthrow';
import { primitives, handlers, blocks } from '@nodeblocks/backend-sdk';
const { applyPayloadArgs } = primitives;
const { mergeData } = handlers;
export const getUserHandler: primitives.AsyncRouteHandler<
Result<primitives.RouteHandlerPayload, primitives.NodeblocksError>
> = async (payload) => {
const { context, params } = payload;
const userId = params.requestParams?.userId;
if (!userId) {
return err(new primitives.NodeblocksError(400, 'User ID is required', 'getUserHandler'));
}
// Use block with applyPayloadArgs to lift payload context
const result = await applyPayloadArgs(
blocks.getUserById,
payload
)(context.db.profiles, userId);
if (result.isErr()) {
return err(result.error);
}
const user = result.value;
return ok(mergeData(payload, { user }));
};
๐ Block vs Handlerโ
| Aspect | Block | Handler |
|---|---|---|
| Input | Only required data parameters | Full payload with context |
| Output | Result<T, Error> | Result<RouteHandlerPayload, NodeblocksError> |
| Purpose | Pure business logic | Payload handling + block orchestration |
| Testability | Easy - just pass data | Requires payload mocking |
| Reusability | High - can be used anywhere | Lower - tied to route structure |
๐ฆ Available Block Categoriesโ
Blocks are organized by domain entity. Each entity provides blocks for common operations:
- Authentication Blocks โ authentication and authorization logic
- Avatar Blocks โ avatar normalization and file management
- Chat Blocks โ messaging and real-time communication
- File Storage Blocks โ secure file operations and signed URLs
- Identity Blocks โ identity lifecycle and security management
- Location Blocks โ hierarchical location management
- Mongo Blocks โ MongoDB database operations and utilities
- OAuth Blocks โ third-party OAuth provider integration
- Order Blocks โ order processing and calculations
- Organization Blocks โ organization and workspace logic
- Product Blocks โ product management operations
- Profile Blocks โ profile relationships and social engagement
- User Blocks โ user retrieval, normalization, and data formatting
โน๏ธ See the Backend Blocks documentation for a complete list of available blocks and their usage.
โก๏ธ Nextโ
Learn about Handler ยป to see how blocks are used within handlers, or explore Backend Blocks ยป to see available block implementations for different entities.