maggie-api
Version:
๐งโโ๏ธ A magical Express middleware to auto-generate CRUD APIs for Mongoose models with validation, unique keys, and middlewares.
548 lines (416 loc) โข 12.4 kB
Markdown
# ๐งโโ๏ธ 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
```bash
npm install maggie-api
# Peer dependencies
npm install express mongoose joi
```
## ๐ Quick Start
```ts
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 the `POST` route.
- Enables features like authentication, authorization, logging, etc.
### 5. Field Filtering (Deprecated)
- โ ๏ธ `getKeys` and `getByIdKeys` are deprecated.
- Use `settings.get.keys` to select fields in `GET /:model`.
- Use `settings.getById.keys` to select fields in `GET /: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` and `settings.getById.populate` to populate referenced fields.
- Each populate config accepts a `path` and optional `select` array for nested or targeted population.
```ts
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`, and `caseSensitive`.
- ๐งฉ 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:**
```ts
settings: {
get: {
search: {
disabled: false,
allowedFields: ["title", "description", "email"]
}
}
}
```
**Sample Request:**
```http
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:
```http
?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` and `page` query parameters:
```http
?limit=10&page=2
```
- Only applied when **both** parameters are valid positive integers.
- Automatically returns metadata:
```json
{
"users": [...],
"pagination": {
"total": 100,
"page": 2,
"limit": 10,
"totalPages": 10
}
}
```
- If not provided, returns the full result set without pagination.
#### ๐ Example:
```http
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:
```http
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
```bash
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
```bash
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
```bash
curl http://localhost:3000/api/v1/user
```
### ๐ Get User by ID
```bash
curl http://localhost:3000/api/v1/user/665c8d1234567890
```
### โ Delete User by ID
```bash
curl -X DELETE http://localhost:3000/api/v1/user/665c8d1234567890
```
### ๐ Bulk Insert Users
```bash
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:**
```json
{
"success": true,
"statusCode": 201,
"message": "User created successfully",
"data": {
"_id": "665c8d1234567890",
"firstName": "Alice",
"email": "alice@example.com"
}
}
```
**Update:**
```json
{
"success": true,
"statusCode": 200,
"message": "User updated successfully",
"data": {
"_id": "665c8d1234567890",
"firstName": "Alicia"
}
}
```
**Get All (with optional pagination):**
```json
{
"success": true,
"statusCode": 200,
"message": "Users fetched successfully",
"data": {
"users": [...],
"pagination": {
"total": 100,
"page": 2,
"limit": 10,
"totalPages": 10
}
}
}
```
**Get by ID:**
```json
{
"success": true,
"statusCode": 200,
"message": "User fetched successfully",
"data": {
"_id": "665c8d1234567890",
"firstName": "Alice"
}
}
```
**Delete:**
```json
{
"success": true,
"statusCode": 200,
"message": "User deleted successfully",
"data": {
"_id": "665c8d1234567890"
}
}
```
**Bulk Insert:**
```json
{
"success": true,
"statusCode": 201,
"message": "3 Users created successfully",
"data": [{}, {}, {}]
}
```
### ๐ด On Errors
**Validation Error:**
```json
{
"success": false,
"statusCode": 400,
"message": "Validation error",
"error": "\"email\" is required"
}
```
**Duplicate Primary Key (Create or Bulk Insert):**
```json
{
"success": false,
"statusCode": 409,
"message": "User with this email already exists",
"data": null
}
```
**Document Not Found (Update or GetById):**
```json
{
"success": false,
"statusCode": 404,
"message": "User not found",
"data": null
}
```
**Invalid Request Format (e.g. Bulk Insert with non-array):**
```json
{
"success": false,
"statusCode": 400,
"message": "Request body must be a non-empty array of documents",
"data": null
}
```
**Server Error:**
```json
{
"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. ๐