JavaScript API
Every export of the package, with signatures and TypeScript types.
Every export#
Everything is a named export from the package root. The package is ESM-only.
import { createRouter, errorHandler, ValidationError } from 'express-controller-sets';
| Export | Kind | Purpose |
|---|---|---|
| CRUD | ||
createRouter | function | A router with the six CRUD routes. |
ControllerSets | class | The six handlers, for routes you wire yourself. |
isQueryMethodSupported | function | Whether this runtime can serve HTTP QUERY. |
createRouterS3upload | function | Deprecated — use createRouter({ upload }). Upgrading |
| Errors | ||
errorHandler | middleware | Turns thrown errors into the JSON envelope. Mount last. |
HttpError | class | An error with a status and a client-safe message. |
ValidationError | class | A 400 with per-field messages. |
| Uploads | ||
fileUploadMiddleware | function | S3 upload middleware for any route. |
compressImage | function | Compress an image buffer. Needs sharp. |
| Cache | ||
createRedisCacheStore | function | A Redis store to share across routers. |
createMemoryCacheStore | function | An in-process store for development and tests. |
| Authentication | ||
createAuthRouter | function | Sign-up, login and user management. Guide |
identifier | function | A login identifier with a custom normalizer. |
requireAuth · requireRole | function | Standalone guards. Prefer auth.requireAuth. |
buildAuthConfig | function | Resolve auth options without mounting a router. |
hashPassword · verifyPassword | function | The scrypt password hashing the router uses. |
signToken · verifyToken | function | The HS256 JWT functions the router uses. |
AUTH_ROUTES | constant | Every auth endpoint with its default path and access level. |
BUILT_IN_PROVIDERS | constant | The social providers available by name. |
DEFAULT_TEMPLATES | constant | The built-in email and SMS templates. Templates |
| Utilities | ||
escapeRegex(value) | function | Escape regex metacharacters so input matches literally. |
QUERY_FILTER_OPERATORS | constant | The QUERY operator table: { eq: '$eq', gt: '$gt', … }. |
createRouter#
createRouter<T>(options: RouterOptions<T>): Router & { invalidateCache(): Promise<void> }
Mounts GET /, QUERY / (when supported and enableQuery isn't false), POST /, GET /:id, PATCH /:id and DELETE /:id. Throws at startup if model is missing or upload isn't true or an object. Options: createRouter.
ControllerSets#
new ControllerSets<T>(options: ControllerOptions<T>)
controller.getAll(req, res) // GET /
controller.query(req, res) // QUERY /
controller.get(req, res) // GET /:id
controller.create(req, res) // POST /
controller.update(req, res) // PATCH /:id
controller.delete(req, res) // DELETE /:id
controller.invalidateCache(): Promise<void>
controller.config // resolved, frozen options
Takes every router option except middlewares, enableQuery and upload. Handlers are pre-bound. getById and queryAll are deprecated aliases of get and query. The positional constructor new ControllerSets(model, orderBy, query, search) is deprecated. Guide
isQueryMethodSupported#
isQueryMethodSupported(): boolean
true when Node's HTTP parser knows QUERY (22.2+) and Express's router exposes router.query(). Check it before calling router.query() yourself.
errorHandler#
app.use('/api/products', productRouter);
app.use(errorHandler); // after every route
HttpError/ValidationError→ their status and message (andfields).- Mongoose validation and cast errors → 400. Duplicate key → 409 naming the fields. Multer limits → 400.
- Anything else → 500
"Internal Server Error". The detail is logged with arequestId, which is also sent to the client. stackis added only whenNODE_ENV=development.
HttpError and ValidationError#
new HttpError(status: number, message: string, options?: { headers?: Record<string, string> })
new ValidationError(message: string, fields?: Record<string, string>, status = 400)
Throw them from validate hooks, onLogin, or your own middleware. The message is sent to the client as-is, so write it for the client. ValidationError extends HttpError; its fields become the response's fields.
throw new HttpError(403, 'Only the author can publish.');
throw new ValidationError('Check the submitted values.', { email: 'is already registered' });
fileUploadMiddleware and compressImage#
fileUploadMiddleware(req, res, next, options?: UploadOptions): void
compressImage(buffer: Buffer, mimetypeOrFormat: string, level: 'low' | 'medium' | 'med' | 'high'): Promise<Buffer>
UploadOptions is the router's upload option with uploadPath in place of path. compressImage returns non-compressible formats unchanged. Guide
Cache stores#
createRedisCacheStore({ url?: string, client?: RedisClient, logger?: Logger }): CacheStore
createMemoryCacheStore({ maxEntries = 1000 }?): CacheStore & { readonly size: number }
interface CacheStore {
get(key: string): Promise<string | null | undefined>;
set(key: string, value: string, ttlSeconds: number): Promise<unknown>;
}
Pass a store as cache: { store }. createRedisCacheStore needs a url or a client (ioredis or node-redis). Any object implementing CacheStore works. Guide
createAuthRouter and identifier#
createAuthRouter<T>(options: AuthOptions<T>): AuthRouter
interface AuthRouter extends Router {
requireAuth: RequestHandler; // sets req.auth = { userId, role, claims }
requireRole(...roles: (string | string[])[]): RequestHandler;
config: ResolvedAuthConfig;
urls: readonly { name, method, path, access }[]; // what was mounted
}
identifier(field: string, normalize?: (value: string) => string): { field, normalize }
Options: Authentication → All options.
Auth building blocks#
For wiring authentication by hand. Most apps only need createAuthRouter and the guards on the router it returns.
buildAuthConfig(options: AuthOptions): ResolvedAuthConfig
requireAuth(config: ResolvedAuthConfig, { loadUser = false }?): RequestHandler // loadUser also sets req.user
requireRole(...roles: (string | string[])[]): RequestHandler
hashPassword(plain: string): Promise<string>
verifyPassword(plain: string, stored: string): Promise<boolean>
signToken(claims: object, { secret, expiresIn, issuer?, audience? }): string
verifyToken(token: string, { secret, issuer?, audience?, clockToleranceSeconds = 5 }): object // throws HttpError(401)
TypeScript#
Declarations ship with the package. Generic options take your document type:
import { createRouter, type RouterOptions } from 'express-controller-sets';
import Product, { type IProduct } from './models/Product.js';
const options: RouterOptions<IProduct> = {
model: Product,
orderBy: '-createdAt',
query: ['category'],
allowedFields: ['name', 'price'],
onGet: async () => ({ populates: 'category', selects: 'name price' }),
};
app.use('/api/products', createRouter(options));
Type the QUERY body on the client to catch mistakes before sending:
import type { QueryRequestBody } from 'express-controller-sets';
const body: QueryRequestBody = { filter: { price: { gte: 50 }, tag: ['sale'] }, sort: ['-price', 'name'] };
| Area | Types |
|---|---|
| Router & controller | RouterOptions, ControllerOptions, ControllerRouter, FieldPolicy, OnGetFn, OnGetResult, Logger |
| Writing | ValidateFn, ValidateContext, ValidatePolicy, WriteOperation |
| Reading | Pagination, PaginationMode, CountStrategy, QueryRequestBody, FilterCondition, FilterOperators, FilterScalar |
| Uploads | UploadOptions, RouterUploadOptions, UploadField, ImageOptimizationLevel |
| Cache | CacheOptions, CacheStore |
| Auth | AuthOptions, AuthRouter, AuthContext, TokenOptions, RefreshOptions, RoleOptions, OtpOptions, MailOptions, SmsOptions, MailTemplate, SmsTemplate, TemplateContext, SocialProvider, SocialProfile, AuthRouteName, AuthUrl, AuthFieldMap, IdentifierSpec |
Importing the package also types req.auth on Express's Request.
Field names aren't type-checked. allowedFields is string[], so 'pirce' compiles and silently drops the real field. If a field won't save, check the spelling first.