
Security News
The Hidden Blast Radius of the Axios Compromise
The Axios compromise shows how time-dependent dependency resolution makes exposure harder to detect and contain.
@nodeblocks/backend-sdk
Advanced tools
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.
The SDK provides a complete set of pre-built services and components for modern applications:
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:
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 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 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.
withSchemaThe 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.
Automatic Security Enhancements
When using withSchema, the SDK automatically applies security enhancements to all object schemas:
additionalProperties: false - Prevents injection of undefined fields by rejecting any properties not explicitly defined in the schemaqueryFilter: true - Validates MongoDB query operators to prevent NoSQL injection attacksThese enhancements are applied recursively to nested object schemas as well. You can override these defaults by explicitly specifying additionalProperties: true or queryFilter: false in your schema definition.
// Automatic security enhancements are applied
const secureSchema = withSchema({
type: 'object',
properties: {
email: { type: 'string' },
},
});
// Result: additionalProperties: false, queryFilter: true (automatically applied)
// Override if needed (e.g., for flexible data structures)
const flexibleSchema = withSchema({
type: 'object',
additionalProperties: true, // Explicitly allow extra properties
properties: {
address: {
type: 'object',
additionalProperties: true, // Nested objects also respect explicit settings
},
},
});
NoSQL Injection Protection with queryFilter
The queryFilter keyword provides comprehensive protection against NoSQL injection attacks:
$eq, $ne, $gt, $gte, $lt, $lte, $in, $nin, $regex, $options, $and, $or)$regex patterns using safe-regex to prevent catastrophic backtracking$regex patterns to 100 characters maximum__proto__, constructor, and prototype// Valid query with allowed operators
const validQuery = {
email: { $in: ['user@example.com', 'admin@example.com'] },
age: { $gte: 18, $lte: 65 },
};
// Invalid queries are automatically rejected
const invalidQuery1 = {
email: 'user@example.com',
$where: 'sleep(10000)', // ❌ Forbidden operator
};
const invalidQuery2 = {
email: { $regex: '^(a+)+$' }, // ❌ ReDoS pattern
};
const invalidQuery3 = {
email: 'user@example.com',
__proto__: { isAdmin: true }, // ❌ Prototype pollution attempt
};
withRouteThe 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 with requestBody, requestParams, and requestQuerycontext: An object with db connections and the original request objectws)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:
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 on 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)))
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.
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);
Let's walk through the process of building a complete service from scratch.
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 };
};
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,
});
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'],
});
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.
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');
});
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);
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 a Result type, short-circuiting on errorsflatMapAsync: Chains asynchronous operations that return a Result type, enabling error-safe compositionimport { 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.
The SDK provides several powerful combinators for building robust, composable functions:
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 LoggingThe withLogging combinator wraps any function to provide logging capabilities with configurable options.
What does withLogging do?
info, debug, warn, error, fatal, and traceimport { 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]',
},
],
});
All log levels produce structured logs with:
// Example log output
{
"args": [{"params": {"requestBody": {"name": "John"}}}],
"functionName": "createUserHandler",
"msg": "Function: createUserHandler",
"result": {"user": {"id": "123", "name": "John"}}
}
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,
});
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)
),
})
);
The withLogging function automatically sanitizes sensitive objects in logs:
🗄️ [Database]⚙️ [Config]📧 [MailService]📤 [Request]/📥 [Response]📝 [Logger]This prevents logging of sensitive data while still providing useful debugging information.
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 valuesThe 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 EThis makes error handling explicit in your type signatures and enables composition patterns with flatMap and flatMapAsync.
While Result types are preferred for business logic composition, you can still throw errors when appropriate. This is common in:
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.
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:
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,
});
The SDK includes complete services for all major functionality areas. Each service provides a full set of CRUD operations and specialized endpoints:
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:
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:
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:
E-commerce product management:
import { productService } from '@nodeblocks/backend-sdk';
// Register product service
app.use(
'/api/products',
productService(database, config, { fileStorageDriver })
);
Features:
Multi-tenant organization management:
import { organizationService } from '@nodeblocks/backend-sdk';
// Register organization service
app.use('/api/organizations', organizationService(database, config));
Features:
Order processing and management:
import { orderService } from '@nodeblocks/backend-sdk';
// Register order service
app.use('/api/orders', orderService(database, config));
Features:
Product categorization system:
import { categoryService } from '@nodeblocks/backend-sdk';
// Register category service
app.use('/api/categories', categoryService(database, config));
Features:
Dynamic attribute management:
import { attributesService } from '@nodeblocks/backend-sdk';
// Register attributes service
app.use('/api/attributes', attributesService(database, config));
Features:
Identity management and administration:
import { identityService } from '@nodeblocks/backend-sdk';
// Register identity service
app.use('/api/identities', identityService(database, config));
Features:
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
);
Authentication Routes:
registerCredentialsRoute, loginWithCredentialsRoute, loginWithOnetimeTokenRouteemailVerificationRoute, confirmEmailRoute, changeEmailRoute, confirmNewEmailRoutesendResetPasswordLinkEmailRoute, completePasswordResetRoute, changePasswordRoutedeactivateRoute, activateRoute, refreshTokenRoute, deleteRefreshTokensRouteresendMfaCodeRoute, verifyMfaCodeRoutegoogleOAuthRoute, googleOAuthCallbackRoutetwitterOAuthRoute, twitterOAuthCallbackRoutelineOAuthRoute, lineOAuthCallbackRouteUser Routes:
createUserRoute, getUserRoute, findUsersRoute, updateUserRoute, deleteUserRoutelockUserRoute, unlockUserRoute, getAvatarUploadUrlRoutecreateProfileFollowRoute, deleteProfileFollowRoute, getProfileFollowersRoutecreateOrganizationFollowRoute, deleteOrganizationFollowRoutecreateProductLikeRoute, deleteProductLikeRouteProduct Routes:
createProductRoute, getProductRoute, findProductsRoute, updateProductRoute, deleteProductRoutecreateProductBatchRoute, updateProductBatchRoute, deleteProductBatchRoutecopyProductRoute, copyProductBatchRoutegetProductImageUploadUrlRoute, createProductImageRoute, deleteProductImageRoutecreateProductVariantRoute, getProductVariantRoute, updateProductVariantRoute, deleteProductVariantRoutefindProductVariantsRoute, createProductVariantBulkRoute, updateProductVariantBulkRoute, deleteProductVariantBulkRoutegetProductLikersRouteChat Routes:
createChannelRoute, getChannelRoute, findChannelsRoute, updateChannelRoute, deleteChannelRoutecreateChatSubscriptionRoute, getChatSubscriptionRoute, findChatSubscriptionsRoute, deleteChatSubscriptionRoutecreateChatMessageRoute, getChatMessageRoute, findChatMessagesRoute, updateChatMessageRoute, deleteChatMessageRoutegetChatMessageAttachmentUrlRoute, createChatMessageAttachmentRoute, deleteChatMessageAttachmentRoutegetChannelIconUploadUrlRoute, createChatMessageTemplateRoute, getChatMessageTemplateRouteupdateChatMessageTemplateRoute, deleteChatMessageTemplateRoute, findChatMessageTemplatesRoutefindChatMessageTemplatesForOrganizationRoute, getChannelMessagesRouteupsertChatChannelReadStateRoute, streamChatMessagesRouteOrganization Routes:
createOrganizationRoute, getOrganizationRoute, findOrganizationsRoute, updateOrganizationRoute, deleteOrganizationRouteOrder Routes:
createOrderRoute, getOrderRoute, findOrdersRoute, updateOrderRoute, deleteOrderRouteCategory Routes:
createCategoryRoute, getCategoryRoute, findCategoriesRoute, updateCategoryRoute, deleteCategoryRouteenableCategoryRoute, disableCategoryRouteAttributes Routes:
createAttributeRoute, getAttributeRoute, findAttributesRoute, updateAttributeRoute, deleteAttributeRouteIdentity Routes:
findIdentitiesRoute, getIdentityRoute, updateIdentityRoute, deleteIdentityRouteAuthentication Schemas:
registerCredentialsSchema, loginWithCredentialsSchema, loginWithOnetimeTokenSchemaemailVerificationSchema, confirmEmailSchema, changeEmailSchema, confirmNewEmailSchemasendResetPasswordLinkEmailSchema, completePasswordResetSchema, changePasswordSchemadeactivateSchema, activateSchema, refreshTokenSchema, deleteRefreshTokensSchemaresendMfaCodeSchema, verifyMfaCodeSchemagoogleOAuthSchema, googleOAuthCallbackSchematwitterOAuthSchema, twitterOAuthCallbackSchemalineOAuthSchema, lineOAuthCallbackSchemaUser Schemas:
createUserSchema, updateUserSchema, userSchema, profileIdPathParameteravatarSchema, createProfileFollowSchema, createOrganizationFollowSchema, createProductLikeSchemaProduct Schemas:
createProductSchema, updateProductSchema, productSchemacreateProductBatchSchema, updateProductBatchSchema, deleteProductBatchSchema, copyProductBatchSchemacreateProductVariantSchema, updateProductVariantSchema, productVariantSchemacreateProductVariantBulkSchema, updateProductVariantBulkSchema, deleteProductVariantBulkSchemaChat Schemas:
createChannelSchema, updateChannelSchema, channelSchemacreateChatSubscriptionSchema, chatSubscriptionSchemacreateChatMessageSchema, updateChatMessageSchema, chatMessageSchemacreateChatMessageTemplateSchema, updateChatMessageTemplateSchema, chatMessageTemplateSchemaupsertChatChannelReadStateSchema, chatChannelReadStateSchemaOrganization Schemas:
createOrganizationSchema, updateOrganizationSchema, organizationSchemaorganizationCommonSchema, organizationAdminApprovedSchemaOrder Schemas:
createOrderSchema, updateOrderSchema, orderSchemaCategory Schemas:
createCategorySchema, updateCategorySchema, categorySchemaAttributes Schemas:
createAttributeSchema, updateAttributeSchema, attributeSchemaCommon Schemas:
baseSchema, baseUpdateSchema, arrayOfStringsSchemaauditSchema, paginationSchema, fileSchema, addressSchema, contactSchemapaginationQueryParametersSchema, contentLengthQueryParameterAuthentication Handlers:
registerCredentials, loginWithCredentials, loginWithOnetimeTokenemailVerification, confirmEmail, changeEmail, confirmNewEmailsendResetPasswordLinkEmail, completePasswordReset, changePassworddeactivate, activate, refreshToken, deleteRefreshTokensresendMfaCode, verifyMfaCodegoogleOAuth, googleOAuthCallbacktwitterOAuth, twitterOAuthCallbacklineOAuth, lineOAuthCallbackUser Handlers:
createUser, getUserById, findUsers, updateUser, deleteUserlockUser, unlockUser, normalizeUserTerminator, normalizeUsersListTerminatordeleteUserTerminator, lockUserTerminator, unlockUserTerminatorcreateProfileFollow, deleteProfileFollow, getProfileFollowerscreateOrganizationFollow, deleteOrganizationFollowcreateProductLike, deleteProductLike, findProfilesByIdentityIdProduct Handlers:
createProduct, getProductById, findProducts, updateProduct, deleteProductcreateProductBatch, updateProductBatch, deleteProductBatchcopyProduct, copyProductBatchnormalizeProductTerminator, normalizeProductsListTerminatordeleteProductTerminator, deleteBatchProductsTerminatorcreateProductVariant, getProductVariant, updateProductVariant, deleteProductVariantfindProductVariants, createProductVariantBulk, updateProductVariantBulk, deleteProductVariantBulkgetProductLikersChat Handlers:
createChannel, getChannelById, findChannels, updateChannel, deleteChannelcreateChatSubscription, getChatSubscriptionById, findChatSubscriptions, deleteChatSubscriptioncreateChatMessage, getChatMessageById, findChatMessages, updateChatMessage, deleteChatMessagegetChatMessageAttachmentUrl, createChatMessageAttachment, deleteChatMessageAttachmentgetChannelIconUploadUrl, createChatMessageTemplate, getChatMessageTemplateByIdupdateChatMessageTemplate, deleteChatMessageTemplate, findChatMessageTemplatesfindChatMessageTemplatesForOrganization, getChannelMessagesupsertChatChannelReadState, streamChatMessagesOrganization Handlers:
createOrganization, getOrganizationById, findOrganizations, updateOrganization, deleteOrganizationnormalizeOrganizationTerminator, normalizeOrganizationsListTerminator, deleteOrganizationTerminatorOrder Handlers:
createOrder, getOrderById, findOrders, updateOrder, deleteOrdernormalizeOrderTerminator, normalizeOrdersListTerminator, deleteOrderTerminatorCategory Handlers:
createCategory, getCategoryById, findCategories, updateCategory, deleteCategoryenableCategory, disableCategorynormalizeCategoryTerminator, normalizeCategoriesListTerminatordeleteCategoryTerminator, enableCategoryTerminator, disableCategoryTerminatorAttributes Handlers:
createAttributeGroup, getAttributeGroupById, findAttributeGroups, updateAttributeGroup, deleteAttributeGroupnormalizeAttributeGroupTerminator, normalizeAttributesListTerminator, deleteAttributeGroupTerminatorIdentity Handlers:
findIdentities, getIdentityById, updateIdentity, deleteIdentitynormalizeIdentitiesWithoutPassword, normalizeIdentity, deleteIdentityTerminatorAuthentication 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)Authentication Features:
registerCredentialsFeature, loginWithCredentialsFeature, loginWithOnetimeTokenFeatureemailVerificationFeature, confirmEmailFeature, changeEmailFeature, confirmNewEmailFeaturesendResetPasswordLinkEmailFeature, completePasswordResetFeature, changePasswordFeaturedeactivateFeature, activateFeature, refreshTokenFeature, deleteRefreshTokensFeatureresendMfaCodeFeature, verifyMfaCodeFeaturegoogleOAuthFeature, googleOAuthCallbackFeaturetwitterOAuthFeature, twitterOAuthCallbackFeaturelineOAuthFeature, lineOAuthCallbackFeatureUser Features:
createUserFeature, getUserFeatures, findUsersFeatures, editUserFeatures, deleteUserFeatureslockUserFeatures, unlockUserFeatures, getAvatarUploadUrlFeaturecreateProfileFollowFeature, deleteProfileFollowFeature, getProfileFollowersFeaturecreateOrganizationFollowFeature, deleteOrganizationFollowFeaturecreateProductLikeFeature, deleteProductLikeFeature, findProfilesByIdentityIdFeatureProduct Features:
createProductFeature, getProductFeatures, findProductsFeatures, editProductFeatures, deleteProductFeaturescreateProductBatchFeature, editProductBatchFeatures, deleteProductBatchFeaturescopyProductFeatures, copyProductBatchFeaturesgetProductImageUploadUrlFeature, createProductImageFeature, deleteProductImageFeaturecreateProductVariantFeature, getProductVariantFeature, updateProductVariantFeature, deleteProductVariantFeaturefindProductVariantsFeature, createProductVariantBulkFeature, updateProductVariantBulkFeature, deleteProductVariantBulkFeaturegetProductLikersFeatureChat Features:
createChannelFeature, getChannelFeature, findChannelsFeature, updateChannelFeature, deleteChannelFeaturecreateChatSubscriptionFeature, getChatSubscriptionFeature, findChatSubscriptionsFeature, deleteChatSubscriptionFeaturecreateChatMessageFeature, getChatMessageFeature, findChatMessagesFeature, updateChatMessageFeature, deleteChatMessageFeaturegetChatMessageAttachmentUrlFeature, createChatMessageAttachmentFeature, deleteChatMessageAttachmentFeaturegetChannelIconUploadUrlFeature, createChatMessageTemplateFeature, getChatMessageTemplateFeatureupdateChatMessageTemplateFeature, deleteChatMessageTemplateFeature, findChatMessageTemplatesFeaturefindChatMessageTemplatesForOrganizationFeature, getChannelMessagesFeatureupsertChatChannelReadStateFeature, streamChatMessagesFeatureOrganization Features:
createOrganizationFeature, getOrganizationFeature, findOrganizationsFeature, updateOrganizationFeature, deleteOrganizationFeatureOrder Features:
createOrderFeature, getOrderFeature, findOrdersFeature, updateOrderFeature, deleteOrderFeatureCategory Features:
createCategoryFeature, getCategoryFeature, findCategoriesFeature, updateCategoryFeature, deleteCategoryFeatureenableCategoryFeature, disableCategoryFeatureAttributes Features:
createAttributeFeature, getAttributeFeature, findAttributesFeature, updateAttributeFeature, deleteAttributeFeatureIdentity Features:
findIdentitiesFeature, getIdentityFeature, updateIdentityFeature, deleteIdentityFeaturewithPaginationThe 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.
withPagination do?find method to automatically apply pagination parameters (page, limit).total, totalPages, hasNext, hasPrev, etc.) into the handler payload.withPagination:import { withPagination } from '@nodeblocks/backend-sdk';
const paginatedHandler = withPagination(myHandler);
Handler signature: Your handler receives a payload whose context.db collections have a monkey-patched find method. When you call .find().toArray(), pagination is applied and metadata is injected into the payload.
Query parameter handling: The page and limit parameters are extracted from requestQuery and used for pagination. Default values are page = 1 and limit = 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 a paginationResult property with the following structure:
{
data: [...],
pagination: {
page: number,
limit: number,
total: number,
totalPages: number,
hasNext: boolean,
hasPrev: boolean,
}
}
__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,
// }
// }
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 SupportThe 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?
deleteOne and deleteMany operations set deletedAt timestampfind, findOne, and countDocuments automatically exclude soft-deleted recordsimport { 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 records
withPaginatedProperty - Property PaginationThe withPaginatedProperty combinator adds pagination to array properties within documents, useful for paginating nested arrays like organization members or product reviews.
What does withPaginatedProperty do?
['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 ApplicationThe 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?
Result return typesimport { 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 TerminationThe orThrow combinator handles Result types by either returning success data or throwing appropriate errors based on error type mapping.
What does orThrow do?
NodeblocksError with proper status codesimport { 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 ValidationThe hasValue combinator validates that a value is not null, undefined, or empty, useful for input validation and data integrity checks.
What does hasValue do?
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 LiftingThe lift combinator lifts a synchronous function to work with promises, enabling composition of sync and async functions.
What does lift do?
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 ChainingThe flatMap and flatMapAsync combinators enable chaining of operations that return Result types, providing safe error handling in composition chains.
What do they do?
Result typesimport { 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
);
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.
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 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 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
);
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
);
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 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 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');
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);
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);
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
}
}
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 }))
);
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.
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 product
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 })
);
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: 'user@example.com',
from: 'noreply@company.com',
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 }));
Social authentication providers:
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 }));
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 }));
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 service
app.use('/api', authService(db, config, { lineOAuthDriver }));
Environment-based configuration:
// .env
MONGODB_URI=mongodb://localhost:27017
MONGODB_DB_NAME=myapp
GOOGLE_CLOUD_PROJECT_ID=my-project
GOOGLE_CLOUD_BUCKET=my-storage-bucket
SENDGRID_API_KEY=your-sendgrid-key
GOOGLE_CLIENT_ID=your-google-client-id
GOOGLE_CLIENT_SECRET=your-google-client-secret
// app.ts
import {
withMongo,
createFileStorageDriver,
getSendGridClient,
createGoogleOAuthDriver
} from '@nodeblocks/backend-sdk';
// Configure all drivers
const connectToDatabase = withMongo(
process.env.MONGODB_URI!,
process.env.MONGODB_DB_NAME!,
'admin',
'password'
);
// Get collections as needed
const users = await connectToDatabase('users');
const products = await connectToDatabase('products');
const orders = await connectToDatabase('orders');
const fileStorage = await createFileStorageDriver(
process.env.GOOGLE_CLOUD_PROJECT_ID!,
process.env.GOOGLE_CLOUD_BUCKET!
);
const mailService = getSendGridClient(process.env.SENDGRID_API_KEY!);
const googleOAuthDriver = createGoogleOAuthDriver({
clientId: process.env.GOOGLE_CLIENT_ID!,
clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
redirectUri: 'http://localhost:3000/auth/google/callback'
});
// Use with services
app.use('/api', authService(users, config, {
mailService,
googleOAuthDriver
}));
app.use('/api/users', userService(users, config, { fileStorageDriver: fileStorage }));
app.use('/api/products', productService(products, config, { fileStorageDriver: fileStorage }));
app.use('/api/orders', orderService(orders, config));
All drivers are fully typed for TypeScript support:
import type {
FileStorageDriver,
MailService,
GoogleOAuthDriver,
TwitterOAuthDriver,
LineOAuthDriver,
} from '@nodeblocks/backend-sdk';
// Type-safe driver usage
function processFile(fileStorage: FileStorageDriver, fileName: string) {
return fileStorage.generateSignedDownloadUrl(fileName);
}
function sendNotification(mailService: MailService, email: string) {
return mailService.sendMail({
to: email,
from: 'noreply@company.com',
subject: 'Notification',
text: 'You have a new notification',
});
}
├── src/
│ ├── blocks/ # Reusable business logic blocks
│ │ ├── authentication.ts # Auth utilities, token management, password hashing
│ │ ├── avatar.ts # Avatar upload and management
│ │ ├── chat/ # Chat system blocks
│ │ │ ├── channel.ts # Channel creation, management, icons
│ │ │ ├── message.ts # Message handling, templates, attachments
│ │ │ ├── message-template.ts # Message template management
│ │ │ ├── read-state.ts # Read state tracking and updates
│ │ │ └── subscription.ts # Channel subscriptions and membership
│ │ ├── common.ts # Common utilities and helpers
│ │ ├── file-storage.ts # File upload, download, CDN integration
│ │ ├── identity.ts # Identity management and administration
│ │ ├── location.ts # Address validation, geocoding, location services
│ │ ├── mongo.ts # MongoDB utilities, findResources, error handling
│ │ ├── oauth/ # OAuth provider integrations
│ │ │ ├── google.ts # Google OAuth implementation
│ │ │ ├── line.ts # LINE OAuth implementation
│ │ │ ├── twitter.ts # Twitter OAuth implementation
│ │ │ └── utils.ts # OAuth utilities and common functions
│ │ ├── order.ts # Order creation, status management, items
│ │ ├── organization.ts # Organization management, members, roles
│ │ ├── products.ts # Product CRUD, variants, images, inventory
│ │ ├── profiles.ts # Profile management, social features, follows
│ │ └── users.ts # User management, profiles, social features
│ ├── drivers/ # External service drivers
│ │ ├── file-storage.ts # Google Cloud Storage driver with signed URLs
│ │ ├── mongo.ts # MongoDB driver with curried connection utility
│ │ ├── oauth/ # OAuth provider drivers
│ │ │ ├── google.ts # Google OAuth 2.0 implementation
│ │ │ ├── line/ # LINE OAuth implementation
│ │ │ │ ├── index.ts # LINE OAuth driver
│ │ │ │ ├── strategy.ts # Passport LINE strategy
│ │ │ │ └── passport-line-augment.d.ts # TypeScript augmentations
│ │ │ └── twitter/ # Twitter OAuth implementation
│ │ │ ├── index.ts # Twitter OAuth driver
│ │ │ ├── strategy.ts # Passport Twitter strategy
│ │ │ ├── types.ts # Twitter-specific types
│ │ │ └── passport-twitter-augment.d.ts # TypeScript augmentations
│ │ └── sendgrid.ts # SendGrid email service driver
│ ├── features/ # Feature definitions (composition of routes and schemas)
│ │ ├── attributes.ts # Dynamic attributes
│ │ ├── authentication.ts # Authentication features
│ │ ├── category.ts # Category management
│ │ ├── chat/ # Chat system features
│ │ ├── identity.ts # Identity management
│ │ ├── invitation.ts # User invitations
│ │ ├── location.ts # Location services
│ │ ├── oauth/ # OAuth features
│ │ ├── order.ts # Order processing
│ │ ├── organization.ts # Organization management
│ │ ├── product.ts # Product management
│ │ ├── profiles.ts # Profile management
│ │ └── user.ts # User management
│ ├── handlers/ # Business logic for routes
│ │ ├── attributes.ts # Attribute handlers
│ │ ├── authentication.ts # Auth handlers
│ │ ├── category.ts # Category handlers
│ │ ├── chat/ # Chat handlers
│ │ ├── invitation.ts # Invitation handlers
│ │ ├── order.ts # Order handlers
│ │ ├── organization.ts # Organization handlers
│ │ ├── product.ts # Product handlers
│ │ ├── user.ts # User handlers
│ │ └── utils.ts # Handler utilities
│ ├── middlewares/ # Express middleware
│ │ └── error.ts # Error handling middleware
│ ├── primitives/ # Core SDK functions
│ │ ├── combinators.ts # Function combinators (compose, lift, flatMap, applyPayloadArgs, orThrow, hasValue, withSoftDelete, withPaginatedProperty)
│ │ ├── error.ts # Error handling
│ │ ├── rx.ts # RxJS utilities
│ │ ├── service.ts # Service definition
│ │ ├── types.ts # Type definitions
│ │ ├── withLogging.ts # Logging combinator
│ │ ├── withPagination.ts # Pagination utility
│ │ ├── withRoute.ts # Route definition
│ │ └── withSchema.ts # Schema validation
│ ├── routes/ # Route definitions using withRoute
│ │ ├── attributes.ts # Attribute routes
│ │ ├── authentication.ts # Auth routes
│ │ ├── category.ts # Category routes
│ │ ├── chat/ # Chat routes
│ │ ├── identity.ts # Identity routes
│ │ ├── invitation.ts # Invitation routes
│ │ ├── location.ts # Location routes
│ │ ├── oauth/ # OAuth routes
│ │ ├── order.ts # Order routes
│ │ ├── organization.ts # Organization routes
│ │ ├── product.ts # Product routes
│ │ ├── profiles.ts # Profile routes
│ │ └── user.ts # User routes
│ ├── schemas/ # Schema definitions using withSchema
│ │ ├── address.ts # Address schemas
│ │ ├── attributes.ts # Attribute schemas
│ │ ├── authentication.ts # Auth schemas
│ │ ├── avatar.ts # Avatar schemas
│ │ ├── category.ts # Category schemas
│ │ ├── chat/ # Chat schemas
│ │ ├── common.ts # Common schemas
│ │ ├── file-storage.ts # File storage schemas
│ │ ├── identity.ts # Identity schemas
│ │ ├── invitation.ts # Invitation schemas
│ │ ├── location.ts # Location schemas
│ │ ├── oauth/ # OAuth schemas
│ │ ├── order.ts # Order schemas
│ │ ├── organization.ts # Organization schemas
│ │ ├── product.ts # Product schemas
│ │ ├── profile.ts # Profile schemas
│ │ ├── schemas.ts # Common schema definitions
│ │ └── user.ts # User schemas
│ ├── services/ # Pre-built services
│ │ ├── attributes.ts # Attributes service
│ │ ├── authentication.ts # Authentication service
│ │ ├── category.ts # Category service
│ │ ├── chat.ts # Chat service
│ │ ├── identity.ts # Identity service
│ │ ├── location.ts # Location service
│ │ ├── order.ts # Order service
│ │ ├── organization.ts # Organization service
│ │ ├── product.ts # Product service
│ │ └── user.ts # User service
│ ├── types/ # TypeScript type definitions
│ │ ├── authentication.ts # Auth types
│ │ ├── email.ts # Email types
│ │ ├── http.ts # HTTP types
│ │ ├── oauth.ts # OAuth types
│ │ ├── organization.ts # Organization types
│ │ └── utils.ts # Utility types
│ ├── utils/ # Utility functions
│ │ ├── authentication.ts # Auth utilities
│ │ ├── common.ts # Common utilities
│ │ ├── entity.ts # Entity utilities
│ │ └── logger.ts # Logging utilities
│ ├── validators/ # Validation predicates and validators
│ │ ├── category.ts # Category validators
│ │ ├── channelExists.ts # Channel existence validators
│ │ ├── checkIdentityType.ts # Identity type validators
│ │ ├── hasOrgRole.ts # Organization role validators
│ │ ├── isAuthenticated.ts # Authentication validators
│ │ ├── isSelf.ts # Self-access validators
│ │ ├── some.ts # Logical OR validators
│ │ ├── validateChannelAccess.ts # Channel access validators
│ │ ├── validateMessageAccess.ts # Message access validators
│ │ ├── validateOrganizationAccess.ts # Organization access validators
│ │ ├── validateResourceAccess.ts # Resource access validators
│ │ ├── validateUserProfileAccess.ts # Profile access validators
│ │ └── validators.ts # Common validators
│ └── index.ts # Main entry point
├── tests/ # Test files
├── docs/ # Documentation
├── examples/ # Usage examples
├── scripts/ # Build and utility scripts
├── coverage/ # Test coverage reports
├── dist/ # Compiled output
└── package.json
Here's a complete example showing how to build a full-featured application with all services:
import express from 'express';
import { createServer } from 'http';
import { WebSocketServer } from 'ws';
import { MongoClient } from 'mongodb';
import {
// Services
authService,
chatService,
userService,
productService,
organizationService,
orderService,
categoryService,
attributesService,
identityService,
// Middleware
nodeBlocksErrorMiddleware,
} from '@nodeblocks/backend-sdk';
const app = express();
const server = createServer(app);
const wss = new WebSocketServer({ server });
// Database connection
const client = new MongoClient(process.env.MONGODB_URI!);
await client.connect();
const db = client.db('myapp');
// Configuration
const config = {
authSecrets: {
accessTokenSecret: process.env.ACCESS_TOKEN_SECRET!,
refreshTokenSecret: process.env.REFRESH_TOKEN_SECRET!,
onetimeTokenSecret: process.env.ONETIME_TOKEN_SECRET!,
},
identity: {
type: 'user',
organizationId: process.env.DEFAULT_ORG_ID!,
},
organization: {
id: process.env.DEFAULT_ORG_ID!,
},
};
// Optional drivers
const drivers = {
fileStorageDriver: {
// Your file storage implementation
upload: async (file) => ({ url: 'https://cdn.example.com/file.jpg' }),
},
mailService: {
// Your email service implementation
send: async (email) => console.log('Email sent:', email),
},
googleOAuthDriver: {
// Google OAuth implementation
getAuthUrl: () => 'https://accounts.google.com/oauth/authorize...',
exchangeCode: async (code) => ({ accessToken: '...', userInfo: {} }),
},
webSocketServer: wss,
};
// Register all services
app.use('/api', authService(db, config, drivers));
app.use('/api/chat', chatService(db, config, { webSocketServer: wss }));
app.use(
'/api/users',
userService(db, config, { fileStorageDriver: drivers.fileStorageDriver })
);
app.use(
'/api/products',
productService(db, config, { fileStorageDriver: drivers.fileStorageDriver })
);
app.use('/api/organizations', organizationService(db, config));
app.use('/api/orders', orderService(db, config));
app.use('/api/categories', categoryService(db, config));
app.use('/api/attributes', attributesService(db, config));
app.use('/api/identities', identityService(db, config));
// Error handling middleware
app.use(nodeBlocksErrorMiddleware());
// Health check
app.get('/health', (req, res) => {
res.json({ status: 'ok', timestamp: new Date().toISOString() });
});
const PORT = process.env.PORT || 3000;
server.listen(PORT, () => {
console.log(`🚀 Server running on port ${PORT}`);
console.log(`📡 WebSocket available at ws://localhost:${PORT}`);
console.log(`🔗 API endpoints available at http://localhost:${PORT}/api`);
});
import { authService } from '@nodeblocks/backend-sdk';
// Register authentication with OAuth providers
app.use(
'/api',
authService(
database,
{
authSecrets: {
accessTokenSecret: process.env.ACCESS_TOKEN_SECRET,
refreshTokenSecret: process.env.REFRESH_TOKEN_SECRET,
onetimeTokenSecret: process.env.ONETIME_TOKEN_SECRET,
},
identity: { type: 'user', organizationId: 'org123' },
organization: { id: 'org123' },
},
{
mailService: myMailService,
googleOAuthDriver: myGoogleDriver,
twitterOAuthDriver: myTwitterDriver,
lineOAuthDriver: myLineDriver,
}
)
);
import { chatService } from '@nodeblocks/backend-sdk';
import { WebSocketServer } from 'ws';
const wss = new WebSocketServer({ server });
// Register chat service with real-time capabilities
app.use(
'/api/chat',
chatService(database, config, {
fileStorageDriver: myFileDriver,
webSocketServer: wss,
})
);
import { productService } from '@nodeblocks/backend-sdk';
// Register product service with image upload support
app.use(
'/api/products',
productService(database, config, {
fileStorageDriver: myFileStorageDriver,
})
);
import {
compose,
withRoute,
withSchema,
defService,
} from '@nodeblocks/backend-sdk';
import { isAuthenticated, checkIdentityType } from '@nodeblocks/backend-sdk';
// Custom feature with authentication and admin access
const customFeature = compose(
withSchema({
type: 'object',
properties: {
name: { type: 'string' },
description: { type: 'string' },
},
required: ['name'],
}),
withRoute({
method: 'POST',
path: '/custom-resource',
validators: [isAuthenticated(), checkIdentityType(['admin'])],
handler: async (payload) => {
const { name, description } = payload.params.requestBody;
// Your custom logic here
return { success: true, data: { name, description } };
},
})
);
// Create service from custom feature
const customService = defService(customFeature);
app.use('/api/custom', customService);
npm install @nodeblocks/backend-sdk
# Install dependencies
npm install
# Run tests
npm test
# Run tests with coverage
npm run test:coverage
# Build the project
npm run build
# Start development server
npm run dev
FAQs
Type-safe Nodeblocks backend implementation
We found that @nodeblocks/backend-sdk demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 4 open source maintainers collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
The Axios compromise shows how time-dependent dependency resolution makes exposure harder to detect and contain.

Research
A supply chain attack on Axios introduced a malicious dependency, plain-crypto-js@4.2.1, published minutes earlier and absent from the project’s GitHub releases.

Research
Malicious versions of the Telnyx Python SDK on PyPI delivered credential-stealing malware via a multi-stage supply chain attack.