π§ββοΈ maggie-api
Auto-generate full-featured CRUD APIs for your Mongoose models in Express with one powerful config.
Supports:
- β Joi Validation
- β Custom Middlewares
- β Unique Primary Key Constraints
- β Add/Update Merged API
- β Consistent JSON Responses
- β Field Selection & Population
- β Bulk Insert Support
- β Dynamic Search (with keyword, fields, and case sensitivity support)
- β Pagination Support (limit & page query)
- β Sorting Support (ascending, descending, multi-field)
- β
Filtering Support (with allowed fields and advanced operators like
$gte
,$in
) - β Auto Pluralized Response Keys
- β Full CRUD API Auto-generation
π¦ Installation
npm install maggie-api
# Peer dependencies
npm install express mongoose joi
π Quick Start
import express from "express";
import { createMaggie } from "maggie-api";
import Models from "./models";
import Joi from "joi";
const app = express();
app.use(express.json());
const UserValidationSchema = Joi.object({
_id: Joi.string(),
firstName: Joi.string().required(),
lastName: Joi.string().required(),
email: Joi.string().email().required(),
});
const apiRouter = createMaggie({
prefix: "/api/v1",
models: [
{
model: ProductModel,
path: "product",
validationSchema: productValidationSchema,
settings: {
get: {
// β
Only these fields will be returned in GET /product
keys: ["_id", "title", "price", "description", "subCategory"],
// π Search by title or description using `?search=some+word`
search: {
disabled: false,
allowedFields: ["title", "description"],
},
// π§Ή Allow filtering via `?filter[price][gte]=100` or `filter[title]=Shoes`
filter: {
allowedFields: ["price", "title", "subCategory"],
},
// π Populate referenced subCategory and its category
populate: [
{
path: "subCategory",
select: ["_id", "title"],
populate: [{ path: "category", select: ["_id", "title"] }],
},
],
},
getById: {
// β
Only these fields will be returned in GET /product/:id
keys: ["_id", "title", "description", "price", "subCategory"],
// π Nested populate same as `get`
populate: [
{
path: "subCategory",
select: ["_id", "title"],
populate: [{ path: "category", select: ["_id", "title"] }],
},
],
},
},
},
],
});
app.use(apiRouter);
app.listen(3000, () => {
console.log("Server running at http://localhost:3000");
});
π Features
1. Add or Update API (POST /:model
)
- Merges create and update logic into a single endpoint.
- If the request body contains
_id
, it triggers an update; otherwise, a new record is created. - Automatically checks
primaryKey
uniqueness during creation. - During update, it ignores the current document when checking for duplicates.
2. Joi Validation
- Supports request body validation using Joi schemas for
POST
operations. - Only one validation error message is returned per request to enhance clarity.
- Validation schemas are customizable per model.
3. Primary Key Uniqueness
- Define a
primaryKey
(e.g.email
,username
) to enforce uniqueness on creation. - If a duplicate is found, the API returns a descriptive error.
4. Custom Middlewares
- Use the
middleWares
array to inject custom Express middlewares into thePOST
route. - Enables features like authentication, authorization, logging, etc.
5. Field Filtering (Deprecated)
- β οΈ
getKeys
andgetByIdKeys
are deprecated. - Use
settings.get.keys
to select fields inGET /:model
. - Use
settings.getById.keys
to select fields inGET /:model/:id
. - This improves flexibility and aligns with modern structured configurations.
6. CRUD Endpoints (Auto-generated)
Method | Endpoint | Description |
---|---|---|
POST |
/api/v1/user |
Create or Update User |
POST |
/api/v1/user/bulk |
Bulk Insert Users |
GET |
/api/v1/user |
Fetch all Users |
GET |
/api/v1/user/:id |
Fetch User by ID |
DELETE |
/api/v1/user/:id |
Delete User by ID |
7. Population Support
- Use
settings.get.populate
andsettings.getById.populate
to populate referenced fields. - Each populate config accepts a
path
and optionalselect
array for nested or targeted population.
settings: {
get: {
populate: [
{ path: "department", select: ["_id", "title"] }
]
},
getById: {
populate: [
{
path: "department",
select: ["_id", "title"] ,
populate: [
{
path: "item",
selected: ["_id", "title"]
}
],
}
]
}
}
8. Search Support
- β
Use
settings.get.search
to enable keyword-based searching on specific fields. - π Accepts query parameters like
search
,searchFields
, andcaseSensitive
. - π§© Only fields defined in
allowedFields
will be considered for searching. - π If
disabled: true
, searching will be turned off for that model. - π Falls back to all allowed fields if
searchFields
param is not provided.
Example Setting:
settings: {
get: {
search: {
disabled: false,
allowedFields: ["title", "description", "email"]
}
}
}
Sample Request:
GET /api/v1/user?search=mascara&searchFields=title,description&caseSensitive=false
Behavior:
- Builds a
$or
regex search query for all specified fields. - If no valid fields are provided or allowed β search is skipped.
9. Sorting, Pagination & Filtering (Built-in)
Sorting, pagination, and filtering are first-class citizens in maggie-api
, available out of the box for all models.
#
π Sorting
Pass a
sort
query param to define sort order:?sort=-createdAt,name
Use a hyphen (
-
) prefix for descending order.- Multiple fields can be sorted in sequence.
- Sorting is always enabled β no extra config needed.
#
π Pagination
Supports standard pagination via
limit
andpage
query parameters:?limit=10&page=2
Only applied when both parameters are valid positive integers.
Automatically returns metadata:
{ "users": [...], "pagination": { "total": 100, "page": 2, "limit": 10, "totalPages": 10 } }
If not provided, returns the full result set without pagination.
π Example:
GET /api/v1/product?filter[price][gte]=500&sort=-createdAt&limit=10&page=1
β οΈ Sorting and pagination are always enabled by default. Filtering requires configuring
allowedFields
to avoid accidental or insecure filtering.
This makes it easy to power powerful, customizable tables and dashboards with minimal backend configuration.
10. Filter Support
maggie-api
allows powerful and flexible filtering on API endpoints using structured query parameters.
π§ Key Features:
- Declarative control over filterable fields via
settings.get.filter.allowedFields
- Automatically transforms nested filters into MongoDB-compatible queries
- Supports value types: primitives, arrays, and range operators
π€ Supported Operators:
Operator | Usage | Translates To |
---|---|---|
eq | filter[status]=active |
{ status: "active" } |
in | filter[tags][]=a&[]=b |
{ tags: { $in: [...] } } |
gte | filter[price][gte]=100 |
{ price: { $gte: 100 } } |
lte | filter[price][lte]=500 |
{ price: { $lte: 500 } } |
gt, lt | Similar usage | $gt , $lt |
π‘ Behavior:
- If a filter field is not included in
allowedFields
, it will be silently ignored. - Case-sensitive by default (you may use search for regex-based keyword lookups).
- Compatible with MongoDB query syntax for advanced filtering.
π§ͺ Example Request:
GET /api/v1/user?filter[role]=admin&filter[age][gte]=18
β οΈ Important:
- Always whitelist filterable fields to avoid misuse or performance hits
- For flexible keyword matching across multiple fields, use the
search
config instead
This filtering system is perfect for admin dashboards, search filters, and dynamic list views.
π‘ Sample cURL Commands
β Add a User
curl -X POST http://localhost:3000/api/v1/user \
-H "Content-Type: application/json" \
-d '{"firstName":"Alice","lastName":"Doe","email":"alice@example.com"}'
βοΈ Update a User
curl -X POST http://localhost:3000/api/v1/user \
-H "Content-Type: application/json" \
-d '{"_id":"665c8d1234567890","firstName":"Alicia","email":"alice@example.com"}'
π Get All Users
curl http://localhost:3000/api/v1/user
π Get User by ID
curl http://localhost:3000/api/v1/user/665c8d1234567890
β Delete User by ID
curl -X DELETE http://localhost:3000/api/v1/user/665c8d1234567890
π Bulk Insert Users
curl -X POST http://localhost:3000/api/v1/user/bulk \
-H "Content-Type: application/json" \
-d '[
{"firstName":"Bob","lastName":"Smith","email":"bob@example.com"},
{"firstName":"Carol","lastName":"Jones","email":"carol@example.com"}
]'
β Standard JSON Response Format
maggie-api
follows a consistent, frontend-friendly response structure for all CRUD operations.
π’ On Success
Create:
{
"success": true,
"statusCode": 201,
"message": "User created successfully",
"data": {
"_id": "665c8d1234567890",
"firstName": "Alice",
"email": "alice@example.com"
}
}
Update:
{
"success": true,
"statusCode": 200,
"message": "User updated successfully",
"data": {
"_id": "665c8d1234567890",
"firstName": "Alicia"
}
}
Get All (with optional pagination):
{
"success": true,
"statusCode": 200,
"message": "Users fetched successfully",
"data": {
"users": [...],
"pagination": {
"total": 100,
"page": 2,
"limit": 10,
"totalPages": 10
}
}
}
Get by ID:
{
"success": true,
"statusCode": 200,
"message": "User fetched successfully",
"data": {
"_id": "665c8d1234567890",
"firstName": "Alice"
}
}
Delete:
{
"success": true,
"statusCode": 200,
"message": "User deleted successfully",
"data": {
"_id": "665c8d1234567890"
}
}
Bulk Insert:
{
"success": true,
"statusCode": 201,
"message": "3 Users created successfully",
"data": [{}, {}, {}]
}
π΄ On Errors
Validation Error:
{
"success": false,
"statusCode": 400,
"message": "Validation error",
"error": "\"email\" is required"
}
Duplicate Primary Key (Create or Bulk Insert):
{
"success": false,
"statusCode": 409,
"message": "User with this email already exists",
"data": null
}
Document Not Found (Update or GetById):
{
"success": false,
"statusCode": 404,
"message": "User not found",
"data": null
}
Invalid Request Format (e.g. Bulk Insert with non-array):
{
"success": false,
"statusCode": 400,
"message": "Request body must be a non-empty array of documents",
"data": null
}
Server Error:
{
"success": false,
"statusCode": 500,
"message": "Failed to process User",
"data": null
}
π Example Project Structure
your-app/
βββ models/
β βββ User.ts
βββ routes/
β βββ index.ts
βββ utils/
β βββ validateBody.ts
βββ app.ts
βββ ...
π Contributing
Want to contribute or enhance? PRs are welcome!
- Add new features like PATCH support, role-based auth, etc.
- Improve test coverage
- Bug fixes
π’ Final Words
Save hours of boilerplate setup. Focus on your app logic.
Let maggie-api
handle the API plumbing. π