crud-api-express
Version:
A powerful, flexible CRUD controller for Express + Mongoose — auto-generates RESTful endpoints with lifecycle hooks, validation, soft delete, search, bulk operations, pagination, and more.
251 lines (250 loc) • 9.36 kB
TypeScript
/// <reference types="mongoose/types/aggregate" />
/// <reference types="mongoose/types/callback" />
/// <reference types="mongoose/types/collection" />
/// <reference types="mongoose/types/connection" />
/// <reference types="mongoose/types/cursor" />
/// <reference types="mongoose/types/document" />
/// <reference types="mongoose/types/error" />
/// <reference types="mongoose/types/expressions" />
/// <reference types="mongoose/types/helpers" />
/// <reference types="mongoose/types/middlewares" />
/// <reference types="mongoose/types/indexes" />
/// <reference types="mongoose/types/models" />
/// <reference types="mongoose/types/mongooseoptions" />
/// <reference types="mongoose/types/pipelinestage" />
/// <reference types="mongoose/types/populate" />
/// <reference types="mongoose/types/query" />
/// <reference types="mongoose/types/schemaoptions" />
/// <reference types="mongoose/types/schematypes" />
/// <reference types="mongoose/types/session" />
/// <reference types="mongoose/types/types" />
/// <reference types="mongoose/types/utility" />
/// <reference types="mongoose/types/validation" />
/// <reference types="mongoose/types/virtuals" />
/// <reference types="mongoose/types/inferschematype" />
/// <reference types="mongoose/types/inferrawdoctype" />
import { Request, Response, Router, NextFunction } from 'express';
import { Document, Model } from 'mongoose';
/** Express middleware function signature. */
export type MiddlewareFunction = (req: Request, res: Response, next: NextFunction) => void;
/** Success callback invoked after a successful operation. */
export type SuccessHandler<T> = (res: Response, method: string, result: T | T[] | any, meta?: PaginationMeta) => void;
/** Error callback invoked when an operation fails. */
export type ErrorHandler = (res: Response, method: string, error: Error) => void;
/** Validation result returned by validate hooks. */
export interface ValidationResult {
valid: boolean;
errors?: string[];
}
/** Pagination metadata included in list responses. */
export interface PaginationMeta {
total: number;
page: number;
limit: number;
pages: number;
hasNext: boolean;
hasPrev: boolean;
}
/** Allowed HTTP methods for the CRUD controller. */
export type HttpMethod = 'POST' | 'GET' | 'PUT' | 'PATCH' | 'DELETE';
/**
* Configuration options for CrudController.
*
* Every option is optional — the controller works with zero configuration,
* but each option unlocks additional flexibility.
*/
export interface CrudOptions<T extends Document> {
/**
* HTTP methods to enable. Defaults to all five.
* @default ['POST', 'GET', 'PUT', 'PATCH', 'DELETE']
*/
methods?: HttpMethod[];
/**
* Global middleware applied to **every** generated route.
* For per-operation middleware, use `routeMiddleware` instead.
*/
middleware?: MiddlewareFunction[];
/**
* Per-operation middleware — lets you apply different middleware
* to different CRUD operations (e.g. only admins can delete).
*
* @example
* routeMiddleware: {
* delete: [requireAdmin],
* create: [validateBody],
* }
*/
routeMiddleware?: {
create?: MiddlewareFunction[];
read?: MiddlewareFunction[];
update?: MiddlewareFunction[];
delete?: MiddlewareFunction[];
};
/**
* Custom success response handler.
* The 4th argument `meta` is provided on paginated list endpoints.
*/
onSuccess?: SuccessHandler<T>;
/** Custom error response handler. */
onError?: ErrorHandler;
/**
* Lifecycle hooks that run before/after each operation.
* `before*` hooks can transform data by returning a modified object.
* `after*` hooks are for side-effects (logging, events, notifications).
*
* @example
* hooks: {
* beforeCreate: async (req, data) => {
* data.createdBy = req.user.id;
* return data;
* },
* afterDelete: async (req, result) => {
* await auditLog('delete', result._id);
* },
* }
*/
hooks?: {
beforeCreate?: (req: Request, data: any) => Promise<any> | any;
afterCreate?: (req: Request, result: T) => Promise<void> | void;
beforeUpdate?: (req: Request, id: string, data: any) => Promise<any> | any;
afterUpdate?: (req: Request, result: T) => Promise<void> | void;
beforeDelete?: (req: Request, id: string) => Promise<void> | void;
afterDelete?: (req: Request, result: T) => Promise<void> | void;
beforeRead?: (req: Request, query: any) => Promise<any> | any;
afterRead?: (req: Request, result: T | T[]) => Promise<T | T[]> | (T | T[]);
};
/**
* Validation hooks that run **before** Mongoose validation.
* Return `{ valid: false, errors: [...] }` to reject early with a 400.
*
* @example
* validate: {
* create: (data) => ({
* valid: !!data.email,
* errors: data.email ? [] : ['Email is required'],
* }),
* }
*/
validate?: {
create?: (data: any) => ValidationResult;
update?: (data: any) => ValidationResult;
};
/**
* Default fields to return (Mongoose select syntax).
* Can be overridden per-request via `?select=name,email`.
* @example "name email -password"
*/
select?: string;
/**
* Auto-populate references on read operations.
* Can be overridden per-request via `?populate=author,comments`.
* @example "author" or ["author", { path: "comments", select: "text" }]
*/
populate?: string | object | (string | object)[];
/**
* Fields to search across when using the `/search` endpoint.
* Uses case-insensitive `$regex` matching.
* @example ['name', 'email', 'description']
*/
searchFields?: string[];
/**
* When `true`, DELETE operations set `deletedAt: new Date()` instead of
* removing the document. GET operations auto-exclude soft-deleted records
* unless `?includeDeleted=true` is passed.
*
* A restore endpoint `PATCH /endpoint/:id/restore` is also created.
* @default false
*/
softDelete?: boolean;
/**
* MongoDB aggregation pipeline stages, or a function that receives the
* request and returns pipeline stages (for dynamic pipelines).
*
* @example
* // Static pipeline
* aggregatePipeline: [{ $match: { status: 'Active' } }]
*
* // Dynamic pipeline
* aggregatePipeline: (req) => [
* { $match: { region: req.query.region } },
* ]
*/
aggregatePipeline?: object[] | ((req: Request) => object[]);
/** Related Mongoose model for cascading operations. */
relatedModel?: Model<any>;
/** Field name linking the related model to this model. */
relatedField?: string;
/** HTTP methods to cascade to the related model. */
relatedMethods?: HttpMethod[];
/**
* Additional custom routes beyond standard CRUD.
* Custom routes are **always** registered regardless of `methods` filter.
*
* @example
* customRoutes: [{
* method: 'get',
* path: '/stats',
* handler: async (req, res) => {
* const count = await Model.countDocuments();
* res.json({ count });
* },
* }]
*/
customRoutes?: {
method: 'post' | 'get' | 'put' | 'patch' | 'delete';
path: string;
middleware?: MiddlewareFunction[];
handler: (req: Request, res: Response) => void;
}[];
}
export interface RouteInfo {
method: string;
path: string;
params?: string[];
}
/**
* A powerful, flexible CRUD Controller for Express + Mongoose.
*
* Automatically generates RESTful endpoints for any Mongoose model with
* support for lifecycle hooks, validation, soft delete, bulk operations,
* search, field selection, population, and more.
*
* @example
* const ctrl = new CrudController(UserModel, 'users', {
* methods: ['GET', 'POST', 'PATCH', 'DELETE'],
* softDelete: true,
* searchFields: ['name', 'email'],
* hooks: {
* beforeCreate: (req, data) => ({ ...data, createdBy: req.user.id }),
* },
* });
* app.use('/api', ctrl.getRouter());
*/
declare class CrudController<T extends Document> {
private model;
private endpoint;
private router;
private routes;
constructor(model: Model<T>, endpoint: string, options?: CrudOptions<T>);
/**
* Merges global middleware + per-operation middleware + handler into a single array.
*/
private buildMiddlewareChain;
/** Records a route in the internal registry. */
private registerRoute;
/** Parses a `?select=name,email` query param into Mongoose select syntax. */
private parseSelect;
/** Parses a `?populate=author,comments` query param. */
private parsePopulate;
/** Safely parses JSON from a query param, returns fallback on failure. */
private safeJsonParse;
/** Builds the soft-delete filter to exclude deleted records. */
private softDeleteFilter;
private configureRoutes;
/** Returns the configured Express Router with all CRUD routes. */
getRouter(): Router;
/** Returns an array of all registered route definitions. */
getRoutes(): RouteInfo[];
}
export { CrudController };
export default CrudController;