npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

@nodeblocks/backend-sdk

v0.9.0

Published

Type-safe Nodeblocks backend implementation

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 with requestBody, requestParams, and requestQuery
  • context: An object with db connections and the original request object

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

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 promises
  • flatMap: Chains synchronous operations that return a Result type, short-circuiting on errors
  • flatMapAsync: Chains asynchronous operations that return a Result type, 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, and trace
  • 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 present
  • isUUID(key): Validates UUID format
  • isNumber(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 T
  • Err<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, loginWithOnetimeTokenRoute
  • emailVerificationRoute, confirmEmailRoute, changeEmailRoute, confirmNewEmailRoute
  • sendResetPasswordLinkEmailRoute, completePasswordResetRoute, changePasswordRoute
  • deactivateRoute, activateRoute, refreshTokenRoute, deleteRefreshTokensRoute
  • resendMfaCodeRoute, verifyMfaCodeRoute
  • googleOAuthRoute, googleOAuthCallbackRoute
  • twitterOAuthRoute, twitterOAuthCallbackRoute
  • lineOAuthRoute, lineOAuthCallbackRoute

User Routes:

  • createUserRoute, getUserRoute, findUsersRoute, updateUserRoute, deleteUserRoute
  • lockUserRoute, unlockUserRoute, getAvatarUploadUrlRoute
  • createProfileFollowRoute, deleteProfileFollowRoute, getProfileFollowersRoute
  • createOrganizationFollowRoute, deleteOrganizationFollowRoute
  • createProductLikeRoute, deleteProductLikeRoute

Product Routes:

  • createProductRoute, getProductRoute, findProductsRoute, updateProductRoute, deleteProductRoute
  • createProductBatchRoute, updateProductBatchRoute, deleteProductBatchRoute
  • copyProductRoute, copyProductBatchRoute
  • getProductImageUploadUrlRoute, createProductImageRoute, deleteProductImageRoute
  • createProductVariantRoute, getProductVariantRoute, updateProductVariantRoute, deleteProductVariantRoute
  • findProductVariantsRoute, createProductVariantBulkRoute, updateProductVariantBulkRoute, deleteProductVariantBulkRoute
  • getProductLikersRoute

Chat Routes:

  • createChannelRoute, getChannelRoute, findChannelsRoute, updateChannelRoute, deleteChannelRoute
  • createChatSubscriptionRoute, getChatSubscriptionRoute, findChatSubscriptionsRoute, deleteChatSubscriptionRoute
  • createChatMessageRoute, getChatMessageRoute, findChatMessagesRoute, updateChatMessageRoute, deleteChatMessageRoute
  • getChatMessageAttachmentUrlRoute, createChatMessageAttachmentRoute, deleteChatMessageAttachmentRoute
  • getChannelIconUploadUrlRoute, createChatMessageTemplateRoute, getChatMessageTemplateRoute
  • updateChatMessageTemplateRoute, deleteChatMessageTemplateRoute, findChatMessageTemplatesRoute
  • findChatMessageTemplatesForOrganizationRoute, getChannelMessagesRoute
  • upsertChatChannelReadStateRoute, streamChatMessagesRoute

Organization Routes:

  • createOrganizationRoute, getOrganizationRoute, findOrganizationsRoute, updateOrganizationRoute, deleteOrganizationRoute

Order Routes:

  • createOrderRoute, getOrderRoute, findOrdersRoute, updateOrderRoute, deleteOrderRoute

Category Routes:

  • createCategoryRoute, getCategoryRoute, findCategoriesRoute, updateCategoryRoute, deleteCategoryRoute
  • enableCategoryRoute, disableCategoryRoute

Attributes Routes:

  • createAttributeRoute, getAttributeRoute, findAttributesRoute, updateAttributeRoute, deleteAttributeRoute

Identity Routes:

  • findIdentitiesRoute, getIdentityRoute, updateIdentityRoute, deleteIdentityRoute

Schemas

Authentication Schemas:

  • registerCredentialsSchema, loginWithCredentialsSchema, loginWithOnetimeTokenSchema
  • emailVerificationSchema, confirmEmailSchema, changeEmailSchema, confirmNewEmailSchema
  • sendResetPasswordLinkEmailSchema, completePasswordResetSchema, changePasswordSchema
  • deactivateSchema, activateSchema, refreshTokenSchema, deleteRefreshTokensSchema
  • resendMfaCodeSchema, verifyMfaCodeSchema
  • googleOAuthSchema, googleOAuthCallbackSchema
  • twitterOAuthSchema, twitterOAuthCallbackSchema
  • lineOAuthSchema, lineOAuthCallbackSchema

User Schemas:

  • createUserSchema, updateUserSchema, userSchema, profileIdPathParameter
  • avatarSchema, createProfileFollowSchema, createOrganizationFollowSchema, createProductLikeSchema

Product Schemas:

  • createProductSchema, updateProductSchema, productSchema
  • createProductBatchSchema, updateProductBatchSchema, deleteProductBatchSchema, copyProductBatchSchema
  • createProductVariantSchema, updateProductVariantSchema, productVariantSchema
  • createProductVariantBulkSchema, updateProductVariantBulkSchema, deleteProductVariantBulkSchema

Chat Schemas:

  • createChannelSchema, updateChannelSchema, channelSchema
  • createChatSubscriptionSchema, chatSubscriptionSchema
  • createChatMessageSchema, updateChatMessageSchema, chatMessageSchema
  • createChatMessageTemplateSchema, updateChatMessageTemplateSchema, chatMessageTemplateSchema
  • upsertChatChannelReadStateSchema, chatChannelReadStateSchema

Organization Schemas:

  • createOrganizationSchema, updateOrganizationSchema, organizationSchema
  • organizationCommonSchema, organizationAdminApprovedSchema

Order Schemas:

  • createOrderSchema, updateOrderSchema, orderSchema

Category Schemas:

  • createCategorySchema, updateCategorySchema, categorySchema

Attributes Schemas:

  • createAttributeSchema, updateAttributeSchema, attributeSchema

Common Schemas:

  • baseSchema, baseUpdateSchema, arrayOfStringsSchema
  • auditSchema, paginationSchema, fileSchema, addressSchema, contactSchema
  • paginationQueryParametersSchema, contentLengthQueryParameter

Handlers

Authentication Handlers:

  • registerCredentials, loginWithCredentials, loginWithOnetimeToken
  • emailVerification, confirmEmail, changeEmail, confirmNewEmail
  • sendResetPasswordLinkEmail, completePasswordReset, changePassword
  • deactivate, activate, refreshToken, deleteRefreshTokens
  • resendMfaCode, verifyMfaCode
  • googleOAuth, googleOAuthCallback
  • twitterOAuth, twitterOAuthCallback
  • lineOAuth, lineOAuthCallback

User Handlers:

  • createUser, getUserById, findUsers, updateUser, deleteUser
  • lockUser, unlockUser, normalizeUserTerminator, normalizeUsersListTerminator
  • deleteUserTerminator, lockUserTerminator, unlockUserTerminator
  • createProfileFollow, deleteProfileFollow, getProfileFollowers
  • createOrganizationFollow, deleteOrganizationFollow
  • createProductLike, deleteProductLike, findProfilesByIdentityId

Product Handlers:

  • createProduct, getProductById, findProducts, updateProduct, deleteProduct
  • createProductBatch, updateProductBatch, deleteProductBatch
  • copyProduct, copyProductBatch
  • normalizeProductTerminator, normalizeProductsListTerminator
  • deleteProductTerminator, deleteBatchProductsTerminator
  • createProductVariant, getProductVariant, updateProductVariant, deleteProductVariant
  • findProductVariants, createProductVariantBulk, updateProductVariantBulk, deleteProductVariantBulk
  • getProductLikers

Chat Handlers:

  • createChannel, getChannelById, findChannels, updateChannel, deleteChannel
  • createChatSubscription, getChatSubscriptionById, findChatSubscriptions, deleteChatSubscription
  • createChatMessage, getChatMessageById, findChatMessages, updateChatMessage, deleteChatMessage
  • getChatMessageAttachmentUrl, createChatMessageAttachment, deleteChatMessageAttachment
  • getChannelIconUploadUrl, createChatMessageTemplate, getChatMessageTemplateById
  • updateChatMessageTemplate, deleteChatMessageTemplate, findChatMessageTemplates
  • findChatMessageTemplatesForOrganization, getChannelMessages
  • upsertChatChannelReadState, streamChatMessages

Organization Handlers:

  • createOrganization, getOrganizationById, findOrganizations, updateOrganization, deleteOrganization
  • normalizeOrganizationTerminator, normalizeOrganizationsListTerminator, deleteOrganizationTerminator

Order Handlers:

  • createOrder, getOrderById, findOrders, updateOrder, deleteOrder
  • normalizeOrderTerminator, normalizeOrdersListTerminator, deleteOrderTerminator

Category Handlers:

  • createCategory, getCategoryById, findCategories, updateCategory, deleteCategory
  • enableCategory, disableCategory
  • normalizeCategoryTerminator, normalizeCategoriesListTerminator
  • deleteCategoryTerminator, enableCategoryTerminator, disableCategoryTerminator

Attributes Handlers:

  • createAttributeGroup, getAttributeGroupById, findAttributeGroups, updateAttributeGroup, deleteAttributeGroup
  • normalizeAttributeGroupTerminator, normalizeAttributesListTerminator, deleteAttributeGroupTerminator

Identity Handlers:

  • findIdentities, getIdentityById, updateIdentity, deleteIdentity
  • normalizeIdentitiesWithoutPassword, 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, loginWithOnetimeTokenFeature
  • emailVerificationFeature, confirmEmailFeature, changeEmailFeature, confirmNewEmailFeature
  • sendResetPasswordLinkEmailFeature, completePasswordResetFeature, changePasswordFeature
  • deactivateFeature, activateFeature, refreshTokenFeature, deleteRefreshTokensFeature
  • resendMfaCodeFeature, verifyMfaCodeFeature
  • googleOAuthFeature, googleOAuthCallbackFeature
  • twitterOAuthFeature, twitterOAuthCallbackFeature
  • lineOAuthFeature, lineOAuthCallbackFeature

User Features:

  • createUserFeature, getUserFeatures, findUsersFeatures, editUserFeatures, deleteUserFeatures
  • lockUserFeatures, unlockUserFeatures, getAvatarUploadUrlFeature
  • createProfileFollowFeature, deleteProfileFollowFeature, getProfileFollowersFeature
  • createOrganizationFollowFeature, deleteOrganizationFollowFeature
  • createProductLikeFeature, deleteProductLikeFeature, findProfilesByIdentityIdFeature

Product Features:

  • createProductFeature, getProductFeatures, findProductsFeatures, editProductFeatures, deleteProductFeatures
  • createProductBatchFeature, editProductBatchFeatures, deleteProductBatchFeatures
  • copyProductFeatures, copyProductBatchFeatures
  • getProductImageUploadUrlFeature, createProductImageFeature, deleteProductImageFeature
  • createProductVariantFeature, getProductVariantFeature, updateProductVariantFeature, deleteProductVariantFeature
  • findProductVariantsFeature, createProductVariantBulkFeature, updateProductVariantBulkFeature, deleteProductVariantBulkFeature
  • getProductLikersFeature

Chat Features:

  • createChannelFeature, getChannelFeature, findChannelsFeature, updateChannelFeature, deleteChannelFeature
  • createChatSubscriptionFeature, getChatSubscriptionFeature, findChatSubscriptionsFeature, deleteChatSubscriptionFeature
  • createChatMessageFeature, getChatMessageFeature, findChatMessagesFeature, updateChatMessageFeature, deleteChatMessageFeature
  • getChatMessageAttachmentUrlFeature, createChatMessageAttachmentFeature, deleteChatMessageAttachmentFeature
  • getChannelIconUploadUrlFeature, createChatMessageTemplateFeature, getChatMessageTemplateFeature
  • updateChatMessageTemplateFeature, deleteChatMessageTemplateFeature, findChatMessageTemplatesFeature
  • findChatMessageTemplatesForOrganizationFeature, getChannelMessagesFeature
  • upsertChatChannelReadStateFeature, streamChatMessagesFeature

Organization Features:

  • createOrganizationFeature, getOrganizationFeature, findOrganizationsFeature, updateOrganizationFeature, deleteOrganizationFeature

Order Features:

  • createOrderFeature, getOrderFeature, findOrdersFeature, updateOrderFeature, deleteOrderFeature

Category Features:

  • createCategoryFeature, getCategoryFeature, findCategoriesFeature, updateCategoryFeature, deleteCategoryFeature
  • enableCategoryFeature, 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' find method 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

  1. Wrap your handler with withPagination:
import { withPagination } from '@nodeblocks/backend-sdk';

const paginatedHandler = withPagination(myHandler);
  1. 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.

  2. 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.

  3. 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,
  }
}

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: deleteOne and deleteMany operations set deletedAt timestamp
  • Filters out deleted records: find, findOne, and countDocuments automatically 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 records

withPaginatedProperty - 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 Result return 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 NodeblocksError with 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 Result types
  • 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 product

File 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