UNPKG

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
/// <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;