@nodeblocks/backend-sdk
v0.9.0
Published
Type-safe Nodeblocks backend implementation
Keywords
Readme
Nodeblocks Backend SDK
Nodeblocks Backend SDK is a comprehensive library for building backend services in Node.js using a functional and compositional approach. Instead of relying on a large, opinionated framework, you construct your application by composing small, independent functions and modules.
At the heart of the SDK is the idea of building systems by putting together smaller parts. This is achieved through a set of primitives and a composition function, allowing you to assemble services from simple building blocks.
What's Included
The SDK provides a complete set of pre-built services and components for modern applications:
- 🔐 Authentication & Identity Management - Complete user authentication with OAuth, MFA, and session management
- 💬 Real-time Chat System - Full-featured chat with channels, messages, subscriptions, and WebSocket streaming
- 🛍️ E-commerce Features - Product management, orders, categories, and inventory
- 👥 User & Profile Management - User profiles, avatars, follows, and social features
- 🏢 Organization Management - Multi-tenant organization support with roles and permissions
- 📁 File Storage - File upload, management, and CDN integration
- 📍 Location Services - Address management and location-based features
- 🔧 Dynamic Attributes - Flexible attribute system for custom data
- 📊 Pagination & Filtering - Built-in pagination and advanced filtering
- 🔍 Search & Discovery - Advanced search capabilities across all entities
Key Features
🚀 Production Ready
- Comprehensive Test Coverage - Extensive test suite with high coverage
- TypeScript Support - Full TypeScript definitions and type safety
- Error Handling - Structured error responses with proper HTTP status codes
- Logging - Built-in logging with configurable levels and sanitization
- Validation - Request validation with JSON Schema and OpenAPI support
- Security - Authentication, authorization, and input sanitization
🔧 Developer Experience
- Functional Composition - Build complex features from simple, composable functions
- Declarative API - Describe what you want, not how to build it
- Hot Reloading - Fast development with instant updates
- Comprehensive Documentation - Detailed docs with examples for every component
- Flexible Architecture - Use pre-built services or compose your own features
📊 Enterprise Features
- Multi-tenancy - Organization-based data isolation and access control
- Role-based Access Control - Fine-grained permissions and authorization
- Audit Logging - Track all changes and user actions
- Pagination - Built-in pagination for all list endpoints
- File Management - Upload, storage, and CDN integration
- Real-time Communication - WebSocket support for live updates
A Functional, Compositional Approach to Backend Services
The core philosophy of the Nodeblocks Backend SDK is that complex systems can be built from simple, pure functions. Rather than creating large classes or objects that hold a lot of state and logic, you define small, focused functions and compose them into more complex ones.
This approach provides:
- Modularity: Each piece of your application is small and self-contained, making it easy to understand, test, and reuse.
- Declarative Style: You declare what your service is, rather than specifying how it should be built step-by-step.
- Predictability: With a foundation in functional principles, the behavior of your system is more predictable and easier to reason about.
Core Concepts
Services
A service is a composable piece of your application that provides HTTP and WebSocket endpoints. It's implemented as Express middleware that you can mount on your Express application. You create a service using the defService function, which takes a composed set of features and configurations and returns an Express router ready to be used as middleware. When using WebSockets, pass a WebSocketServer to defService.
Composition
Composition is the central pattern in the SDK. The compose function (a wrapper around ramda.pipe) allows you to chain together different parts of your application. You start with simple functions and progressively build them up into more complex ones.
// Compose multiple features into a single service definition
const serviceDefinition = compose(feature1, feature2, feature3);
// Create the service from the composed definition
const service = defService(serviceDefinition);Composers
Composers are higher-order functions that create the building blocks of your service. They are designed to be used with compose. The primary composers are withSchema and withRoute.
withSchema
The withSchema composer provides comprehensive request validation with support for both JSON Schema and OpenAPI specifications. It supports two distinct usage patterns:
1. JSON Schema Validation (Shorthand)
The simplified approach validates only the request body using JSON Schema:
// Define a reusable schema for request body validation
const userSchema = withSchema({
type: 'object',
properties: {
name: { type: 'string' },
email: { type: 'string', format: 'email' },
},
required: ['name', 'email'],
});
// Apply the same schema to multiple routes
const createUserFeature = compose(userSchema, createUserRoute);
const updateUserFeature = compose(userSchema, updateUserRoute);2. OpenAPI Validation (Enhanced)
The enhanced OpenAPI syntax allows you to validate URL path parameters and query parameters in addition to request bodies:
// Validate URL parameters (/:userId) and query parameters (?include=profile)
const getUserSchema = withSchema({
parameters: [
{
name: 'userId', // Validates the :userId in /users/:userId
in: 'path',
required: true,
schema: { type: 'string', format: 'uuid' },
},
{
name: 'include', // Validates ?include=profile query parameter
in: 'query',
required: false,
schema: { type: 'string', enum: ['profile', 'settings'] },
},
],
requestBody: {
// Still validates request body like before
required: true,
content: {
'application/json': {
schema: {
type: 'object',
properties: {
name: { type: 'string' },
email: { type: 'string', format: 'email' },
},
required: ['name', 'email'],
},
},
},
},
});
// Now validates ALL parts: URL params, query params, AND request body
const updateUserRoute = withRoute({
method: 'PATCH',
path: '/users/:userId', // :userId will be validated
handler: updateUserHandler,
});
const updateUserFeature = compose(getUserSchema, updateUserRoute);This is a major enhancement because previously withSchema only validated request bodies. Now you can validate the entire HTTP request - URL parameters, query parameters, and request body - all in one schema definition.
withRoute
The withRoute composer defines an endpoint. It supports both HTTP and WebSocket routes via the protocol option.
// HTTP route (default protocol: 'http')
const createUserRoute = withRoute({
method: 'POST',
path: '/users',
handler: async (payload) => {
const newUser = await payload.context.db.users.create(
payload.params.requestBody
);
return { user: newUser };
},
});The handler receives a payload object containing:
params: An object withrequestBody,requestParams, andrequestQuerycontext: An object withdbconnections and the originalrequestobject
WebSocket routes (protocol: ws)
You can expose real-time endpoints using WebSockets. Define a route with protocol: 'ws' and a handler that returns an RxJS Subject (or a Promise<Subject>). Messages sent by your handler via subject.next(value) will be delivered to connected clients. Incoming client messages are JSON-parsed and pushed into your subject.
Validation:
- Schema validation (message-level): Compose
withSchema(jsonSchema)before your WebSocket route (same order as HTTP). The schema is applied to each inbound client message. Invalid messages cause a structured error and the connection to close with a policy violation. - Validators (connection-level): Provide
validatorson the route (same shape as HTTP). They run once at connection time and can reject the connection (useful for auth/permissions) by throwing an error.
import { withSchema, withRoute, compose } from '@nodeblocks/backend-sdk';
const chatMessageSchema = withSchema({
type: 'object',
required: ['type', 'text'],
properties: {
type: { type: 'string', enum: ['chat'] },
text: { type: 'string', minLength: 1 },
},
});
const authValidator = async ({ context }: any) => {
if (!context.request?.headers['x-api-key']) {
throw new Error('Unauthorized');
}
};
const wsFeature = compose(
chatMessageSchema, // validates each inbound message
withRoute({
protocol: 'ws',
path: '/ws/chat',
validators: [authValidator], // validates the connection
handler: () => new Subject(),
})
);Example:
import { createServer } from 'http';
import express from 'express';
import { Subject, interval } from 'rxjs';
import { primitives } from '@nodeblocks/backend-sdk';
import { WebSocketServer } from 'ws';
const { compose, withRoute, defService } = primitives;
// Feature with a WebSocket route
const wsFeature = compose(
withRoute({
protocol: 'ws',
path: '/ws/time',
handler: () => {
const subject = new Subject<{
type: string;
at?: number;
[k: string]: any;
}>();
// Periodically broadcast a tick to all connected clients
interval(1000).subscribe(() => {
subject.next({ type: 'tick', at: Date.now() });
});
// You can also react to client messages as they are pushed into this subject
// e.g., subject.subscribe(msg => { ... })
return subject;
},
})
);
const app = express();
const server = createServer(app);
const wss = new WebSocketServer({ server });
const router = defService(wsFeature, wss);
app.use('/', router);
server.listen(3000, () => {
console.log('HTTP on :3000, WebSocket at ws://localhost:3000/ws/time');
});Rx helper for convenience:
import { filter } from 'rxjs';
import { notFromEmitter } from '@nodeblocks/backend-sdk';
// Use inside observable pipelines to ignore messages from a specific emitter id
// observable$.pipe(filter(notFromEmitter(someEmitterId)))Handlers
A handler is a function that contains the business logic for a route. In the spirit of composition, handlers themselves can be composed of smaller functions. The SDK provides utilities like lift and flatMapAsync to help compose asynchronous operations and handle errors in composition.
// A handler composed of multiple steps
const createUserHandler = compose(
createUserInDb,
flatMapAsync(findUserById), // Continues if createUserInDb is successful
lift(normalizeUserResponse) // Transforms the final result
);
const createUserRoute = withRoute({
method: 'POST',
path: '/users',
handler: createUserHandler,
});This allows you to create reusable pieces of business logic that can be combined in different ways.
Features
A feature is a logical grouping of related functionality, typically consisting of one or more routes and their associated schemas. You create a feature by composing its parts.
// schemas/user.ts
export const createUserSchema = withSchema({
/* ... */
});
// routes/user.ts
export const createUserRoute = withRoute({
/* ... */
});
// features/user.ts
import { compose } from '@nodeblocks/backend-sdk';
import { createUserSchema } from '../schemas/user';
import { createUserRoute } from '../routes/user';
export const userFeature = compose(createUserSchema, createUserRoute);Building a Service from First Principles
Let's walk through the process of building a complete service from scratch.
1. Define a Handler
First, we define a handler function that will contain our business logic.
// handlers/products.ts
export const createProductHandler = async (payload) => {
const { name, price } = payload.params.requestBody;
// In a real app, you would save this to a database
const newProduct = { id: 'prod_123', name, price };
return { product: newProduct };
};2. Define a Route
Next, we use withRoute to associate our handler with an HTTP endpoint.
// routes/products.ts
import { withRoute } from '@nodeblocks/backend-sdk';
import { createProductHandler } from '../handlers/products';
export const createProductRoute = withRoute({
method: 'POST',
path: '/products',
handler: createProductHandler,
});3. Define a Schema
To validate the incoming data, we define a schema using withSchema.
// schemas/products.ts
import { withSchema } from '@nodeblocks/backend-sdk';
export const createProductSchema = withSchema({
type: 'object',
properties: {
name: { type: 'string' },
price: { type: 'number', minimum: 0 },
},
required: ['name', 'price'],
});4. Compose into a Feature
Now we compose the schema and the route into a single feature.
// features/products.ts
import { compose } from '@nodeblocks/backend-sdk';
import { createProductSchema } from '../schemas/products';
import { createProductRoute } from '../routes/products';
export const productFeature = compose(createProductSchema, createProductRoute);The order is important here: createProductSchema comes first, so its validation is applied to createProductRoute.
5. Create the Service
Finally, we use defService to create the service from our feature.
// index.ts
import express from 'express';
import { defService } from '@nodeblocks/backend-sdk';
import { productFeature } from './features/products';
const app = express();
const service = defService(productFeature);
app.use('/api', service);
app.listen(3000, () => {
console.log('Server is running on port 3000');
});6. Composing Multiple Features
As your application grows, you can define more features and compose them together at the service level.
// index.ts
import { compose, defService } from '@nodeblocks/backend-sdk';
import { productFeature } from './features/products';
import { userFeature } from './features/users';
import { orderFeature } from './features/orders';
// Compose all features into one service definition
const allFeatures = compose(productFeature, userFeature, orderFeature);
const service = defService(allFeatures);Advanced Concepts
Handler Composition
For more business logic, you can compose handlers from smaller, specialized functions. The SDK provides helpers for working with Result types:
lift: Lifts a regular function into a context where it can work with promisesflatMap: Chains synchronous operations that return aResulttype, short-circuiting on errorsflatMapAsync: Chains asynchronous operations that return aResulttype, enabling error-safe composition
import { Result, ok, err } from 'neverthrow';
import { flatMapAsync, lift } from '@nodeblocks/backend-sdk';
const validateUser = (data: any): Result<ValidatedUser, ValidationError> => {
// validation logic
return ok(validatedData);
};
const saveUser = async (
user: ValidatedUser
): Promise<Result<User, DatabaseError>> => {
// database operation
return ok(savedUser);
};
const formatResponse = (user: User) => ({
user,
message: 'Created successfully',
});
// Compose operations that can fail
const createUserHandler = compose(
(payload) => validateUser(payload.params.requestBody),
flatMapAsync(saveUser),
lift(formatResponse)
);This pattern ensures that if any step fails, the error is propagated without executing subsequent steps, making your error handling both explicit and safe.
Function Combinators
The SDK provides several powerful combinators for building robust, composable functions:
Core Functional Primitives
compose - Function composition using pipe from Ramda
import { compose } from '@nodeblocks/backend-sdk';
// Compose functions from left to right
const processUser = compose(
validateUser,
saveUser,
sendWelcomeEmail,
formatResponse
);ifElse - Conditional function application
import { ifElse } from '@nodeblocks/backend-sdk';
const processUser = ifElse(
(user) => user.isAdmin,
(user) => processAdminUser(user),
(user) => processRegularUser(user)
);match - Path-based condition checking
import { match } from '@nodeblocks/backend-sdk';
const isAdminUser = match(Boolean, ['role', 'isAdmin']);withLogging - Function Logging
The withLogging combinator wraps any function to provide logging capabilities with configurable options.
What does withLogging do?
- Wraps any function to add logging of function calls, arguments, and results
- Supports multiple log levels:
info,debug,warn,error,fatal, andtrace - Configurable options: Pass logger, level, and redaction rules via options object
- Automatic logger injection: Injects logger into payload objects for downstream functions
- Smart sanitization: Sanitizes sensitive objects (database connections, configs) in logs
- Promise awareness: Handles both sync and async functions with proper promise logging
Basic Usage
import { withLogging } from '@nodeblocks/backend-sdk';
// Simple logging with default settings (info level, default logger)
const loggedHandler = withLogging(createUserHandler);
// With options object
const debugHandler = withLogging(createUserHandler, {
level: 'debug',
logger: customLogger,
redact: [/password/i, /token/i],
});
// With custom redaction patterns
const secureHandler = withLogging(createUserHandler, {
level: 'trace',
redact: [
{ approach: 'fields', pattern: /password/i, replacement: '[REDACTED]' },
{
approach: 'patterns',
pattern: /Bearer\s+[^\s]+/i,
replacement: '[REDACTED_TOKEN]',
},
],
});Log Output
All log levels produce structured logs with:
- Function name
- Sanitized input arguments
- Sanitized result/return value
- Promise detection for async functions
// Example log output
{
"args": [{"params": {"requestBody": {"name": "John"}}}],
"functionName": "createUserHandler",
"msg": "Function: createUserHandler",
"result": {"user": {"id": "123", "name": "John"}}
}Example: Logging a Handler
import { withLogging } from '@nodeblocks/backend-sdk';
const createUserHandler = async (payload) => {
const { name, email } = payload.params.requestBody;
const newUser = await payload.context.db.users.create({ name, email });
return { user: newUser };
};
const loggedCreateUserHandler = withLogging(createUserHandler, 'trace');
// Use in your route
const createUserRoute = withRoute({
method: 'POST',
path: '/users',
handler: loggedCreateUserHandler,
});Integration with Composition
The withLogging combinator works seamlessly with the SDK's composition patterns:
import { compose, withLogging } from '@nodeblocks/backend-sdk';
// Log individual functions
const loggedValidateUser = withLogging(validateUser, 'debug');
const loggedSaveUser = withLogging(saveUser, 'debug');
const loggedFormatResponse = withLogging(formatResponse, 'info');
// Compose with logging
const createUserFeature = compose(
createUserSchema,
withRoute({
method: 'POST',
path: '/users',
handler: compose(
loggedValidateUser,
flatMapAsync(loggedSaveUser),
lift(loggedFormatResponse)
),
})
);Sanitization Features
The withLogging function automatically sanitizes sensitive objects in logs:
- Database connections are shown as
🗄️ [Database] - Config objects are shown as
⚙️ [Config] - Mail services are shown as
📧 [MailService] - Request/Response objects are shown as
📤 [Request]/📥 [Response] - Logger instances are shown as
📝 [Logger]
This prevents logging of sensitive data while still providing useful debugging information.
Validation and Predicates
Beyond schema validation, the SDK provides a set of validation predicates:
import { requireParam, isUUID, isNumber } from '@nodeblocks/backend-sdk';
const getUserRoute = withRoute({
method: 'GET',
path: '/users/:userId',
validators: [requireParam('userId'), isUUID('userId')],
handler: getUserHandler,
});Available validators include:
requireParam(key): Ensures a parameter is presentisUUID(key): Validates UUID formatisNumber(key): Validates numeric values
Result-Based Error Handling
The SDK embraces functional error handling using the Result type from the neverthrow library. This approach makes error handling explicit and composable, avoiding the unpredictability of thrown exceptions.
import { Result, ok, err } from 'neverthrow';
// A handler that returns a Result
const createUserHandler = async (payload): Promise<Result<User, Error>> => {
const validation = validateUserData(payload.params.requestBody);
if (validation.isErr()) {
return err(validation.error);
}
const user = await createUser(validation.value);
return ok(user);
};The Result type has two states:
Ok<T>- Contains a successful value of type TErr<E>- Contains an error of type E
This makes error handling explicit in your type signatures and enables composition patterns with flatMap and flatMapAsync.
Throwing Errors
While Result types are preferred for business logic composition, you can still throw errors when appropriate. This is common in:
- Formatters/Terminators: Functions at the end of composition chains that format responses
- Validation failures: When input validation fails and you want to return an HTTP error
- Unexpected errors: When something goes wrong that should result in a 500 error
The SDK provides NodeblocksError for structured HTTP error responses:
import {
NodeblocksError,
nodeBlocksErrorMiddleware,
} from '@nodeblocks/backend-sdk';
// Use for structured HTTP errors
throw new NodeblocksError(400, 'Invalid input', 'validation', [
'Name is required',
]);
// Add middleware to handle NodeblocksError and other thrown errors
app.use(nodeBlocksErrorMiddleware());The error middleware will catch thrown errors and format appropriate HTTP responses.
Schema Merging and Extraction
You can merge multiple schemas together and extract schema definitions for reuse using helper functions.
JSON Schema Merging (Shorthand)
export const createCategorySchema = withSchema(
getSchemaDefinition(categorySchema),
{
$schema: 'http://json-schema.org/draft-07/schema#',
required: ['name', 'description', 'status'],
type: 'object',
}
);The SDK provides the getSchemaDefinition() function to extract JSON Schema definitions from shorthand withSchema composers for reuse and extension.
This function allows you to:
- Reuse existing schemas: Extract schema definitions from pre-built components
- Extend schemas: Add new properties and validation rules to existing schemas
- Combine schemas: Merge multiple schema definitions into a single schema
- Share validation logic: Reuse complex validation definitions across multiple routes
Custom Validators
The withRoute configuration accepts a validators array for custom validation logic that goes beyond schema validation. A validator is an async function that receives the request payload and should throw an error if validation fails.
// Custom validator to check if user has permission to access a resource
const checkUserPermission = async (payload) => {
const { userId } = payload.params.requestParams;
const { user } = payload.context;
if (user.id !== userId && user.role !== 'admin') {
throw new NodeblocksError(403, 'Access denied', 'permission', {
requiredRole: 'admin',
userId: user.id,
targetUserId: userId,
});
}
};
// Custom validator to check business rules
const checkBusinessHours = async (payload) => {
const now = new Date();
const hour = now.getHours();
if (hour < 9 || hour > 17) {
throw new NodeblocksError(400, 'Service unavailable', 'business_hours', {
currentHour: hour,
availableHours: '9:00 AM - 5:00 PM',
});
}
};
// Use custom validators in a route
const updateUserRoute = withRoute({
method: 'PATCH',
path: '/users/:userId',
validators: [checkUserPermission, checkBusinessHours],
handler: updateUserHandler,
});Pre-built Services
The SDK includes complete services for all major functionality areas. Each service provides a full set of CRUD operations and specialized endpoints:
Authentication Service
Complete identity lifecycle and OAuth management:
import { authService } from '@nodeblocks/backend-sdk';
// Register authentication service with OAuth providers
app.use(
'/api',
authService(database, config, {
mailService,
googleOAuthDriver,
twitterOAuthDriver,
lineOAuthDriver,
})
);Features:
- User registration and login with credentials
- Email verification and password reset
- Multi-factor authentication (MFA)
- OAuth integration (Google, Twitter, LINE)
- Session management and token refresh
- User invitations and role management
- Account activation/deactivation
Chat Service
Real-time chat system with WebSocket support:
import { chatService } from '@nodeblocks/backend-sdk';
// Register chat service with WebSocket server
app.use('/api/chat', chatService(database, config, { webSocketServer }));Features:
- Channel creation and management
- Real-time messaging with WebSocket streaming
- Message templates and attachments
- User subscriptions and read states
- File upload for message attachments
- Message search and filtering
User Service
Complete user and profile management:
import { userService } from '@nodeblocks/backend-sdk';
// Register user service with file storage
app.use('/api/users', userService(database, config, { fileStorageDriver }));Features:
- User profile creation and management
- Avatar upload and management
- Social features (follows, likes)
- Profile discovery and search
- User blocking and privacy controls
Product Service
E-commerce product management:
import { productService } from '@nodeblocks/backend-sdk';
// Register product service
app.use(
'/api/products',
productService(database, config, { fileStorageDriver })
);Features:
- Product CRUD operations
- Batch operations for bulk management
- Product variants and attributes
- Image upload and management
- Product copying and duplication
- Advanced search and filtering
- Product likes and social features
Organization Service
Multi-tenant organization management:
import { organizationService } from '@nodeblocks/backend-sdk';
// Register organization service
app.use('/api/organizations', organizationService(database, config));Features:
- Organization creation and management
- Role-based access control
- Member management
- Organization settings and configuration
- Multi-tenant data isolation
Order Service
Order processing and management:
import { orderService } from '@nodeblocks/backend-sdk';
// Register order service
app.use('/api/orders', orderService(database, config));Features:
- Order creation and processing
- Order status management
- Order history and tracking
- Payment integration hooks
- Order search and filtering
Category Service
Product categorization system:
import { categoryService } from '@nodeblocks/backend-sdk';
// Register category service
app.use('/api/categories', categoryService(database, config));Features:
- Category hierarchy management
- Category enable/disable functionality
- Category-based product filtering
- Admin-only category management
Attributes Service
Dynamic attribute management:
import { attributesService } from '@nodeblocks/backend-sdk';
// Register attributes service
app.use('/api/attributes', attributesService(database, config));Features:
- Dynamic attribute group creation
- Flexible attribute schemas
- Attribute-based filtering
- Custom data type support
Identity Service
Identity management and administration:
import { identityService } from '@nodeblocks/backend-sdk';
// Register identity service
app.use('/api/identities', identityService(database, config));Features:
- Identity administration
- User account management
- Identity type management
- Bulk identity operations
Individual Components
You can also use individual components to build custom features:
import {
// Routes
createUserRoute,
getUserRoute,
updateUserRoute,
deleteUserRoute,
createProductRoute,
findProductsRoute,
createChannelRoute,
findChannelsRoute,
// Schemas
createUserSchema,
updateUserSchema,
createProductSchema,
createChannelSchema,
// Handlers
createUser,
getUserById,
createProduct,
findProducts,
createChannel,
findChannels,
// Validators
isAuthenticated,
checkIdentityType,
requireParam,
isUUID,
} from '@nodeblocks/backend-sdk';
// Compose your own features using pre-built components
const customUserFeature = compose(
createUserSchema,
createUserRoute,
getUserRoute,
updateUserSchema,
updateUserRoute,
deleteUserRoute
);Available Components
Routes
Authentication Routes:
registerCredentialsRoute,loginWithCredentialsRoute,loginWithOnetimeTokenRouteemailVerificationRoute,confirmEmailRoute,changeEmailRoute,confirmNewEmailRoutesendResetPasswordLinkEmailRoute,completePasswordResetRoute,changePasswordRoutedeactivateRoute,activateRoute,refreshTokenRoute,deleteRefreshTokensRouteresendMfaCodeRoute,verifyMfaCodeRoutegoogleOAuthRoute,googleOAuthCallbackRoutetwitterOAuthRoute,twitterOAuthCallbackRoutelineOAuthRoute,lineOAuthCallbackRoute
User Routes:
createUserRoute,getUserRoute,findUsersRoute,updateUserRoute,deleteUserRoutelockUserRoute,unlockUserRoute,getAvatarUploadUrlRoutecreateProfileFollowRoute,deleteProfileFollowRoute,getProfileFollowersRoutecreateOrganizationFollowRoute,deleteOrganizationFollowRoutecreateProductLikeRoute,deleteProductLikeRoute
Product Routes:
createProductRoute,getProductRoute,findProductsRoute,updateProductRoute,deleteProductRoutecreateProductBatchRoute,updateProductBatchRoute,deleteProductBatchRoutecopyProductRoute,copyProductBatchRoutegetProductImageUploadUrlRoute,createProductImageRoute,deleteProductImageRoutecreateProductVariantRoute,getProductVariantRoute,updateProductVariantRoute,deleteProductVariantRoutefindProductVariantsRoute,createProductVariantBulkRoute,updateProductVariantBulkRoute,deleteProductVariantBulkRoutegetProductLikersRoute
Chat Routes:
createChannelRoute,getChannelRoute,findChannelsRoute,updateChannelRoute,deleteChannelRoutecreateChatSubscriptionRoute,getChatSubscriptionRoute,findChatSubscriptionsRoute,deleteChatSubscriptionRoutecreateChatMessageRoute,getChatMessageRoute,findChatMessagesRoute,updateChatMessageRoute,deleteChatMessageRoutegetChatMessageAttachmentUrlRoute,createChatMessageAttachmentRoute,deleteChatMessageAttachmentRoutegetChannelIconUploadUrlRoute,createChatMessageTemplateRoute,getChatMessageTemplateRouteupdateChatMessageTemplateRoute,deleteChatMessageTemplateRoute,findChatMessageTemplatesRoutefindChatMessageTemplatesForOrganizationRoute,getChannelMessagesRouteupsertChatChannelReadStateRoute,streamChatMessagesRoute
Organization Routes:
createOrganizationRoute,getOrganizationRoute,findOrganizationsRoute,updateOrganizationRoute,deleteOrganizationRoute
Order Routes:
createOrderRoute,getOrderRoute,findOrdersRoute,updateOrderRoute,deleteOrderRoute
Category Routes:
createCategoryRoute,getCategoryRoute,findCategoriesRoute,updateCategoryRoute,deleteCategoryRouteenableCategoryRoute,disableCategoryRoute
Attributes Routes:
createAttributeRoute,getAttributeRoute,findAttributesRoute,updateAttributeRoute,deleteAttributeRoute
Identity Routes:
findIdentitiesRoute,getIdentityRoute,updateIdentityRoute,deleteIdentityRoute
Schemas
Authentication Schemas:
registerCredentialsSchema,loginWithCredentialsSchema,loginWithOnetimeTokenSchemaemailVerificationSchema,confirmEmailSchema,changeEmailSchema,confirmNewEmailSchemasendResetPasswordLinkEmailSchema,completePasswordResetSchema,changePasswordSchemadeactivateSchema,activateSchema,refreshTokenSchema,deleteRefreshTokensSchemaresendMfaCodeSchema,verifyMfaCodeSchemagoogleOAuthSchema,googleOAuthCallbackSchematwitterOAuthSchema,twitterOAuthCallbackSchemalineOAuthSchema,lineOAuthCallbackSchema
User Schemas:
createUserSchema,updateUserSchema,userSchema,profileIdPathParameteravatarSchema,createProfileFollowSchema,createOrganizationFollowSchema,createProductLikeSchema
Product Schemas:
createProductSchema,updateProductSchema,productSchemacreateProductBatchSchema,updateProductBatchSchema,deleteProductBatchSchema,copyProductBatchSchemacreateProductVariantSchema,updateProductVariantSchema,productVariantSchemacreateProductVariantBulkSchema,updateProductVariantBulkSchema,deleteProductVariantBulkSchema
Chat Schemas:
createChannelSchema,updateChannelSchema,channelSchemacreateChatSubscriptionSchema,chatSubscriptionSchemacreateChatMessageSchema,updateChatMessageSchema,chatMessageSchemacreateChatMessageTemplateSchema,updateChatMessageTemplateSchema,chatMessageTemplateSchemaupsertChatChannelReadStateSchema,chatChannelReadStateSchema
Organization Schemas:
createOrganizationSchema,updateOrganizationSchema,organizationSchemaorganizationCommonSchema,organizationAdminApprovedSchema
Order Schemas:
createOrderSchema,updateOrderSchema,orderSchema
Category Schemas:
createCategorySchema,updateCategorySchema,categorySchema
Attributes Schemas:
createAttributeSchema,updateAttributeSchema,attributeSchema
Common Schemas:
baseSchema,baseUpdateSchema,arrayOfStringsSchemaauditSchema,paginationSchema,fileSchema,addressSchema,contactSchemapaginationQueryParametersSchema,contentLengthQueryParameter
Handlers
Authentication Handlers:
registerCredentials,loginWithCredentials,loginWithOnetimeTokenemailVerification,confirmEmail,changeEmail,confirmNewEmailsendResetPasswordLinkEmail,completePasswordReset,changePassworddeactivate,activate,refreshToken,deleteRefreshTokensresendMfaCode,verifyMfaCodegoogleOAuth,googleOAuthCallbacktwitterOAuth,twitterOAuthCallbacklineOAuth,lineOAuthCallback
User Handlers:
createUser,getUserById,findUsers,updateUser,deleteUserlockUser,unlockUser,normalizeUserTerminator,normalizeUsersListTerminatordeleteUserTerminator,lockUserTerminator,unlockUserTerminatorcreateProfileFollow,deleteProfileFollow,getProfileFollowerscreateOrganizationFollow,deleteOrganizationFollowcreateProductLike,deleteProductLike,findProfilesByIdentityId
Product Handlers:
createProduct,getProductById,findProducts,updateProduct,deleteProductcreateProductBatch,updateProductBatch,deleteProductBatchcopyProduct,copyProductBatchnormalizeProductTerminator,normalizeProductsListTerminatordeleteProductTerminator,deleteBatchProductsTerminatorcreateProductVariant,getProductVariant,updateProductVariant,deleteProductVariantfindProductVariants,createProductVariantBulk,updateProductVariantBulk,deleteProductVariantBulkgetProductLikers
Chat Handlers:
createChannel,getChannelById,findChannels,updateChannel,deleteChannelcreateChatSubscription,getChatSubscriptionById,findChatSubscriptions,deleteChatSubscriptioncreateChatMessage,getChatMessageById,findChatMessages,updateChatMessage,deleteChatMessagegetChatMessageAttachmentUrl,createChatMessageAttachment,deleteChatMessageAttachmentgetChannelIconUploadUrl,createChatMessageTemplate,getChatMessageTemplateByIdupdateChatMessageTemplate,deleteChatMessageTemplate,findChatMessageTemplatesfindChatMessageTemplatesForOrganization,getChannelMessagesupsertChatChannelReadState,streamChatMessages
Organization Handlers:
createOrganization,getOrganizationById,findOrganizations,updateOrganization,deleteOrganizationnormalizeOrganizationTerminator,normalizeOrganizationsListTerminator,deleteOrganizationTerminator
Order Handlers:
createOrder,getOrderById,findOrders,updateOrder,deleteOrdernormalizeOrderTerminator,normalizeOrdersListTerminator,deleteOrderTerminator
Category Handlers:
createCategory,getCategoryById,findCategories,updateCategory,deleteCategoryenableCategory,disableCategorynormalizeCategoryTerminator,normalizeCategoriesListTerminatordeleteCategoryTerminator,enableCategoryTerminator,disableCategoryTerminator
Attributes Handlers:
createAttributeGroup,getAttributeGroupById,findAttributeGroups,updateAttributeGroup,deleteAttributeGroupnormalizeAttributeGroupTerminator,normalizeAttributesListTerminator,deleteAttributeGroupTerminator
Identity Handlers:
findIdentities,getIdentityById,updateIdentity,deleteIdentitynormalizeIdentitiesWithoutPassword,normalizeIdentity,deleteIdentityTerminator
Validators
Authentication Validators:
isAuthenticated(),checkIdentityType(types),isSelf(path),hasOrgRole(roles, orgIdPath)
Resource Access Validators:
validateResourceAccess(allowedSubjects),validateOrganizationAccess(orgIdPath)validateChannelAccess(channelIdPath),validateMessageAccess(messageIdPath)validateUserProfileAccess(profileIdPath)
Parameter Validators:
requireParam(key),isUUID(key),isNumber(key)
Domain-Specific Validators:
doesCategoryExist,channelExists,ownsOrder(path),some(...validators)
Features
Authentication Features:
registerCredentialsFeature,loginWithCredentialsFeature,loginWithOnetimeTokenFeatureemailVerificationFeature,confirmEmailFeature,changeEmailFeature,confirmNewEmailFeaturesendResetPasswordLinkEmailFeature,completePasswordResetFeature,changePasswordFeaturedeactivateFeature,activateFeature,refreshTokenFeature,deleteRefreshTokensFeatureresendMfaCodeFeature,verifyMfaCodeFeaturegoogleOAuthFeature,googleOAuthCallbackFeaturetwitterOAuthFeature,twitterOAuthCallbackFeaturelineOAuthFeature,lineOAuthCallbackFeature
User Features:
createUserFeature,getUserFeatures,findUsersFeatures,editUserFeatures,deleteUserFeatureslockUserFeatures,unlockUserFeatures,getAvatarUploadUrlFeaturecreateProfileFollowFeature,deleteProfileFollowFeature,getProfileFollowersFeaturecreateOrganizationFollowFeature,deleteOrganizationFollowFeaturecreateProductLikeFeature,deleteProductLikeFeature,findProfilesByIdentityIdFeature
Product Features:
createProductFeature,getProductFeatures,findProductsFeatures,editProductFeatures,deleteProductFeaturescreateProductBatchFeature,editProductBatchFeatures,deleteProductBatchFeaturescopyProductFeatures,copyProductBatchFeaturesgetProductImageUploadUrlFeature,createProductImageFeature,deleteProductImageFeaturecreateProductVariantFeature,getProductVariantFeature,updateProductVariantFeature,deleteProductVariantFeaturefindProductVariantsFeature,createProductVariantBulkFeature,updateProductVariantBulkFeature,deleteProductVariantBulkFeaturegetProductLikersFeature
Chat Features:
createChannelFeature,getChannelFeature,findChannelsFeature,updateChannelFeature,deleteChannelFeaturecreateChatSubscriptionFeature,getChatSubscriptionFeature,findChatSubscriptionsFeature,deleteChatSubscriptionFeaturecreateChatMessageFeature,getChatMessageFeature,findChatMessagesFeature,updateChatMessageFeature,deleteChatMessageFeaturegetChatMessageAttachmentUrlFeature,createChatMessageAttachmentFeature,deleteChatMessageAttachmentFeaturegetChannelIconUploadUrlFeature,createChatMessageTemplateFeature,getChatMessageTemplateFeatureupdateChatMessageTemplateFeature,deleteChatMessageTemplateFeature,findChatMessageTemplatesFeaturefindChatMessageTemplatesForOrganizationFeature,getChannelMessagesFeatureupsertChatChannelReadStateFeature,streamChatMessagesFeature
Organization Features:
createOrganizationFeature,getOrganizationFeature,findOrganizationsFeature,updateOrganizationFeature,deleteOrganizationFeature
Order Features:
createOrderFeature,getOrderFeature,findOrdersFeature,updateOrderFeature,deleteOrderFeature
Category Features:
createCategoryFeature,getCategoryFeature,findCategoriesFeature,updateCategoryFeature,deleteCategoryFeatureenableCategoryFeature,disableCategoryFeature
Attributes Features:
createAttributeFeature,getAttributeFeature,findAttributesFeature,updateAttributeFeature,deleteAttributeFeature
Identity Features:
findIdentitiesFeature,getIdentityFeature,updateIdentityFeature,deleteIdentityFeature
Pagination with withPagination
The SDK provides a powerful utility, withPagination, to add automatic pagination to your database-backed route handlers. This is especially useful for MongoDB-style collections, but can be adapted for any database interface that supports find and toArray/count methods.
What does withPagination do?
- Monkey-patches your database collections'
findmethod to automatically apply pagination parameters (page,limit). - Automatically calculates skip from page and limit parameters (skip = (page - 1) * limit).
- Injects pagination metadata (like
total,totalPages,hasNext,hasPrev, etc.) into the handler payload. - Makes it easy to build paginated endpoints without manually handling skip/limit logic in every handler.
How to use
- Wrap your handler with
withPagination:
import { withPagination } from '@nodeblocks/backend-sdk';
const paginatedHandler = withPagination(myHandler);Handler signature: Your handler receives a payload whose
context.dbcollections have a monkey-patchedfindmethod. When you call.find().toArray(), pagination is applied and metadata is injected into the payload.Query parameter handling: The
pageandlimitparameters are extracted fromrequestQueryand used for pagination. Default values arepage = 1andlimit = 10. All other query parameters are preserved as filter parameters for your handler to use.Accessing pagination metadata: After calling
.toArray()on a collection, the payload will have apaginationResultproperty with the following structure:
{
data: [...],
pagination: {
page: number,
limit: number,
total: number,
totalPages: number,
hasNext: boolean,
hasPrev: boolean,
}
}Example: Paginated Query with MongoDB (from __scratch__.ts)
import { MongoMemoryServer } from 'mongodb-memory-server';
import { MongoClient } from 'mongodb';
import { withPagination } from '@nodeblocks/backend-sdk';
const server = await MongoMemoryServer.create();
const uri = server.getUri();
const client = new MongoClient(uri);
const db = client.db();
// Insert some test data
for (let i = 0; i < 100; i++) {
await db.collection('attributes').insertOne({ name: 'test' + i });
}
// Define a handler that returns all attribute groups
const findAttributeGroups = async (payload) => {
const data = await payload.context.db.attributes.find({}).toArray();
return { attributeGroups: data };
};
// Wrap the handler with pagination
const paginatedHandler = withPagination(findAttributeGroups);
// Call the paginated handler
const result = await paginatedHandler({
context: {
db: { attributes: db.collection('attributes') },
request: {} as any,
},
params: {
requestQuery: {
page: 1, // Current page (1-based)
limit: 10, // Items per page
// Note: skip is automatically calculated as (page - 1) * limit
},
},
});
console.log(result.paginationResult);
// {
// data: [...],
// pagination: {
// page: 1,
// limit: 10,
// total: 100,
// totalPages: 10,
// hasNext: true,
// hasPrev: false,
// }
// }Testing Pagination Logic
When testing handlers wrapped with withPagination, make sure to call .toArray() on the cursor to trigger the pagination logic and metadata injection. Example:
const payload = createMockPayload({ page: 2, limit: 5 }, mockData, 25);
const paginatedHandler = withPagination(mockHandler);
await paginatedHandler(payload);
const cursor = payload.context.db.users.find({});
await cursor.toArray();
expect(payload.paginationResult?.pagination).toEqual({
page: 2,
limit: 5,
total: 25,
totalPages: 5,
hasNext: true,
hasPrev: true,
});This ensures your tests accurately reflect how pagination metadata is set in real usage.
withSoftDelete - Soft Delete Support
The withSoftDelete combinator automatically converts hard deletes into soft deletes by adding a deletedAt timestamp instead of removing records from the database.
What does withSoftDelete do?
- Converts deletes to updates:
deleteOneanddeleteManyoperations setdeletedAttimestamp - Filters out deleted records:
find,findOne, andcountDocumentsautomatically exclude soft-deleted records - Preserves data integrity: Records are marked as deleted but remain in the database
- Maintains API compatibility: Works transparently with existing handlers
import { withSoftDelete } from '@nodeblocks/backend-sdk';
// Wrap any handler to enable soft delete
const softDeleteHandler = withSoftDelete(myHandler);
// Now all delete operations will be soft deletes
// and find operations will exclude deleted recordswithPaginatedProperty - Property Pagination
The withPaginatedProperty combinator adds pagination to array properties within documents, useful for paginating nested arrays like organization members or product reviews.
What does withPaginatedProperty do?
- Paginates array properties: Adds pagination to specific array fields in documents
- Preserves document structure: Returns the full document with paginated array properties
- Maintains metadata: Includes pagination metadata for the paginated property
- Flexible property paths: Supports nested property paths like
['members', 'roles']
import { withPaginatedProperty } from '@nodeblocks/backend-sdk';
// Paginate the 'members' array in organization documents
const paginatedMembersHandler = withPaginatedProperty(getOrganizationHandler, [
'members',
]);
// Paginate nested array properties
const paginatedRolesHandler = withPaginatedProperty(getUserHandler, [
'profile',
'roles',
]);applyPayloadArgs - Function Application
The applyPayloadArgs combinator extracts arguments from a payload and applies them to a pure function, enabling clean separation of business logic from payload handling.
What does applyPayloadArgs do?
- Extracts payload data: Pulls specific paths from the payload object
- Applies to pure functions: Calls your business logic with extracted arguments
- Handles async/sync functions: Works with both synchronous and asynchronous functions
- Supports Result types: Automatically handles
Resultreturn types - Merges results back: Stores function results back into the payload
import { applyPayloadArgs } from '@nodeblocks/backend-sdk';
// Extract arguments and apply to pure function
const updateUserHandler = applyPayloadArgs(
updateUserInDatabase, // Pure function
[
['params', 'requestParams', 'userId'], // Extract userId
['params', 'requestBody'], // Extract request body
['context', 'db', 'users'], // Extract database collection
],
'updatedUser' // Store result in payload.updatedUser
);orThrow - Result Termination
The orThrow combinator handles Result types by either returning success data or throwing appropriate errors based on error type mapping.
What does orThrow do?
- Maps errors to HTTP status codes: Converts specific error types to appropriate HTTP responses
- Extracts success data: Optionally extracts specific data from successful results
- Throws structured errors: Throws
NodeblocksErrorwith proper status codes - Type-safe error handling: Ensures all error cases are handled explicitly
import { orThrow } from '@nodeblocks/backend-sdk';
// Map errors to HTTP status codes
const errorHandler = orThrow([
[ValidationError, 400],
[NotFoundError, 404],
[DuplicateError, 409],
[DatabaseError, 500],
]);
// With success data extraction
const successHandler = orThrow(
[[ValidationError, 400]], // Error mappings
[['user'], 200] // Extract user data with 200 status
);hasValue - Value Validation
The hasValue combinator validates that a value is not null, undefined, or empty, useful for input validation and data integrity checks.
What does hasValue do?
- Validates meaningful content: Ensures values are not null, undefined, or empty
- Type-safe validation: Returns a type guard for TypeScript
- Handles various empty types: Checks for empty strings, arrays, and objects
- Composable validation: Can be used in validation pipelines
import { hasValue } from '@nodeblocks/backend-sdk';
// Validate input has meaningful content
if (hasValue(userInput)) {
// userInput is guaranteed to have content
processUserInput(userInput);
}
// Use in validation pipelines
const validateInput = (input: unknown) => {
if (!hasValue(input)) {
throw new Error('Input is required');
}
return input; // TypeScript knows input has value
};lift - Promise Lifting
The lift combinator lifts a synchronous function to work with promises, enabling composition of sync and async functions.
What does lift do?
- Lifts sync functions: Converts synchronous functions to work with promises
- Enables composition: Allows mixing sync and async functions in composition chains
- Maintains type safety: Preserves TypeScript types through the transformation
- Handles promise unwrapping: Automatically awaits promises before applying the function
import { lift } from '@nodeblocks/backend-sdk';
// Lift a sync function to work with promises
const asyncNormalizeUser = lift(normalizeUser);
// Use in composition with async functions
const userHandler = compose(
getUserFromDatabase, // Returns Promise<User>
lift(normalizeUser), // Lifts sync function to work with Promise<User>
lift(formatUserResponse) // Lifts another sync function
);flatMap and flatMapAsync - Result Chaining
The flatMap and flatMapAsync combinators enable chaining of operations that return Result types, providing safe error handling in composition chains.
What do they do?
- Chain Result operations: Compose functions that return
Resulttypes - Short-circuit on errors: Stop execution if any step fails
- Preserve error context: Maintain error information through the chain
- Type-safe composition: Ensure type safety across the composition chain
import { flatMap, flatMapAsync } from '@nodeblocks/backend-sdk';
// Chain synchronous Result operations
const syncChain = compose(
validateUser,
flatMap(saveUser), // Only runs if validateUser succeeds
flatMap(sendWelcomeEmail) // Only runs if saveUser succeeds
);
// Chain asynchronous Result operations
const asyncChain = compose(
validateUser,
flatMapAsync(saveUserToDatabase), // Async operation
flatMapAsync(sendWelcomeEmail), // Another async operation
lift(formatResponse) // Sync operation at the end
);Business Blocks
The SDK includes a comprehensive set of business blocks that provide reusable, domain-specific functionality. These blocks encapsulate complex business logic and can be composed together to build sophisticated features.
Authentication Blocks
Core authentication utilities and token management:
import {
checkToken,
compareStringAgainstHash,
generateUserAccessToken,
generateRefreshToken,
generateOnetimeToken,
softDeleteRefreshTokens,
} from '@nodeblocks/backend-sdk';
// Token validation with security checks
const tokenResult = await checkToken(
db.tokens,
authSecrets,
request,
'jwt-token-string',
'user-session'
);
// Password verification
const isValidPassword = await compareStringAgainstHash(
hashedPassword,
plainPassword
);File Storage Blocks
File upload, management, and CDN integration:
import {
generateSignedUploadUrl,
generateSignedDownloadUrl,
deleteFile,
normalizeFile,
} from '@nodeblocks/backend-sdk';
// Generate signed upload URL
const uploadUrl = await generateSignedUploadUrl(
fileStorageDriver,
'images/avatars',
'image/jpeg',
1024 * 1024 // 1MB limit
);
// Normalize file metadata
const normalizedFile = normalizeFile(uploadedFile, fileStorageDriver);Product Blocks
Product management with variants, images, and inventory:
import {
createProduct,
updateProduct,
deleteProduct,
createProductVariant,
createProductImage,
deleteProductImage,
} from '@nodeblocks/backend-sdk';
// Create product with variants
const product = await createProduct(db.products, productData, organizationId);
// Add product variant
const variant = await createProductVariant(
db.productVariants,
productId,
variantData
);Chat Blocks
Real-time messaging and channel management:
import {
createChannel,
createChatMessage,
createChatSubscription,
upsertChatChannelReadState,
} from '@nodeblocks/backend-sdk';
// Create chat channel
const channel = await createChannel(db.channels, channelData, organizationId);
// Send message
const message = await createChatMessage(
db.messages,
channelId,
messageData,
senderId
);Organization Blocks
Multi-tenant organization management:
import {
createOrganization,
addOrganizationMember,
removeOrganizationMember,
updateOrganizationMemberRole,
} from '@nodeblocks/backend-sdk';
// Create organization
const org = await createOrganization(
db.organizations,
organizationData,
ownerId
);
// Add member with role
await addOrganizationMember(
db.organizationMembers,
organizationId,
userId,
'member'
);User & Profile Blocks
User management and social features:
import {
createUser,
updateUser,
createProfileFollow,
createProductLike,
getAvatarUploadUrl,
} from '@nodeblocks/backend-sdk';
// Create user profile
const user = await createUser(db.users, userData, identityId);
// Social features
await createProfileFollow(db.profileFollows, followerId, followeeId);
await createProductLike(db.productLikes, userId, productId);Order Blocks
Order processing and management:
import {
createOrder,
updateOrderStatus,
addOrderItem,
removeOrderItem,
} from '@nodeblocks/backend-sdk';
// Create order
const order = await createOrder(db.orders, orderData, customerId);
// Update order status
await updateOrderStatus(db.orders, orderId, 'shipped');Location Blocks
Address and location management:
import {
createAddress,
updateAddress,
validateAddress,
geocodeAddress,
} from '@nodeblocks/backend-sdk';
// Create address
const address = await createAddress(db.addresses, addressData, userId);
// Validate and geocode
const validatedAddress = await validateAddress(address);
const geocoded = await geocodeAddress(validatedAddress);MongoDB Utility Blocks
Database operation helpers:
import {
findResources,
createBaseEntity,
updateBaseEntity,
} from '@nodeblocks/backend-sdk';
// Find resources with error handling
const resources = await findResources(
db.collection,
{ filter: { status: 'active' }, options: { limit: 10 } },
CustomError,
'Failed to find resources'
);
// Entity management
const newEntity = createBaseEntity(data);
const updatedEntity = updateBaseEntity(existingEntity, changes);Error Handling
All blocks use structured error handling with specific error types:
import {
AuthenticationBlockError,
ProductBlockError,
ChatChannelBlockError,
OrganizationBlockError,
UserBlockError,
OrderBlockError,
LocationBlockError,
} from '@nodeblocks/backend-sdk';
// Each block has its own error hierarchy
try {
await createProduct(db, data);
} catch (error) {
if (error instanceof ProductBlockError) {
// Handle product-specific error
}
}Block Composition
Blocks can be composed together to create complex business operations:
import { compose, flatMapAsync, lift } from '@nodeblocks/backend-sdk';
// Compose multiple blocks for a complete workflow
const createUserWithProfile = compose(
(data) => createUser(db.users, data, data.identityId),
flatMapAsync((user) => createAddress(db.addresses, data.address, user.id)),
flatMapAsync((user) =>
createProfileFollow(db.follows, user.id, data.followingId)
),
lift((user) => ({ user, success: true }))
);Drivers
The SDK includes a comprehensive set of drivers that provide integrations with external services. These drivers abstract away the complexity of third-party APIs and provide a consistent interface for your applications.
MongoDB Driver
Database connection and collection management with curried functions:
import { withMongo, getMongoClient } from '@nodeblocks/backend-sdk';
// Simple connection (non-curried)
const db = getMongoClient('mongodb://localhost:27017', 'myapp');
// Curried connection with authentication - returns a function
const connectToDatabase = withMongo(
'mongodb+srv://nodeblocks-dev-cluster.8pxjylf.mongodb.net/' +
'?retryWrites=true&w=majority&appName=nodeblocks-dev-cluster',
'myapp',
'adam_local_user',
'4irpPYzyox7iV3Hyh3et'
);
// Use the curried function to get collections
const products = await connectToDatabase('products');
const users = await connectToDatabase('users');
const orders = await connectToDatabase('orders');
// Each call returns an object with the collection
// products = { products: Collection<Document> }
// users = { users: Collection<Document> }
// orders = { orders: Collection<Document> }
// Use with handlers directly
const payload = {
context: {
db: await connectToDatabase('products'),
},
params: {
requestBody: {
name: 'Product 1',
price: 100,
},
},
};
// Use with services
app.use(
'/api/products',
productService(await connectToDatabase('products'), config)
);
app.use('/api/users', userService(await connectToDatabase('users'), config));Advanced curried usage patterns:
// Create a reusable connection factory
const connectToMyApp = withMongo(
'mongodb://localhost:27017',
'myapp',
'admin',
'password'
);
// Use with different collections
const users = await connectToMyApp('users');
const orders = await connectToMyApp('orders');
const products = await connectToMyApp('products');
// Fully curried application (all parameters at once)
const productsCollection = await withMongo(
'mongodb://localhost:27017',
'myapp',
'admin',
'password',
'products'
);
// Step-by-step currying
const step1 = withMongo('mongodb://localhost:27017');
const step2 = step1('myapp');
const step3 = step2('admin');
const step4 = step3('password');
const final = await step4('products');Practical usage with handlers and soft delete:
import { withMongo } from '@nodeblocks/backend-sdk';
import { withSoftDelete } from '@nodeblocks/backend-sdk';
import {
createProduct,
updateProduct,
deleteProduct,
findProducts,
} from '@nodeblocks/backend-sdk';
// Set up database connection
const connectToDatabase = withMongo(
'mongodb+srv://nodeblocks-dev-cluster.8pxjylf.mongodb.net/' +
'?retryWrites=true&w=majority&appName=nodeblocks-dev-cluster',
'adam_tst_1007',
'adam_local_user',
'4irpPYzyox7iV3Hyh3et'
);
// Create a handler with soft delete enabled
const createProductWithSoftDelete = withSoftDelete(createProduct);
// Use with handlers
const payload = {
context: {
db: await connectToDatabase('products'),
},
params: {
requestBody: {
name: 'Product 1',
price: 100,
},
},
};
// Execute handler with soft delete
const result = await createProductWithSoftDelete(payload);
// The handler will automatically:
// - Connect to the products collection
// - Apply soft delete logic (deletedAt field)
// - Return the created productFile Storage Driver
Google Cloud Storage integration with signed URLs:
import { createFileStorageDriver } from '@nodeblocks/backend-sdk';
// Initialize file storage driver
const fileStorage = await createFileStorageDriver(
'my-project-id',
'my-storage-bucket',
{
signedUrlExpiresInSeconds: 900, // 15 minutes
}
);
// Generate signed upload URL
const uploadUrl = await fileStorage.generateSignedUploadUrl(
'image/jpeg',
5 * 1024 * 1024, // 5MB limit
'uploads/profile-avatar.jpg'
);
// Generate signed download URL
const downloadUrl = await fileStorage.generateSignedDownloadUrl(
'uploads/document.pdf'
);
// Delete file
await fileStorage.deleteFile('uploads/temp-file.jpg');
// Use with services
app.use(
'/api/products',
productService(db, config, { fileStorageDriver: fileStorage })
);SendGrid Mail Driver
Email service integration:
import { getSendGridClient } from '@nodeblocks/backend-sdk';
// Basic configuration
const mailService = getSendGridClient(process.env.SENDGRID_API_KEY);
// With custom base URL for testing
const testMailService = getSendGridClient(
process.env.SENDGRID_API_KEY,
'https://api-staging.sendgrid.com/v3'
);
// Send email
const success = await mailService.sendMail({
to: '[email protected]',
from: '[email protected]',
subject: 'Welcome!',
text: 'Welcome to our platform',
html: '<h1>Welcome!</h1><p>Welcome to our platform</p>',
});
// Use with authentication service
app.use('/api', authService(db, config, { mailService }));OAuth Drivers
Social authentication providers:
Google OAuth Driver
import { createGoogleOAuthDriver } from '@nodeblocks/backend-sdk';
const googleOAuthDriver = createGoogleOAuthDriver({
clientId: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
redirectUri: 'http://localhost:3000/auth/google/callback',
});
// Use with authentication service
app.use('/api', authService(db, config, { googleOAuthDriver }));Twitter OAuth Driver
import { createTwitterOAuthDriver } from '@nodeblocks/backend-sdk';
const twitterOAuthDriver = createTwitterOAuthDriver({
consumerKey: process.env.TWITTER_CONSUMER_KEY,
consumerSecret: process.env.TWITTER_CONSUMER_SECRET,
callbackURL: 'http://localhost:3000/auth/twitter/callback',
});
// Use with authentication service
app.use('/api', authService(db, config, { twitterOAuthDriver }));LINE OAuth Driver
import { createLineOAuthDriver } from '@nodeblocks/backend-sdk';
const lineOAuthDriver = createLineOAuthDriver({
channelID: process.env.LINE_CHANNEL_ID,
channelSecret: process.env.LINE_CHANNEL_SECRET,
callbackURL: 'http://localhost:3000/auth/line/callback',
});
// Use with authentication serv