@dpartida/crud-core
v1.0.0
Published
Generic CRUD core functionality for Idrall ERP microservices
Maintainers
Readme
@idrall/crud-core
Generic CRUD core functionality for Idrall ERP microservices built with NestJS, Kysely, and PostgreSQL.
Features
- 🚀 Generic CRUD Operations: List, Get, Create, Update, Delete operations with filtering, sorting, and pagination
- 🔧 Configurable: Per-entity configuration with table mapping and field exclusion
- 🎣 Lifecycle Hooks: Pre/post hooks for create, update, and delete operations
- 🔍 Advanced Filtering: Enhanced query filter system with validation and sanitization
- 📨 Message Pattern Support: Built-in NestJS microservice message pattern handling
- 🛡️ Type Safe: Full TypeScript support with comprehensive type definitions
- 🏗️ Modular Architecture: Dynamic module system with configuration support
Installation
npm install @idrall/crud-corePeer Dependencies
Make sure you have the following peer dependencies installed:
npm install @nestjs/common @nestjs/core @nestjs/microservices kysely reflect-metadata rxjsQuick Start
1. Configure the Module
import { Module } from "@nestjs/common";
import { DynamicCrudModule, CrudConfig } from "@idrall/crud-core";
import { KyselyModule } from "./kysely/kysely.module";
const crudConfig: CrudConfig = {
serviceName: "idrall-crm",
entities: ["customer", "contact", "opportunity", "lead", "campaign"],
defaultTableMapping: {
opportunity: "crm_opportunity",
},
entityConfigs: {
contact: {
excludeFields: ["password_hash"],
hooks: {
preCreate: async (entity, data, user) => {
// Email validation hook
if (
data.email &&
!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(data.email as string)
) {
throw new Error("Invalid email format");
}
return data;
},
},
},
},
};
@Module({
imports: [KyselyModule, DynamicCrudModule.forRoot(crudConfig)],
})
export class AppModule {}2. Provide KyselyService
Create a provider for the KyselyService:
import { Module } from "@nestjs/common";
import { KyselyService } from "./kysely.service";
@Module({
providers: [
KyselyService,
{
provide: "KYSELY_SERVICE",
useExisting: KyselyService,
},
],
exports: ["KYSELY_SERVICE"],
})
export class KyselyModule {}3. Use Message Patterns
Your microservice will automatically handle these message patterns:
idrall-crm.customer.list- List customers with filtering/paginationidrall-crm.customer.get- Get a single customer by IDidrall-crm.customer.create- Create a new customeridrall-crm.customer.update- Update an existing customeridrall-crm.customer.delete- Delete a customer
Configuration
CrudConfig
interface CrudConfig {
serviceName: string; // Microservice name
entities: string[]; // Supported entities
entityConfigs?: Record<string, EntityConfig>; // Per-entity config
defaultTableMapping?: Record<string, string>; // Entity to table mapping
globalHooks?: {
// Global lifecycle hooks
preCreate?: PreCreateHook;
preUpdate?: PreUpdateHook;
postCreate?: PostCreateHook;
postUpdate?: PostUpdateHook;
preDelete?: PreDeleteHook;
postDelete?: PostDeleteHook;
};
}EntityConfig
interface EntityConfig {
tableName?: string; // Override table name
excludeFields?: string[]; // Fields to exclude from responses
hooks?: {
// Entity-specific hooks
preCreate?: PreCreateHook;
preUpdate?: PreUpdateHook;
postCreate?: PostCreateHook;
postUpdate?: PostUpdateHook;
preDelete?: PreDeleteHook;
postDelete?: PostDeleteHook;
};
}Lifecycle Hooks
Hooks allow you to customize behavior at different points in the CRUD lifecycle:
const config: CrudConfig = {
serviceName: "my-service",
entities: ["user"],
entityConfigs: {
user: {
hooks: {
preCreate: async (entity, data, user) => {
// Hash password before creating user
if (data.password) {
data.password_hash = await hashPassword(data.password);
delete data.password;
}
return data;
},
preUpdate: async (entity, id, data, user) => {
// Validate email format
if (
data.email &&
!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(data.email as string)
) {
throw new ValidationError("Invalid email format");
}
return data;
},
postCreate: async (entity, result, user) => {
// Send welcome email after user creation
await sendWelcomeEmail(result.email);
return result;
},
},
},
},
};Query Filtering
The package includes advanced filtering capabilities:
import { QueryFilter } from "@idrall/crud-core";
const filters: QueryFilter[] = [
{ field: "name", operator: "contains", value: "John" },
{ field: "age", operator: "ge", value: 18 },
{ field: "status", operator: "in", value: ["active", "pending"] },
];Supported Operators
eq- Equalne- Not equalgt- Greater thanlt- Less thange- Greater than or equalle- Less than or equalcontains- String contains (case-insensitive)startswith- String starts with (case-insensitive)endswith- String ends with (case-insensitive)in- Value in array
Usage from Gateway
Send requests to your CRM microservice from the gateway:
import { Injectable, Inject } from "@nestjs/common";
import { ClientProxy } from "@nestjs/microservices";
import { lastValueFrom, timeout } from "rxjs";
@Injectable()
export class CustomerGatewayService {
constructor(@Inject("NATS_SERVICE") private client: ClientProxy) {}
async listCustomers(filters: any[] = [], pagination?: any) {
const payload = {
entity: "customer",
filters,
sort: [{ field: "created_at", order: "desc" }],
pagination: pagination || { skip: 0, limit: 20 },
};
return lastValueFrom(
this.client.send("idrall-crm.customer.list", payload).pipe(timeout(10000))
);
}
async createCustomer(data: any) {
const payload = {
entity: "customer",
data,
};
return lastValueFrom(
this.client
.send("idrall-crm.customer.create", payload)
.pipe(timeout(10000))
);
}
}Advanced Usage
Async Configuration
DynamicCrudModule.forRootAsync({
imports: [ConfigModule],
inject: [ConfigService],
useFactory: async (configService: ConfigService): Promise<CrudConfig> => {
return {
serviceName: configService.get("SERVICE_NAME"),
entities: configService.get("SUPPORTED_ENTITIES").split(","),
// ... other config
};
},
});Multiple Feature Modules
@Module({
imports: [
DynamicCrudModule.forFeature({
serviceName: "crm-contacts",
entities: ["contact", "address"],
}),
DynamicCrudModule.forFeature({
serviceName: "crm-sales",
entities: ["opportunity", "quote"],
}),
],
})
export class CrmModule {}Error Handling
The package includes custom error types:
import {
CrudError,
ValidationError,
EntityNotFoundError,
UnauthorizedError,
} from "@idrall/crud-core";
// These errors are automatically handled by the controller
// and converted to appropriate response formatsDocker & AWS Fargate Optimization
The package is optimized for containerized deployments:
- Minimal dependencies to reduce image size
- Efficient connection pooling for PostgreSQL
- Proper error handling for distributed systems
- Health check endpoints included
API Reference
DynamicCrudService
list<T>(query: DynamicQuery): Promise<ListResponse<T>>getOne<T>(entity: string, id: string | number): Promise<T | null>create<T>(entity: string, data: Record<string, unknown>, user?: any): Promise<T>update<T>(entity: string, id: string | number, data: Record<string, unknown>, user?: any): Promise<T>delete<T>(entity: string, id: string | number, user?: any): Promise<T>
DynamicCrudController
Automatically handles message patterns:
*.*.list- List entities*.*.get- Get single entity*.*.create- Create entity*.*.update- Update entity*.*.delete- Delete entity*.*.bulk-create- Bulk create*.*.bulk-update- Bulk update*.*.bulk-delete- Bulk delete*.*.count- Count entities*.health- Health check
Contributing
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add some amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
License
This project is licensed under the MIT License - see the LICENSE file for details.
Support
For support, please contact the Idrall Development Team or create an issue in the repository.
