Download OpenAPI specification:
REST API for an online bookstore.
This API provides authentication, user management, book management, cart, favourites, orders, and other bookstore-related functionality.
Authentication and account verification endpoints.
Includes:
Creates a new customer account in the BookStore database using the supplied personal details, contact numbers, date of birth, and physical delivery address.
Upon account creation:
role: "user", isEmailVerified: false, isActive: true, and an empty cart and favourites list.verificationTokenExpiry)./auth/verifyOtp within 30 seconds to verify their email and obtain an access token.Rate limit: 5 requests per minute.
Personal and delivery details required to register a customer account.
| name required | string >= 2 characters The user's full name. |
| email required | string <email> Email address used for the user's account. |
| password required | string <password> >= 8 characters Password used to authenticate the user. |
| dateOfBirth required | string <date> The user's date of birth. |
| phoneNumber required | integer The user's 10-digit phone number. |
| gender required | string Enum: "male" "female" "others" Gender selected by the user. |
required | object (Address) Physical address associated with a user. |
{- "name": "Panchal Keyur",
- "email": "panchalkeyur.dev@gmail.com",
- "password": "hello-1234",
- "dateOfBirth": "2005-06-08",
- "phoneNumber": 1234567890,
- "gender": "male",
- "address": {
- "line": "vastral",
- "street": "Dakor",
- "city": "amdavad",
- "state": "guj",
- "zipCode": 382418,
- "country": "ind"
}
}{- "status": "success",
- "message": "user is registered, otp is being sent to email for verification",
- "data": {
- "_id": "6ab8c6b4deb5c92fda07308a",
- "name": "Panchal Keyur",
- "email": "panchalkeyur.dev@gmail.com",
- "isEmailVerified": false,
- "role": "user",
- "dateOfBirth": "2005-06-08T00:00:00.000Z",
- "phoneNumber": 1234567890,
- "gender": "male",
- "address": {
- "line": "vastral",
- "street": "Dakor",
- "city": "amdavad",
- "state": "guj",
- "zipCode": 382418,
- "country": "ind"
}, - "isActive": true,
- "totalOrderes": 0,
- "totalSpent": 0,
- "cart": [ ],
- "faviourites": [ ],
- "createdAt": "2026-09-27T07:33:08.757Z",
- "updatedAt": "2026-09-27T07:33:08.757Z"
}
}Authenticates an existing user account using email and password credentials.
Authentication flow:
JWT_SECRET_KEY and configured expiration (JWT_EXPIRES) is generated containing the user ID payload { id: userId }.Client requests to protected endpoints must supply this token in the header:
Authorization: Bearer <JWT_TOKEN>
Rate limit: 5 requests per minute.
User credentials for login authentication.
| email required | string <email> Email address associated with the user account. |
| password required | string <password> Password associated with the user account. |
{- "email": "hello-1@gmail.com",
- "password": "hello-1234"
}{- "status": "success",
- "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
- "data": {
- "_id": "683aa9c34ae6c56e60594eb2",
- "name": "hello-1",
- "email": "hello-1@gmail.com",
- "isEmailVerified": true,
- "role": "user",
- "dateOfBirth": "2005-06-08T00:00:00.000Z",
- "phoneNumber": 1234567890,
- "gender": "male",
- "address": {
- "line": "6 nilkanth row house",
- "street": "vastral",
- "city": "amdavad",
- "state": "guj",
- "zipCode": 382418,
- "country": "ind"
}, - "isActive": true,
- "totalOrderes": 0,
- "totalSpent": 0,
- "cart": [ ],
- "faviourites": [
- "6836a66013f0b230bee67c1f",
- "683697c5c4efe00d7ca88e5a"
], - "createdAt": "2025-05-31T07:03:31.815Z",
- "updatedAt": "2025-05-31T07:03:31.815Z"
}
}Verifies the 6-digit one-time password (OTP) sent to the user's email during registration.
Verification logic:
email and the 6-digit otp.isEmailVerified === true).Date.now() > verificationTokenExpiry).emailVerifiedToken.isEmailVerified is updated to true.emailVerifiedToken and verificationTokenExpiry are cleared.Rate limit: 5 requests per minute.
Email address and 6-digit verification OTP.
| otp required | integer [ 100000 .. 999999 ] Six-digit OTP sent to the user's email. |
| email required | string <email> Email address associated with the account. |
{- "email": "panchalkeyur.dev@gmail.com",
- "otp": 151858
}{- "status": "success",
- "message": "otp verified successfully",
- "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
- "data": {
- "_id": "6abe528d92d30f6fb4097f64",
- "name": "Panchal Keyur",
- "email": "panchalkeyur.dev@gmail.com",
- "isEmailVerified": true,
- "role": "user",
- "dateOfBirth": "2005-06-08T00:00:00.000Z",
- "phoneNumber": 1234567890,
- "gender": "male",
- "address": {
- "line": "near metro station, indiranagar",
- "street": "10th main road",
- "city": "bengaluru",
- "state": "kar",
- "zipCode": 560038,
- "country": "ind"
}, - "isActive": true,
- "totalOrderes": 0,
- "totalSpent": 0,
- "cart": [ ],
- "faviourites": [ ],
- "createdAt": "2026-10-01T12:31:09.871Z",
- "updatedAt": "2026-10-01T12:31:09.871Z"
}
}Generates and sends a new 6-digit verification OTP to the user's registered email address.
Endpoint rules:
isEmailVerified === true), resending is rejected with 400.Rate limit: 5 requests per minute.
Email address of the unverified customer account.
| email required | string <email> Email address associated with the user account. |
{- "email": "panchalkeyur.dev@gmail.com"
}{- "status": "success",
- "message": "otp is sent to email, please check your email"
}Generates a secure password reset token and emails a reset link to the user.
Security & controller requirements:
Authorization header (protect middleware).isEmailVerified === true).passwordResetToken on the user record.passwordExpires = Date.now() + 30 * 1000).${protocol}://${host}/auth/resetPassword/${resetToken} is sent to the customer.Rate limit: 5 requests per minute.
Email address of the account requesting password reset.
| email required | string <email> Email address associated with the account. |
{- "email": "panchalkeyur.dev@gmail.com"
}{- "status": "success",
- "message": "email send successfully"
}Changes the user's password using the unhashed password reset token received via email.
Flow & controller requirements:
Authorization header (protect middleware).:token.:token with SHA-256 and searches for a user where passwordResetToken matches and passwordExpires > Date.now().password is set, passwordResetToken and passwordExpires are cleared to undefined, and the user document is saved (which triggers bcrypt hashing via Mongoose pre('save')).Rate limit: 5 requests per minute.
| token required | string Example: a1b2c3d4e5f67890123456789abcdef0123456789abcdef0123456789abcdef0 Unhashed password reset token string extracted from the email link. |
The new password to replace the current password.
| password required | string <password> >= 8 characters New password that replaces the existing password. |
{- "password": "hello-1234"
}{- "status": "success",
- "message": "password chagned successfully"
}Retrieves aggregated key performance statistics for the administrator dashboard.
The dashboard aggregates:
Rate limit: 30 requests per minute.
{- "status": "success",
- "salesData": {
- "totalRevenue": {
- "total": 125000
}, - "totalOrders": {
- "total": 250
}, - "totalBooks": {
- "total": 85
}, - "monthlyTotal": {
- "total": 18500,
- "count": 42
}, - "yearlyTotal": {
- "total": 125000,
- "count": 250
}, - "dailySales": [
- {
- "date": "2026-09-27",
- "sales": 1250,
- "count": 5
}, - {
- "date": "2026-09-28",
- "sales": 2100,
- "count": 8
}
], - "topBook": {
- "bookId": "6ab8c6b4deb5c92fda07308a",
- "title": "Clean Code",
- "totalSold": 45,
- "revenue": 13500
}
}, - "userStats": {
- "totalUsers": {
- "count": 450
}, - "newUserThisMonth": {
- "count": 38
}, - "userByRoles": [
- {
- "role": "user",
- "count": 420
}, - {
- "role": "seller",
- "count": 30
}
]
}, - "orderStats": {
- "orderByStatus": [
- {
- "orderStatus": "paid",
- "count": 210
}, - {
- "orderStatus": "pending",
- "count": 25
}, - {
- "orderStatus": "cancelled",
- "count": 15
}
], - "pendingOrders": {
- "count": 25
}
}, - "lowInventory": [
- {
- "title": "Clean Code",
- "author": "Robert C. Martin",
- "stock": 5
}, - {
- "title": "The Pragmatic Programmer",
- "author": "Andrew Hunt",
- "stock": 8
}
]
}Retrieves aggregated sales metrics for non-cancelled orders grouped by day, week, or month.
Optional query parameters allow filtering by date range (startDate and endDate)
and selecting the aggregation period (groupBy):
day: Groups data daily (YYYY-MM-DD) [default]week: Groups data weekly (YYYY-Www)month: Groups data monthly (YYYY-MM)For each time bucket, the response returns:
totalRevenue: Sum of total pricesavgRevenue: Average order valuetotalOrders: Count of orderstotalItems: Total quantity of books purchasedRate limit: 30 requests per minute.
| startDate | string <date> Example: startDate=2026-01-01 Earliest order creation date to include (inclusive). |
| endDate | string <date> Example: endDate=2026-10-01 Latest order creation date to include (inclusive). |
| groupBy | string Default: "day" Enum: "day" "week" "month" Example: groupBy=day Time-series bucket granularity. |
{- "status": "success",
- "data": [
- {
- "_id": "2026-10-01",
- "totalRevenue": 4500,
- "avgRevenue": 450,
- "totalOrders": 10,
- "totalItems": 18
}, - {
- "_id": "2026-09-30",
- "totalRevenue": 3200,
- "avgRevenue": 400,
- "totalOrders": 8,
- "totalItems": 12
}
]
}Returns three comprehensive analytical datasets across the bookstore:
booksAnalytic: Total books, active books, inactive/deleted books, out-of-stock books, and low-stock books (< 100 units).bookSaleAnalytics: Top 10 best-selling books and top 10 low-selling books based on quantity sold in paid orders.bookReviewAnalytics: Top 10 most-rated books and top 10 highest-rated books (by average rating).{- "stauts": "success",
- "data": {
- "booksAnalytic": [
- {
- "total": 150,
- "totalActiveBooks": 142,
- "totalInactiveBooks": 8,
- "outOfStockBooks": 4,
- "lowStockBooks": 19
}
], - "bookSaleAnalytics": [
- {
- "bestSellingBooks": [
- {
- "_id": "664b1f89e21b8c12a8901234",
- "totalSold": 120,
- "totalOrders": 65,
- "book": [
- {
- "_id": "664b1f89e21b8c12a8901234",
- "title": "Clean Code",
- "price": 450,
- "category": "Action"
}
]
}
], - "lowSellingBooks": [
- {
- "_id": "664b1f89e21b8c12a8909876",
- "totalSold": 1,
- "totalOrders": 1,
- "book": [
- {
- "_id": "664b1f89e21b8c12a8909876",
- "title": "Cobol Programming in Depth",
- "price": 320,
- "category": "Biography"
}
]
}
]
}
], - "bookReviewAnalytics": [
- {
- "mostRatedBook": [
- {
- "bookId": "664b1f89e21b8c12a8901234",
- "totalRating": 250,
- "reviewCount": 55,
- "avgRating": 4.55
}
], - "highestRatedBook": [
- {
- "bookId": "664b1f89e21b8c12a8905555",
- "totalRating": 98,
- "reviewCount": 20,
- "avgRating": 4.9
}
]
}
]
}
}Identifies stagnant inventory ("dead stock") books that have generated zero sales in paid orders over the past 30 days.
Returns paginated book items with title, author, current stock, price, and totalSold: 0.
| page | integer >= 1 Default: 1 Example: page=1 Page number for pagination. |
| limit | integer >= 1 Default: 2 Example: limit=10 Number of books per page. |
{- "status": "success",
- "pagination": {
- "totalItems": 8,
- "totalPages": 4,
- "currentPage": 1,
- "hasNextPage": true
}, - "data": [
- {
- "_id": "664b1f89e21b8c12a8901234",
- "title": "Legacy Fortran Architecture",
- "stock": 35,
- "author": "Walter Smith",
- "price": 45,
- "totalSold": 0
}, - {
- "_id": "664b1f89e21b8c12a8905678",
- "title": "Ancient Assembly Language",
- "stock": 12,
- "author": "Alan Turing",
- "price": 60,
- "totalSold": 0
}
]
}Retrieves a paginated list of all books enriched with multi-dimensional performance data:
orderAnalytics: Total units sold and total revenue from paid orders.wishlistAnalytics: Total times the book was wishlisted.bookRating: Average customer review rating.| page | integer >= 1 Default: 1 Example: page=1 Page number for pagination. |
| limit | integer >= 1 Default: 10 Example: limit=10 Number of books per page. |
{- "status": "success",
- "pagination": {
- "totalItems": 50,
- "totalPage": 5,
- "currentPage": 1,
- "hasNextPage": true
}, - "data": [
- {
- "_id": "664b1f89e21b8c12a8901234",
- "title": "Clean Code",
- "author": "Robert C. Martin",
- "price": 450,
- "stock": 25,
- "category": "Action",
- "orderAnalytics": [
- {
- "totalSold": 15,
- "totalRevenue": 6750
}
], - "wishlistAnalytics": [
- {
- "totalWishlistCount": 8
}
], - "bookRating": [
- {
- "averageRating": 4.5
}
]
}
]
}Calculates total units sold (getSales) and cumulative revenue (getRevenue) across all paid orders,
mapped across standard bookstore categories:
Fantasy, Action, Adventure, Mystery, Horror, Thriller, Romance, Self-help, and Biography.
Categories without any sales return 0 for both fields.
{- "status": "success",
- "data": [
- {
- "category": "Fantasy",
- "getSales": 120,
- "getRevenue": 48000
}, - {
- "category": "Action",
- "getSales": 85,
- "getRevenue": 34000
}, - {
- "category": "Adventure",
- "getSales": 60,
- "getRevenue": 24000
}, - {
- "category": "Mystery",
- "getSales": 45,
- "getRevenue": 18000
}, - {
- "category": "Horror",
- "getSales": 20,
- "getRevenue": 8000
}, - {
- "category": "Thriller",
- "getSales": 55,
- "getRevenue": 22000
}, - {
- "category": "Romance",
- "getSales": 90,
- "getRevenue": 36000
}, - {
- "category": "Self-help",
- "getSales": 110,
- "getRevenue": 44000
}, - {
- "category": "Biography",
- "getSales": 30,
- "getRevenue": 12000
}
]
}Computes monthly sales quantities and revenue per category within a specified date range.
Validations enforced:
startDate and endDate are required valid date strings.endDate must be strictly after startDate.startDate and endDate cannot exceed 1 year.Missing calendar months in the range are automatically densified with zero values.
| startDate required | string <date> Example: startDate=2026-01-01 Start date in YYYY-MM-DD format. |
| endDate required | string <date> Example: endDate=2026-09-01 End date in YYYY-MM-DD format (must be within 1 year from startDate). |
{- "status": "success",
- "data": [
- {
- "year": "2026-01",
- "category": "Fantasy",
- "totalQuantity": 25,
- "totalRevenue": 12500
}, - {
- "year": "2026-02",
- "category": "Action",
- "totalQuantity": 18,
- "totalRevenue": 9000
}
]
}Aggregates customer reviews grouped by book, displaying individual counts for 5-star, 4-star, 3-star, 2-star, and 1-star ratings.
Results are paginated.
| page | integer >= 1 Default: 1 Example: page=1 Page number for pagination. |
| limit | integer >= 1 Default: 10 Example: limit=10 Number of books per page. |
{- "status": "success",
- "pagination": {
- "totalItems": 45,
- "totalPages": 5,
- "currentPage": 1,
- "hasNextPage": true
}, - "data": [
- {
- "bookId": "664b1f89e21b8c12a8901234",
- "fiveStarRating": 25,
- "fourStarRating": 12,
- "threeStarRating": 5,
- "twoStarRating": 2,
- "oneStarRating": 1
}
]
}Returns two high-level order metrics:
orderStatusAnalytics: Total count of orders grouped by order status (e.g. paid, pending, cancelled).orderCancellationAnalytics: Total orders, number of cancelled orders, and calculated cancellation rate percentage.{- "status": "success",
- "data": {
- "orderStatusAnalytics": [
- {
- "orderStatus": "paid",
- "totalOrders": 150
}, - {
- "orderStatus": "pending",
- "totalOrders": 20
}, - {
- "orderStatus": "cancelled",
- "totalOrders": 10
}
], - "orderCancellationAnalytics": [
- {
- "totalOrders": 180,
- "cancelledOrders": 10,
- "cancellationRate": 5.56
}
]
}
}Provides day-by-day order counts between startDate and endDate.
Validations enforced:
startDate and endDate are required.startDate cannot be later than endDate.Days without any order activity are automatically densified with a count of 0.
| startDate required | string <date> Example: startDate=2026-09-01 Start date (YYYY-MM-DD). |
| endDate required | string <date> Example: endDate=2026-09-30 End date (YYYY-MM-DD, within 90 days of startDate). |
{- "status": "success",
- "data": [
- {
- "date": "2026-09-01",
- "totalOrders": 12
}, - {
- "date": "2026-09-02",
- "totalOrders": 8
}, - {
- "date": "2026-09-03",
- "totalOrders": 0
}
]
}Calculates the monthly average order value (avgOrderValue), count of completed paid orders (completedOrders),
and total revenue for paid orders within the specified date range.
Validations enforced:
startDate and endDate are required.endDate must be strictly after startDate.Months with zero orders return 0 for revenue and average order value.
| startDate required | string <date> Example: startDate=2026-01-01 Start date (YYYY-MM-DD). |
| endDate required | string <date> Example: endDate=2026-10-01 End date (YYYY-MM-DD, within 1 year from startDate). |
{- "status": "success",
- "data": [
- {
- "month": "2026-01",
- "completedOrders": 45,
- "totalRevenue": 20250,
- "avgOrderValue": 450
}, - {
- "month": "2026-02",
- "completedOrders": 50,
- "totalRevenue": 24000,
- "avgOrderValue": 480
}
]
}Provides in-depth customer engagement and retention statistics:
userAnalytics: Monthly count of newly registered users over the date range (densified with 0).topPayingCustomers: Top 10 customers ranked by total money spent on completed paid orders.returningCustomers: Total unique buyers, count of repeat customers (> 1 paid order), and returning customer rate percentage.Validations enforced:
startDate and endDate are required.endDate must be greater than or equal to startDate.| startDate required | string <date> Example: startDate=2026-01-01 Start date (YYYY-MM-DD). |
| endDate required | string <date> Example: endDate=2026-10-01 End date (YYYY-MM-DD, within 1 year from startDate). |
{- "status": "success",
- "data": {
- "userAnalytics": [
- {
- "month": "2026-01",
- "totalUsers": 15
}, - {
- "month": "2026-02",
- "totalUsers": 28
}
], - "topPayingCustomers": [
- {
- "customerId": "664b1f89e21b8c12a8901234",
- "totalOrders": 12,
- "totalMoneySpent": 5400
}, - {
- "customerId": "664b1f89e21b8c12a8905678",
- "totalOrders": 8,
- "totalMoneySpent": 4100
}
], - "returningCustomers": [
- {
- "totalCustomers": 120,
- "returningCustomers": 35,
- "returningCustomersRate": 29.17
}
]
}
}Analyzes transaction history and financial performance across payment statuses:
paymentStatusAnalytics: Volume counts and financial amounts partitioned into successful (paid), failed (failed), and refunded (cancelled) categories.paymentStatusRate: Total payment attempts, success rate percentage, and failure rate percentage rounded to two decimal places.{- "status": "success",
- "data": {
- "paymentStatusAnalytics": [
- {
- "successfullPayments": 180,
- "successfullAmounts": 81000,
- "failedPayments": 12,
- "failedAmounts": 5400,
- "refundedPayments": 5,
- "refundedAmounts": 2250
}
], - "paymentStatusRate": [
- {
- "successfullPayments": 185,
- "failedPayments": 12,
- "totalPayments": 197,
- "successRate": 93.91,
- "failedRate": 6.09
}
]
}
}Restores and activates a suspended user account by setting isActive: true and persisting the change with user.save().
Once activated, the customer regains access to their account, can log in, and place bookstore orders.
Authorization header (protect middleware).restrictTo("admin")).validateId("userId") validates that userId is a valid 24-character hexadecimal MongoDB ObjectId. Returns HTTP 400 Bad Request ("Invalid Id, please enter correct id") if malformed.mutationLimiter (maximum 10 mutation requests per minute) and router-level getRateLimiter (maximum 30 requests per minute).| userId required | string^[0-9a-fA-F]{24}$ Example: 683aa9c34ae6c56e60594eb2 24-character hexadecimal MongoDB ObjectId of the user account to activate. |
{- "status": "success",
- "message": "User is activated",
- "data": {
- "_id": "683aa9c34ae6c56e60594eb2",
- "name": "John Doe",
- "email": "johndoe@example.com",
- "isEmailVerified": true,
- "role": "user",
- "dateOfBirth": "2000-01-15T00:00:00.000Z",
- "phoneNumber": 1234567890,
- "gender": "male",
- "isActive": true,
- "totalOrderes": 3,
- "totalSpent": 1250,
- "cart": [ ],
- "faviourites": [ ],
- "createdAt": "2025-05-31T07:03:31.815Z",
- "updatedAt": "2026-10-09T12:00:00.000Z"
}
}Suspends and deactivates a user account by setting isActive: false using User.findByIdAndUpdate.
Once deactivated, the user is blocked from logging in or performing customer operations, and excluded from public user queries.
Authorization header (protect middleware).restrictTo("admin")).userId === req.user.id). Attempting to do so returns HTTP 400 Bad Request ("You can't deactivate your self").user.role === 'admin'). Attempting to do so returns HTTP 400 Bad Request ("You can't deactivate admin user").validateId("userId") validates that userId is a valid 24-character hexadecimal MongoDB ObjectId. Returns HTTP 400 Bad Request ("Invalid Id, please enter correct id") if malformed.mutationLimiter (maximum 10 mutation requests per minute) and router-level getRateLimiter (maximum 30 requests per minute).| userId required | string^[0-9a-fA-F]{24}$ Example: 683aa9c34ae6c56e60594eb2 24-character hexadecimal MongoDB ObjectId of the user account to deactivate. |
{- "status": "success",
- "message": "User is suspended",
- "data": {
- "_id": "683aa9c34ae6c56e60594eb2",
- "name": "John Doe",
- "email": "johndoe@example.com",
- "isEmailVerified": true,
- "role": "user",
- "dateOfBirth": "2000-01-15T00:00:00.000Z",
- "phoneNumber": 1234567890,
- "gender": "male",
- "isActive": false,
- "totalOrderes": 3,
- "totalSpent": 1250,
- "cart": [ ],
- "faviourites": [ ],
- "createdAt": "2025-05-31T07:03:31.815Z",
- "updatedAt": "2026-10-09T12:00:00.000Z"
}
}Changes the authorization role of a user account. Permits switching between "user" and "admin".
Authorization header (protect middleware).restrictTo("admin")).userId === req.user.id). Attempting to do so returns HTTP 400 Bad Request ("You can't change your role").["admin", "user"] are accepted. Any other string value triggers HTTP 400 Bad Request ("Please provide valid role").validateId("userId") validates that userId is a valid 24-character hexadecimal MongoDB ObjectId. Returns HTTP 400 Bad Request ("Invalid Id, please enter correct id") if malformed.mutationLimiter (maximum 10 mutation requests per minute) and router-level getRateLimiter (maximum 30 requests per minute).| userId required | string^[0-9a-fA-F]{24}$ Example: 683aa9c34ae6c56e60594eb2 24-character hexadecimal MongoDB ObjectId of the target user whose role is to be updated. |
JSON payload containing the new role assignment.
| role required | string Enum: "admin" "user" Role to assign to the user ("admin" or "user"). |
{- "role": "admin"
}{- "status": "success",
- "message": "User role changed successfully",
- "data": {
- "_id": "683aa9c34ae6c56e60594eb2",
- "name": "John Doe",
- "email": "johndoe@example.com",
- "isEmailVerified": true,
- "role": "admin",
- "dateOfBirth": "2000-01-15T00:00:00.000Z",
- "phoneNumber": 1234567890,
- "gender": "male",
- "isActive": true,
- "totalOrderes": 3,
- "totalSpent": 1250,
- "cart": [ ],
- "faviourites": [ ],
- "createdAt": "2025-05-31T07:03:31.815Z",
- "updatedAt": "2026-10-09T12:00:00.000Z"
}
}Book catalog, inventory, and product management endpoints.
Includes:
Retrieves a paginated collection of active books from the catalog.
Supports keyword filtering by book title and author using case-insensitive
regular expressions ($regex, $options: "i"). Results can be sorted in
descending (newest) or ascending (oldest) chronological order by creation date.
Soft-deleted books (isDeleted: true) are automatically excluded from the
result set by a Mongoose pre(/^find/) query middleware.
sort=new (default): Sorts descending by -createdAt (newest books first).sort=old: Sorts ascending by createdAt (oldest books first).new.page: 1-based page index (defaults to 1).limit: Number of records returned per page (defaults to 10).limit exceeds 100, the server automatically caps it at 100.skip = (page - 1) * limit.totalPages = Math.ceil(totalItems / limit) and hasNextPage = page < totalPages.getRateLimiter: Maximum 30 requests per minute per client IP.| title | string Example: title=clean Case-insensitive substring search for book title. Matches any book whose title contains the specified query text. |
| author | string Example: author=martin Case-insensitive substring search for author name. Matches any book written by an author matching the query string. |
| sort | string Default: "new" Enum: "new" "old" Example: sort=new Ordering criterion for the returned books:
|
| page | integer >= 1 Default: 1 Example: page=1 1-based page number for paginated results. |
| limit | integer [ 1 .. 100 ] Default: 10 Example: limit=10 Number of book records returned per page. Maximum allowed value is 100. Values exceeding 100 are automatically clamped to 100 by the server. |
{- "status": "success",
- "length": 2,
- "pagination": {
- "totalItems": 42,
- "totalPages": 5,
- "currentPage": 1,
- "hasNextPage": true
}, - "message": "all books found",
- "data": [
- {
- "_id": "683aab454ae6c56e60594eb7",
- "title": "Clean Code: A Handbook of Agile Software Craftsmanship",
- "author": "Robert C. Martin",
- "stock": 35,
- "description": "A comprehensive handbook for software craftsmen containing best practices, principles, and code smells.",
- "totalSales": 89,
- "price": 38.99,
- "category": "Self-help",
- "ratingAverage": 4.7,
- "ratingQuantity": 112,
- "createdAt": "2026-05-30T10:15:30.000Z",
- "isDeleted": false
}, - {
- "_id": "683aab454ae6c56e60594eb8",
- "title": "The Pragmatic Programmer",
- "author": "Andrew Hunt, David Thomas",
- "stock": 20,
- "description": "Your journey to mastery: continuous learning, career growth, and software craft.",
- "totalSales": 64,
- "price": 42.5,
- "category": "Self-help",
- "ratingAverage": 4.9,
- "ratingQuantity": 95,
- "createdAt": "2026-05-28T08:22:15.000Z",
- "isDeleted": false
}
]
}Creates and registers a new book entity in the bookstore catalog.
This operation is restricted to verified administrators (role: 'admin').
The request must be transmitted as multipart/form-data to accommodate
both book metadata fields and the binary image file for the book's cover.
Authorization header.restrictTo("admin") middleware verifies the caller holds admin role and that their email is verified.mutationLimiter restricts the caller to 10 mutations per minute.coverImage and writing it to temporary disk storage (uploads/).validateBooks validates the presence and constraints of title, author, stock, description, price, and category."BookStore" folder.fs.unlink.totalSales: 0, ratingAverage: 0, ratingQuantity: 0, isDeleted: false).201 Created with the complete book document.title: String, trimmed, non-empty, minimum length of 2 characters.author: String, non-empty.stock: Numeric, non-empty, non-negative integer.description: String, non-empty.price: Numeric, non-empty, non-negative, minimum length 1 character.category: Must match one of the predefined categories: Fantasy, Action, Adventure, Mystery, Horror, Thriller, Romance, Self-help, Biography. Cannot be empty.coverImage: Required binary file representing the book cover art.Multipart form data containing book metadata and the cover image file.
| title required | string >= 2 characters Title or name of the book (minimum 2 characters). |
| author required | string Full name of the author. Cannot be empty. |
| stock required | integer >= 0 Initial inventory quantity. Must be a valid non-negative number. |
| description required | string Detailed description or synopsis of the book content. |
| price required | number <double> >= 0 Retail price of the book. Must be non-negative and at least 1 character in length. |
| category required | string Enum: "Fantasy" "Action" "Adventure" "Mystery" "Horror" "Thriller" "Romance" "Self-help" "Biography" Genre category selected from the predefined list of allowed categories. |
| coverImage required | string <binary> Cover image file (JPEG, PNG, WebP) uploaded via form-data.
The server automatically transmits this file to Cloudinary in the |
{- "status": "success",
- "message": "Book is created",
- "data": {
- "_id": "683aab454ae6c56e60594eb7",
- "title": "Atomic Habits",
- "author": "James Clear",
- "stock": 50,
- "description": "An Easy & Proven Way to Build Good Habits & Break Bad Ones. Tiny Changes, Remarkable Results.",
- "totalSales": 0,
- "price": 19.99,
- "category": "Self-help",
- "ratingAverage": 0,
- "ratingQuantity": 0,
- "createdAt": "2026-10-04T12:00:00.000Z",
- "isDeleted": false
}
}Retrieves detailed information for a specific book identified by its unique MongoDB ObjectId.
This endpoint implements a cache-aside caching strategy using Redis:
book:{bookId}.bookId parameter is present and valid.Books.findById(bookId).pre(/^find/) middleware automatically filters out soft-deleted books (isDeleted: false).404 Not Found.book:{bookId} and returns HTTP 200 OK.getRateLimiter: Maximum 30 requests per minute per client IP.| bookId required | string Example: 683aab454ae6c56e60594eb7 24-character hexadecimal MongoDB ObjectId of the book to retrieve. |
{- "status": "success",
- "message": "Book is found",
- "data": {
- "_id": "683aab454ae6c56e60594eb7",
- "title": "Atomic Habits",
- "author": "James Clear",
- "stock": 45,
- "description": "An Easy & Proven Way to Build Good Habits & Break Bad Ones. Tiny Changes, Remarkable Results.",
- "totalSales": 120,
- "price": 19.99,
- "category": "Self-help",
- "ratingAverage": 4.8,
- "ratingQuantity": 84,
- "createdAt": "2026-05-30T10:15:30.000Z",
- "isDeleted": false
}
}Updates an existing book's metadata fields or replaces its cover image.
This operation is restricted to verified administrators (role: 'admin').
validateId("bookId") middleware verifies that bookId is a syntactically valid 24-character hexadecimal MongoDB ObjectId.protect and restrictTo("admin") ensure caller has active admin credentials.mutationLimiter enforces a rate limit of 10 requests per minute.coverImage file is attached via multipart/form-data:"BookStore" folder.fs.unlink.secure_url replaces the existing coverImage in the update payload.Books.findByIdAndUpdate(bookId, updatePayload).book:{bookId}, ensuring subsequent GET requests immediately reflect changes.200 OK with the updated book entity.multipart/form-data: Use when replacing the cover image (with or without textual metadata changes).application/json: Use when updating only text or numeric fields (title, author, price, stock, description, category).| bookId required | string Example: 683aab454ae6c56e60594eb7 24-character hexadecimal MongoDB ObjectId of the book to update. |
Fields to be updated. All fields are optional; only provided fields are changed.
| title | string >= 2 characters Updated book title (minimum 2 characters). |
| author | string Updated author name. |
| stock | integer >= 0 Updated inventory stock quantity. |
| description | string Updated synopsis or description. |
| price | number <double> >= 0 Updated retail price. |
| category | string Enum: "Fantasy" "Action" "Adventure" "Mystery" "Horror" "Thriller" "Romance" "Self-help" "Biography" Updated genre category. |
| coverImage | string <binary> Optional replacement cover image file. If supplied, the image is uploaded to Cloudinary,
and the book's |
{- "status": "success",
- "message": "book is updated",
- "data": {
- "_id": "683aab454ae6c56e60594eb7",
- "title": "Atomic Habits: Expanded Edition",
- "author": "James Clear",
- "stock": 65,
- "description": "Updated edition with new exercises and best practices for modern software development.",
- "totalSales": 120,
- "price": 21.99,
- "category": "Self-help",
- "ratingAverage": 4.8,
- "ratingQuantity": 84,
- "createdAt": "2026-05-30T10:15:30.000Z",
- "isDeleted": false
}
}Performs a soft deletion on the book specified by bookId.
This operation is restricted to verified administrators (role: 'admin').
Rather than permanently purging the record from MongoDB, the controller
updates the document with { isDeleted: true }:
pre(/^find/) middleware automatically intercepts all queries (find, findOne, findById) and applies { isDeleted: false }, ensuring deleted books are immediately hidden from the public store catalog.404 Not Found.generalRateLimiter: Maximum 15 requests per minute per client IP.| bookId required | string Example: 683aab454ae6c56e60594eb7 24-character hexadecimal MongoDB ObjectId of the book to soft-delete. |
{- "status": "success",
- "message": "Book is deleted"
}Shopping cart operations for authenticated customers.
Includes:
Retrieves the active shopping cart for the authenticated customer.
This endpoint requires authentication and is restricted to customers with
role: 'user'. The user's email address must be verified.
The cart utilizes a cache-aside caching strategy via Redis:
user:{userId}:cart.200 OK without touching the MongoDB database:
{ status: "success", message: "cart is found", data: cartData }.Cart.findOne({ user: userId }).user:{userId}:cart.200 OK with { status: "success", data: getCart }.data is null.getRateLimiter: Maximum 30 requests per minute per client IP.{- "status": "success",
- "message": "cart is found",
- "data": {
- "_id": "683aacc04ae6c56e60594eb8",
- "user": "683aa9c34ae6c56e60594eb2",
- "items": [
- {
- "_id": "683aae014ae6c56e60594eb9",
- "book": "683aab454ae6c56e60594eb7",
- "quantity": 2
}, - {
- "_id": "683aae014ae6c56e60594eba",
- "book": "683aab454ae6c56e60594eb8",
- "quantity": 1
}
], - "totalPrice": 80.48,
- "totalQuantity": 3,
- "isOrdered": false
}
}Adds a specified quantity of a book to the authenticated customer's shopping cart.
If a cart does not yet exist for the user, a new one is initialized. If the item is already in the cart, its quantity is increased and financial totals are recalculated. Synchronizes changes with Redis caching.
This endpoint requires authentication and is restricted to customers with role: 'user'.
bookId and quantity from request body.userId from authenticated req.user.id.Books.findById(bookId).book.stock >= quantity. If the requested quantity exceeds total book stock,
rejects immediately with HTTP 400 Bad Request:
"No book stock is available yet, please contact admin to futher information".Cart.findOne({ user: userId }).{
"user": "userId",
"items": [{ "book": "bookId", "quantity": quantity }],
"totalQuantity": quantity,
"totalPrice": book.price * quantity
}
bookId within cart.items:index > -1):newTotalQty = existingQty + quantity.newTotalQty > book.stock, rejects with HTTP 400 Bad Request:
"Only <availableStock> more units available".cart.items[index].quantity += quantity.cart.totalQuantity += quantity, cart.totalPrice += book.price * quantity.index === -1):quantity > book.stock, rejects with HTTP 400 Bad Request:
"Only <availableStock> more units available".cart.items: { book: bookId, quantity }.cart.totalQuantity += quantity, cart.totalPrice += book.price * quantity.await cart.save().user:{userId}:cart with the newly saved cart JSON.200 OK with { status: "success", message: "added to cart", data: cart }.mutationLimiter: Maximum 10 mutations per minute per client IP.Book identifier and quantity to add to the cart.
| bookId required | string 24-character hexadecimal MongoDB ObjectId of the book to add. |
| quantity required | integer >= 1 Number of copies to add to the cart. Must be at least 1 and within available inventory stock. |
{- "bookId": "683aab454ae6c56e60594eb7",
- "quantity": 2
}{- "status": "success",
- "message": "added to cart",
- "data": {
- "_id": "683aacc04ae6c56e60594eb8",
- "user": "683aa9c34ae6c56e60594eb2",
- "items": [
- {
- "_id": "683aae014ae6c56e60594eb9",
- "book": "683aab454ae6c56e60594eb7",
- "quantity": 2
}
], - "totalPrice": 49.98,
- "totalQuantity": 2,
- "isOrdered": false
}
}Removes a specific book item entirely from the authenticated customer's shopping cart.
Automatically recalculates cumulative price (totalPrice) and quantity (totalQuantity)
based on the removed book's unit price, and synchronizes the updated cart with Redis cache.
This endpoint requires authentication and is restricted to customers with role: 'user'.
validateId("bookId") validates that bookId is a syntactically valid
24-character hexadecimal MongoDB ObjectId (mongoose.Types.ObjectId.isValid).
If invalid, returns HTTP 400 Bad Request: "Invalid Id, please enter correct id".bookId is missing, returns HTTP 400 Bad Request:
"please provide book id to remove a book from the cart".Cart.findOne({ user: id }).404 Not Found:
"cart not found, please create your cart first".cart.items:
index = findExistingCart.items.findIndex(item => item.book.toString() === bookId).index === -1), returns HTTP 404 Not Found:
"book not found in the cart".removedQty = removedItem.quantity.Books.findById(removedItem.book._id)) to get its unit price.removedPrice = findBook.price * removedQty.items array: cart.items.splice(index, 1).cart.totalPrice -= removedPricecart.totalQuantity -= removedQtycart.items.length === 0),
explicitly resets totalPrice = 0 and totalQuantity = 0.await findExistingCart.save().user:{userId}:cart with the serialized updated cart.200 OK with { status: "success", message: "book was removed", data: findExistingCart }.mutationLimiter: Maximum 10 mutations per minute per client IP.| bookId required | string Example: 683aab454ae6c56e60594eb7 24-character hexadecimal MongoDB ObjectId of the book to remove from the cart. |
{- "status": "success",
- "message": "book was removed",
- "data": {
- "_id": "683aacc04ae6c56e60594eb8",
- "user": "683aa9c34ae6c56e60594eb2",
- "items": [ ],
- "totalPrice": 0,
- "totalQuantity": 0,
- "isOrdered": false
}
}Order processing, Stripe checkout, refund requests, and fulfillment tracking.
Includes:
Initializes a secure, Stripe-hosted payment checkout session for all books currently stored in the authenticated customer's shopping cart.
Authorization header (protect middleware).role: 'user' (restrictTo("user")).isEmailVerified: true).Cart.findOne({ user: req.user.id })
with populated book details (populate("items.book")). If no cart exists, returns HTTP 404 Not Found.makeReservation middleware):Books collection (Books.findById(item.book)).book.stock < item.quantity),
checkout halts immediately with HTTP 400 Bad Request ("We have only [stock] of [title] left").Reservation.findByIdAndDelete).Reservation.create) to hold inventory
while payment is processed.currency: "inr", book title, unit amount
(price converted to paise: unit_amount = book.price * 100), and quantity.stripe.checkout.sessions.create configured with mode: "payment" and payment_method_types: ["card"].success_url: {host}/orders/success?session_id={CHECKOUT_SESSION_ID}cancel_url: {host}/orders/cancelorderQueue):checkout.session.completed webhook event.order-processing background job into BullMQ orderQueue.Order record, marks the cart
isOrdered: true, decrements book stock, updates sales metrics, and cleans up the reservation.mutationLimiter: Maximum 10 mutation requests per minute per client IP.{- "status": "success",
- "data": {
- "id": "cs_test_a1b2c3d4e5f67890",
- "object": "checkout.session",
- "customer_email": "customer@example.com",
- "mode": "payment",
- "payment_status": "unpaid",
}
}Initializes a secure, Stripe-hosted payment checkout session for all books currently stored in the authenticated customer's shopping cart.
Authorization header (protect middleware).role: 'user' (restrictTo("user")).isEmailVerified: true).Cart.findOne({ user: req.user.id })
with populated book details (populate("items.book")). If no cart exists, returns HTTP 404 Not Found.makeReservation middleware):Books collection (Books.findById(item.book)).book.stock < item.quantity),
checkout halts immediately with HTTP 400 Bad Request ("We have only [stock] of [title] left").Reservation.findByIdAndDelete).Reservation.create) to hold inventory
while payment is processed.currency: "inr", book title, unit amount
(price converted to paise: unit_amount = book.price * 100), and quantity.stripe.checkout.sessions.create configured with mode: "payment" and payment_method_types: ["card"].success_url: {host}/orders/success?session_id={CHECKOUT_SESSION_ID}cancel_url: {host}/orders/cancelorderQueue):checkout.session.completed webhook event.order-processing background job into BullMQ orderQueue.Order record, marks the cart
isOrdered: true, decrements book stock, updates sales metrics, and cleans up the reservation.mutationLimiter: Maximum 10 mutation requests per minute per client IP.{- "status": "success",
- "data": {
- "id": "cs_test_a1b2c3d4e5f67890",
- "object": "checkout.session",
- "customer_email": "customer@example.com",
- "mode": "payment",
- "payment_status": "unpaid",
}
}Initiates a customer payment refund through the Stripe payment gateway for a completed order.
protect middleware).role: 'user' (restrictTo("user")).isEmailVerified: true).checkRefundStatus middleware)paymentInfo.stripePaymentId matches the path parameter
and user matches the authenticated customer (req.user._id).
If no matching order is found, returns HTTP 404 Not Found ("Order not found").paidAt).
If todayDate > (paidAt + 168 hours), the request is rejected with HTTP 400 Bad Request
("date is passed, you can't make a refund request")."paid" status. Unpaid, pending, or already cancelled orders cannot be refunded
("This payment cannot be refunded").stripe.refunds.create({ payment_intent: stripePaymentId, reason: "requested_by_customer" }).charge.refunded):
Upon Stripe refund confirmation, the incoming webhook handler:orderStatus = "cancelled", paymentInfo.status = "cancelled".bulkWrite to replenish catalog stock (stock += quantity) and decrement
revenue figures (totalSales -= salesAmount) on affected Books documents.| stripePaymentId required | string Example: pi_3NzABC1234567890XYZ The Stripe Payment Intent identifier associated with the order (e.g., |
{- "status": "success",
- "message": "payment refund successfull",
- "data": {
- "id": "re_3NzABC1234567890XYZ",
- "object": "refund",
- "amount": 125000,
- "currency": "inr",
- "payment_intent": "pi_3NzABC1234567890XYZ",
- "status": "succeeded",
- "reason": "requested_by_customer"
}
}Retrieves a paginated list of all customer orders across the platform for administrative auditing, status tracking, and fulfillment operations.
protect middleware).role: 'admin' (restrictTo("admin")).isEmailVerified: true).sort=new (default): Sorts descending by -createdAt (most recent orders first).sort=old: Sorts ascending by createdAt (oldest orders first).new.page: 1-based page index (defaults to 1).limit: Number of records returned per page (defaults to 10).limit exceeds 100, the server automatically caps it at 100.skip = (page - 1) * limit.totalPages = Math.ceil(totalItems / limit) and hasNextPage = page < totalPages."all orders found successfully" when records match, or "orders not found"
when the catalog yields 0 records.getRateLimiter: Maximum 30 requests per minute per client IP.| page | integer >= 1 Default: 1 Example: page=1 1-based page index for pagination. Defaults to 1. |
| limit | integer [ 1 .. 100 ] Default: 10 Example: limit=10 Number of order records to return per page. Defaults to 10. Automatically clamped to a maximum of 100 by the server. |
| sort | string Default: "new" Enum: "new" "old" Example: sort=new Chronological ordering of orders:
|
{- "status": "success",
- "length": 2,
- "message": "all orders found successfully",
- "pagination": {
- "totalItems": 42,
- "totalPages": 5,
- "currentPage": 1,
- "hasNextPage": true
}, - "data": [
- {
- "_id": "683b1c204ae6c56e60594ed0",
- "user": "683aa9c34ae6c56e60594eb2",
- "items": [
- {
- "_id": "683b1c204ae6c56e60594ed1",
- "book": "683aab454ae6c56e60594eb7",
- "quantity": 2
}
], - "totalQuantity": 2,
- "totalPrice": 900,
- "shippingAddress": {
- "name": "Keyur Panchal",
- "email": "keyur.panchal@example.com",
- "phoneNumber": 9876543210,
- "street": "123 Tech Park Road",
- "city": "Ahmedabad",
- "state": "Gujarat",
- "zipCode": 382418
}, - "orderStatus": "paid",
- "paymentInfo": {
- "stripePaymentId": "pi_3NzABC1234567890XYZ",
- "stripeClientSecret": "pi_3NzABC1234567890XYZ_secret_abc123",
- "paymentMethod": "card",
- "status": "paid",
- "paidAt": "2026-05-12T10:05:00.000Z"
}, - "createdAt": "2026-05-12T10:00:00.000Z"
}
]
}Retrieves the complete document details of an individual order by its unique identifier, utilizing a high-performance Redis cache-aside caching pattern.
protect middleware).role: 'user' or 'admin').isEmailVerified: true).validateId("orderId") verifies that orderId is a syntactically
valid 24-character hexadecimal MongoDB ObjectId before controller execution.order:{orderId} using client.get(...).200 OK: { status: "success", data: JSON.parse(orderData) }.Order.findById(orderId).404 Not Found ("Order not found").order.user.toString() === req.user.id.toString()). If the order belongs to another customer, access is denied with HTTP 404 Not Found ("Order not found").order:{orderId}.200 OK: { status: "success", data: order }.getRateLimiter: Maximum 30 requests per minute per client IP.| orderId required | string^[0-9a-fA-F]{24}$ Example: 683b1c204ae6c56e60594ed0 The 24-character hexadecimal MongoDB ObjectId uniquely identifying the order. |
{- "status": "success",
- "data": {
- "_id": "683b1c204ae6c56e60594ed0",
- "user": "683aa9c34ae6c56e60594eb2",
- "items": [
- {
- "_id": "683b1c204ae6c56e60594ed1",
- "book": "683aab454ae6c56e60594eb7",
- "quantity": 2
}
], - "totalQuantity": 2,
- "totalPrice": 900,
- "shippingAddress": {
- "name": "Keyur Panchal",
- "email": "keyur.panchal@example.com",
- "phoneNumber": 9876543210,
- "street": "123 Tech Park Road",
- "city": "Ahmedabad",
- "state": "Gujarat",
- "zipCode": 382418
}, - "orderStatus": "paid",
- "paymentInfo": {
- "stripePaymentId": "pi_3NzABC1234567890XYZ",
- "stripeClientSecret": "pi_3NzABC1234567890XYZ_secret_abc123",
- "paymentMethod": "card",
- "status": "paid",
- "paidAt": "2026-05-12T10:05:00.000Z"
}, - "createdAt": "2026-05-12T10:00:00.000Z"
}
}Handles client redirection callback from Stripe hosted checkout following payment completion.
session_id query parameter forwarded by Stripe upon checkout completion.stripe.checkout.sessions.retrieve(req.query.session_id).res.redirect("/")) forwarding the customer to the storefront homepage.| session_id required | string Example: session_id=cs_test_a1b2c3d4e5f67890 The Stripe Checkout Session ID passed by Stripe upon customer payment completion. |
<p>Payment successful. Redirecting...</p>
Customer book review and rating management endpoints.
Includes:
Retrieves a paginated list of customer reviews and ratings for the specified book.
Authorization header (protect middleware).isEmailVerified: true).msg): Optional case-insensitive regular expression substring filter
({ review: { $regex: msg, $options: "i" } }). Matches any review whose commentary contains
the query text.bookId ({ book: new mongoose.Types.ObjectId(bookId) }).sort=new (default): Sorts descending by -createdAt (newest reviews first).sort=old: Sorts ascending by createdAt (oldest reviews first).new.page: 1-based page index (defaults to 1).limit: Number of records returned per page (defaults to 10).limit exceeds 100, the server automatically caps it at 100.skip = (page - 1) * limit.totalPages = Math.ceil(totalItems / limit) and hasNextPage = page < totalPages.user: { _id, name }) and book title
(book: { _id, title }) via Mongoose pre(/^find/) query middleware.getRateLimiter: Maximum 30 requests per minute per client IP.| bookId required | string^[0-9a-fA-F]{24}$ Example: 683aab454ae6c56e60594eb7 The 24-character hexadecimal MongoDB ObjectId uniquely identifying the book. |
| page | integer >= 1 Default: 1 Example: page=1 1-based page index for pagination. Defaults to 1. |
| limit | integer [ 1 .. 100 ] Default: 10 Example: limit=10 Number of review records to return per page. Defaults to 10. Automatically clamped to a maximum of 100 by the server. |
| sort | string Default: "new" Enum: "new" "old" Example: sort=new Chronological ordering of reviews:
|
| msg | string Example: msg=helpful Case-insensitive substring search filter matching text within the review commentary. |
{- "status": "success",
- "length": 2,
- "pagination": {
- "totalItems": 14,
- "totalPages": 7,
- "currentPage": 1,
- "hasNextPage": true
}, - "data": [
- {
- "_id": "683ab1234ae6c56e60594eca",
- "review": "An exceptional, comprehensive guide to clean architecture and readable code.",
- "rating": 5,
- "user": {
- "_id": "683aa9c34ae6c56e60594eb2",
- "name": "Keyur Panchal"
}, - "book": {
- "_id": "683aab454ae6c56e60594eb7",
- "title": "Clean Code"
}, - "createdAt": "2026-05-12T10:00:00.000Z",
- "updatedAt": "2026-05-12T10:00:00.000Z"
}, - {
- "_id": "683ab1234ae6c56e60594ecb",
- "review": "Must-read for every aspiring software craftsman. Changed how I structure functions.",
- "rating": 4.5,
- "user": {
- "_id": "683aa9c34ae6c56e60594eb5",
- "name": "Jane Smith"
}, - "book": {
- "_id": "683aab454ae6c56e60594eb7",
- "title": "Clean Code"
}, - "createdAt": "2026-05-10T14:30:00.000Z",
- "updatedAt": "2026-05-10T14:30:00.000Z"
}
]
}Creates a new review commentary and numerical star rating for the designated book.
protect middleware).role: 'user' (restrictTo("user")).isEmailVerified: true).Books.findById(bookId). If the target book does not exist in the catalog,
the request fails immediately with HTTP 404 Not Found ("Book not exists").{ user: 1, book: 1 }. If the authenticated
customer has already submitted a review for this book, the creation request is rejected.Upon saving a new review, a Mongoose post("save") lifecycle hook triggers:
Review.calcAvgRating(this.book).nRating) and arithmetic average (avgRating).ratingQuantity and ratingAverage fields directly on the corresponding Books catalog document.mutationLimiter: Maximum 10 mutation requests per minute per client IP.| bookId required | string^[0-9a-fA-F]{24}$ Example: 683aab454ae6c56e60594eb7 The 24-character hexadecimal MongoDB ObjectId uniquely identifying the book. |
Review text and numerical rating submitted by the customer.
| review required | string non-empty Customer's detailed review commentary. Leading and trailing whitespace is trimmed. |
| rating required | number [ 1 .. 5 ] Star rating for the book, ranging from 1 to 5. |
{- "review": "An exceptional, comprehensive guide to clean architecture and readable code.",
- "rating": 5
}{- "status": "success",
- "message": "Review created successfully",
- "data": {
- "_id": "683ab1234ae6c56e60594eca",
- "user": "683aa9c34ae6c56e60594eb2",
- "book": "683aab454ae6c56e60594eb7",
- "review": "An exceptional, comprehensive guide to clean architecture and readable code.",
- "rating": 5,
- "createdAt": "2026-05-12T10:00:00.000Z",
- "updatedAt": "2026-05-12T10:00:00.000Z"
}
}Updates the existing review text commentary and/or star rating submitted by the authenticated customer for the designated book.
protect middleware).role: 'user' (restrictTo("user")).isEmailVerified: true).bookId is supplied in the request path parameters (!bookId -> HTTP 404 "Book id not found").Books.findById(bookId). If the book does not exist in the catalog, returns HTTP 404 "Book not found".Review.findOneAndUpdate({ book: bookId, user: req.user.id }, req.body, { new: true }).post(/^findOneAnd/) middleware hook executes immediately after the update:calcAvgRating(doc.book._id) with the reviewed book's ID.ratingAverage and ratingQuantity
on the Books document.mutationLimiter: Maximum 10 mutation requests per minute per client IP.| bookId required | string^[0-9a-fA-F]{24}$ Example: 683aab454ae6c56e60594eb7 The 24-character hexadecimal MongoDB ObjectId uniquely identifying the book. |
Modified review commentary or revised numerical rating.
| review | string non-empty Updated review commentary. |
| rating | number [ 1 .. 5 ] Updated star rating score on a scale from 1 to 5. |
{- "review": "Updated review: Still an absolute classic for any software craftsman.",
- "rating": 4.5
}{- "status": "success",
- "message": "review updated successfully",
- "data": {
- "_id": "683ab1234ae6c56e60594eca",
- "user": "683aa9c34ae6c56e60594eb2",
- "book": "683aab454ae6c56e60594eb7",
- "review": "Updated review: Still an absolute classic for any software craftsman.",
- "rating": 4.5,
- "createdAt": "2026-05-12T10:00:00.000Z",
- "updatedAt": "2026-10-09T14:30:00.000Z"
}
}Permanently deletes the customer's review and star rating for the specified book.
protect middleware).role: 'user' (restrictTo("user")).isEmailVerified: true).Review.findOneAndDelete({ book: bookId, user: req.user.id }).post(/^findOneAnd/) middleware hook executes immediately after deletion:calcAvgRating(doc.book._id).ratingQuantity and ratingAverage on the Books document.ratingAverage and ratingQuantity to 0.generalRateLimiter: Maximum 15 requests per minute per client IP.| bookId required | string^[0-9a-fA-F]{24}$ Example: 683aab454ae6c56e60594eb7 The 24-character hexadecimal MongoDB ObjectId uniquely identifying the book. |
{- "status": "success",
- "data": null
}Provides comprehensive administrative oversight of all customer reviews submitted across the bookstore catalog.
protect middleware).role: 'admin' (restrictTo("admin")).isEmailVerified: true).msg): Optional case-insensitive regular expression substring filter
({ review: { $regex: msg, $options: "i" } }) searched across all review bodies in the database.sort=new (default): Sorts descending by -createdAt (most recently submitted reviews first).sort=old: Sorts ascending by createdAt (oldest reviews first).new.page: 1-based page index (defaults to 1).limit: Number of records returned per page (defaults to 10).limit exceeds 100, the server automatically caps it at 100.skip = (page - 1) * limit.totalPages = Math.ceil(totalItems / limit) and hasNextPage = page < totalPages.user: { _id, name }) and book title
(book: { _id, title }) via Mongoose pre(/^find/) query middleware.getRateLimiter: Maximum 30 requests per minute per client IP.| page | integer >= 1 Default: 1 Example: page=1 1-based page index for pagination. Defaults to 1. |
| limit | integer [ 1 .. 100 ] Default: 10 Example: limit=10 Number of review records to return per page. Defaults to 10. Automatically clamped to a maximum of 100 by the server. |
| sort | string Default: "new" Enum: "new" "old" Example: sort=new Chronological ordering of reviews:
|
| msg | string Example: msg=practical Case-insensitive substring search filter matching text within the review commentary. |
{- "status": "success",
- "message": "reviews found successfully",
- "pagination": {
- "totalItems": 48,
- "totalPages": 5,
- "currentPage": 1,
- "hasNextPage": true
}, - "length": 10,
- "data": [
- {
- "_id": "683ab1234ae6c56e60594eca",
- "review": "An exceptional, comprehensive guide to clean architecture and readable code.",
- "rating": 5,
- "user": {
- "_id": "683aa9c34ae6c56e60594eb2",
- "name": "Keyur Panchal"
}, - "book": {
- "_id": "683aab454ae6c56e60594eb7",
- "title": "Clean Code"
}, - "createdAt": "2026-05-12T10:00:00.000Z",
- "updatedAt": "2026-05-12T10:00:00.000Z"
}, - {
- "_id": "683ab1234ae6c56e60594ecc",
- "review": "A masterpiece of system architecture and concurrency patterns.",
- "rating": 5,
- "user": {
- "_id": "683aa9c34ae6c56e60594eb6",
- "name": "Alex Johnson"
}, - "book": {
- "_id": "683aab454ae6c56e60594eb9",
- "title": "Designing Data-Intensive Applications"
}, - "createdAt": "2026-05-11T09:15:00.000Z",
- "updatedAt": "2026-05-11T09:15:00.000Z"
}
]
}Retrieves the specific review and rating written by the authenticated customer for the designated book, utilizing a high-performance Redis cache-aside caching pattern.
protect middleware).role: 'user' (restrictTo("user")).isEmailVerified: true).validateId("bookId") validates that bookId is a valid
24-character hexadecimal MongoDB ObjectId before controller execution.user:{userId}:book:{bookId} using client.get(...).200 OK:
{ status: "success", message: "review found successfully", data: JSON.parse(reviewData) }.Review.findOne({ book: bookId, user: req.user.id }).404 Not Found ("Review not found").user:{userId}:book:{bookId}.200 OK: { status: "success", data: review }.getRateLimiter: Maximum 30 requests per minute per client IP.| bookId required | string^[0-9a-fA-F]{24}$ Example: 683aab454ae6c56e60594eb7 The 24-character hexadecimal MongoDB ObjectId uniquely identifying the book. |
{- "status": "success",
- "message": "review found successfully",
- "data": {
- "_id": "683ab1234ae6c56e60594eca",
- "review": "An exceptional, comprehensive guide to clean architecture and readable code.",
- "rating": 5,
- "user": {
- "_id": "683aa9c34ae6c56e60594eb2",
- "name": "Keyur Panchal"
}, - "book": {
- "_id": "683aab454ae6c56e60594eb7",
- "title": "Clean Code"
}, - "createdAt": "2026-05-12T10:00:00.000Z",
- "updatedAt": "2026-05-12T10:00:00.000Z"
}
}User account management, profile settings, order history, and favourites collection.
Includes:
/users/me)/users/updateMe)/users/orders)/users/favBooks)/users/all)/users/{userId})Retrieves the complete profile and personal information of the currently authenticated user.
Authorization header (protect middleware).isEmailVerified: true).pre(/^find/) middleware that automatically excludes
deactivated accounts ({ isActive: { $ne: false } }).404 Not Found
("user does not exists").getRateLimiter: Maximum 30 requests per minute per client IP.{- "status": "success",
- "message": "user found successfully",
- "data": {
- "_id": "683aa9c34ae6c56e60594eb2",
- "name": "Keyur Panchal",
- "email": "keyur.panchal@example.com",
- "isEmailVerified": true,
- "role": "user",
- "dateOfBirth": "2005-06-08T00:00:00.000Z",
- "phoneNumber": 9876543210,
- "gender": "male",
- "address": {
- "line": "Near Metro Station",
- "street": "123 Tech Park Road",
- "city": "Ahmedabad",
- "state": "Gujarat",
- "zipCode": 382418,
- "country": "India"
}, - "isActive": true,
- "totalOrderes": 2,
- "totalSpent": 1250,
- "faviourites": [
- "683aab454ae6c56e60594eb7",
- "683aab454ae6c56e60594eb8"
], - "createdAt": "2025-05-31T07:03:31.815Z",
- "updatedAt": "2026-05-12T10:00:00.000Z"
}
}Updates personal profile information, contact number, gender, date of birth, or physical shipping address for the currently authenticated user.
protect middleware).isEmailVerified: true).validateUser middleware):name: Optional, must be a string ("Name must be a string").dateOfBirth: Optional, must be a valid ISO date ("Date of birth must be a date").phoneNumber: Optional, must be a numeric value ("Phone Number must be a number").gender: Optional, must be one of ["male", "female", "others"] ("Gender must be male, female, or others").address: Optional object ("Address must be an object").address.line, address.street, address.city, address.state, address.country: Optional, must be strings.address.zipCode: Optional, must be a numeric value ("Zip Code must be a number").400 Bad Request with the first validation error message.User.findByIdAndUpdate(req.user.id, { name, dateOfBirth, phoneNumber, gender, address }).400 Bad Request ("user is not updated").201 Created returning the updated user document in data.mutationLimiter: Maximum 10 mutation requests per minute per client IP.Personal profile attributes and delivery address fields to modify.
| name | string >= 2 characters Updated full name of the user. |
| phoneNumber | integer Updated 10-digit primary telephone contact number. |
| gender | string Enum: "male" "female" "others" Updated gender identity. |
| dateOfBirth | string <date> Updated date of birth (YYYY-MM-DD). |
object (Address) Physical address associated with a user. |
{- "name": "Keyur Panchal",
- "phoneNumber": 9876543210,
- "gender": "male",
- "dateOfBirth": "2005-06-08",
- "address": {
- "line": "Near Metro Station",
- "street": "123 Tech Park Road",
- "city": "Ahmedabad",
- "state": "Gujarat",
- "zipCode": 382418,
- "country": "India"
}
}{- "status": "success",
- "message": "user updated successfully",
- "data": {
- "_id": "683aa9c34ae6c56e60594eb2",
- "name": "Keyur Panchal",
- "email": "keyur.panchal@example.com",
- "isEmailVerified": true,
- "role": "user",
- "dateOfBirth": "2005-06-08T00:00:00.000Z",
- "phoneNumber": 9876543210,
- "gender": "male",
- "address": {
- "line": "Near Metro Station",
- "street": "123 Tech Park Road",
- "city": "Ahmedabad",
- "state": "Gujarat",
- "zipCode": 382418,
- "country": "India"
}, - "isActive": true,
- "totalOrderes": 2,
- "totalSpent": 1250,
- "createdAt": "2025-05-31T07:03:31.815Z",
- "updatedAt": "2026-10-09T12:00:00.000Z"
}
}Retrieves the complete list of historical bookstore orders placed by the currently authenticated customer.
protect middleware).isEmailVerified: true).Order collection matching { user: req.user.id }.getRateLimiter: Maximum 30 requests per minute per client IP.{- "status": "success",
- "length": 1,
- "data": [
- {
- "_id": "683b1c204ae6c56e60594ed0",
- "user": "683aa9c34ae6c56e60594eb2",
- "items": [
- {
- "_id": "683b1c204ae6c56e60594ed1",
- "book": "683aab454ae6c56e60594eb7",
- "quantity": 2
}
], - "totalQuantity": 2,
- "totalPrice": 900,
- "shippingAddress": {
- "name": "Keyur Panchal",
- "email": "keyur.panchal@example.com",
- "phoneNumber": 9876543210,
- "street": "123 Tech Park Road",
- "city": "Ahmedabad",
- "state": "Gujarat",
- "zipCode": 382418
}, - "orderStatus": "paid",
- "paymentInfo": {
- "stripePaymentId": "pi_3NzABC1234567890XYZ",
- "stripeClientSecret": "pi_3NzABC1234567890XYZ_secret_abc123",
- "paymentMethod": "card",
- "status": "paid",
- "paidAt": "2026-05-12T10:05:00.000Z"
}, - "createdAt": "2026-05-12T10:00:00.000Z"
}
]
}Appends a specific book to the authenticated user's personal favourites collection.
protect middleware).isEmailVerified: true).bookId in the JSON request body. If omitted, returns HTTP 404 Not Found
("please provide book id").user.faviourites. If the book already resides in the customer's favourites list,
the request is rejected with HTTP 400 Bad Request ("Book is already in favourites").faviourites array and saves
the modified user record.mutationLimiter: Maximum 10 mutation requests per minute per client IP.Object containing the 24-character MongoDB ObjectId of the book to favourite.
| bookId required | string^[0-9a-fA-F]{24}$ 24-character hexadecimal MongoDB ObjectId of the book to add to favourites. |
{- "bookId": "683aab454ae6c56e60594eb7"
}{- "status": "success",
- "message": "Book added to favourites",
- "data": {
- "_id": "683aa9c34ae6c56e60594eb2",
- "name": "Keyur Panchal",
- "email": "keyur.panchal@example.com",
- "faviourites": [
- "683aab454ae6c56e60594eb7"
]
}
}Retrieves a paginated list of registered users across the platform for administrative auditing, customer support, and account management.
protect middleware).role: 'admin' (restrictTo("admin")).isEmailVerified: true).name): Case-insensitive regular expression substring match ({ name: { $regex: name, $options: "i" } }).email): Case-insensitive regular expression substring match ({ email: { $regex: email, $options: "i" } }).phone): Matches against phone number digits by converting numeric phone to string
via MongoDB $expr with $regexMatch.sort=new (default): Sorts descending by -createdAt (most recently registered accounts first).sort=old: Sorts ascending by createdAt (earliest registered accounts first).new.page: 1-based page index (defaults to 1).limit: Number of records returned per page (defaults to 10).limit exceeds 100, the server automatically caps it at 100.skip = (page - 1) * limit.totalPages = Math.ceil(totalItems / limit) and hasNextPage = page < totalPages."all users found successfully" if records exist, otherwise "users not found".{ isActive: { $ne: false } }) via Mongoose pre(/^find/) hook.getRateLimiter: Maximum 30 requests per minute per client IP.| name | string Example: name=Keyur Case-insensitive substring search matching user's full name. |
string Example: email=gmail.com Case-insensitive substring search matching user's email address. | |
| phone | string Example: phone=9876 Substring digit pattern matching user's telephone number. |
| sort | string Default: "new" Enum: "new" "old" Example: sort=new Ordering criterion for user accounts:
|
| page | integer >= 1 Default: 1 Example: page=1 1-based page index for pagination. Defaults to 1. |
| limit | integer [ 1 .. 100 ] Default: 10 Example: limit=10 Number of user records returned per page. Defaults to 10. Automatically clamped to a maximum of 100 by the server. |
{- "status": "success",
- "length": 2,
- "pagination": {
- "totalItems": 45,
- "totalPages": 5,
- "currentPage": 1,
- "hasNextPage": true
}, - "message": "all users found successfully",
- "allUser": [
- {
- "_id": "683aa9c34ae6c56e60594eb2",
- "address": {
- "line": "Near Metro Station",
- "street": "123 Tech Park Road",
- "city": "Ahmedabad",
- "state": "Gujarat",
- "zipCode": 382418,
- "country": "India"
}, - "name": "Keyur Panchal",
- "email": "keyur.panchal@example.com",
- "isEmailVerified": true,
- "role": "user",
- "dateOfBirth": "2005-06-08T00:00:00.000Z",
- "phoneNumber": 9876543210,
- "gender": "male",
- "isActive": true,
- "totalOrderes": 2,
- "totalSpent": 1250,
- "createdAt": "2025-05-31T07:03:31.815Z"
}, - {
- "_id": "683aa9c34ae6c56e60594eb3",
- "address": {
- "line": "Near Metro Station",
- "street": "123 Tech Park Road",
- "city": "Ahmedabad",
- "state": "Gujarat",
- "zipCode": 382418,
- "country": "India"
}, - "name": "Jane Smith",
- "email": "jane.smith@example.com",
- "isEmailVerified": true,
- "role": "user",
- "dateOfBirth": "1998-11-20T00:00:00.000Z",
- "phoneNumber": 9123456780,
- "gender": "female",
- "isActive": true,
- "totalOrderes": 5,
- "totalSpent": 4320,
- "createdAt": "2025-06-01T12:00:00.000Z"
}
]
}Retrieves complete personal profile and account metadata for an individual user by their unique MongoDB ObjectId.
protect middleware).role: 'admin' (restrictTo("admin")).isEmailVerified: true).validateId("userId") validates that userId is a valid
24-character hexadecimal MongoDB ObjectId before execution.{ isActive: { $ne: false } }). If the user does not exist or has been suspended, returns
HTTP 404 Not Found ("user does not exists").getRateLimiter: Maximum 30 requests per minute per client IP.| userId required | string^[0-9a-fA-F]{24}$ Example: 683aa9c34ae6c56e60594eb2 The 24-character hexadecimal MongoDB ObjectId uniquely identifying the user account. |
{- "status": "success",
- "message": "user found successfully",
- "data": {
- "_id": "683aa9c34ae6c56e60594eb2",
- "name": "Keyur Panchal",
- "email": "keyur.panchal@example.com",
- "isEmailVerified": true,
- "role": "user",
- "dateOfBirth": "2005-06-08T00:00:00.000Z",
- "phoneNumber": 9876543210,
- "gender": "male",
- "address": {
- "line": "Near Metro Station",
- "street": "123 Tech Park Road",
- "city": "Ahmedabad",
- "state": "Gujarat",
- "zipCode": 382418,
- "country": "India"
}, - "isActive": true,
- "totalOrderes": 2,
- "totalSpent": 1250,
- "faviourites": [
- "683aab454ae6c56e60594eb7"
], - "createdAt": "2025-05-31T07:03:31.815Z",
- "updatedAt": "2026-05-12T10:00:00.000Z"
}
}Customer book wishlist and saved items management endpoints.
Includes:
/wishlist)/wishlist/{bookId})/wishlist/{bookId})Retrieves the complete list of books saved to the authenticated customer's wishlist, with full catalog metadata populated for each book.
Authorization header (protect middleware).isEmailVerified: true).Wishlist collection matching { user: req.user._id }.book with essential catalog attributes:
title, author, price, coverImage, category, ratingAverage, and current inventory stock.getRateLimiter: Maximum 30 requests per minute per client IP.{- "status": "success",
- "message": "All wishlist items are successfully found",
- "results": 2,
- "data": [
- {
- "_id": "683b5f104ae6c56e60594eee",
- "user": "683aa9c34ae6c56e60594eb2",
- "book": {
- "_id": "683aab454ae6c56e60594eb7",
- "title": "Clean Code",
- "author": "Robert C. Martin",
- "price": 450,
- "category": "Action",
- "ratingAverage": 4.8,
- "stock": 15
}, - "createdAt": "2026-05-12T10:00:00.000Z"
}, - {
- "_id": "683b5f104ae6c56e60594eef",
- "user": "683aa9c34ae6c56e60594eb2",
- "book": {
- "_id": "683aab454ae6c56e60594eb9",
- "title": "Designing Data-Intensive Applications",
- "author": "Martin Kleppmann",
- "price": 750,
- "category": "Action",
- "ratingAverage": 4.9,
- "stock": 8
}, - "createdAt": "2026-05-11T14:30:00.000Z"
}
]
}Saves a designated book to the authenticated customer's personal wishlist for future purchase.
protect middleware).isEmailVerified: true).validateId("bookId") verifies that bookId is a syntactically
valid 24-character hexadecimal MongoDB ObjectId.Books.findById(bookId). If the target book does not exist in the catalog,
the request fails immediately with HTTP 404 Not Found ("Book not found").Wishlist.findOne({ user: req.user._id, book: bookId }). If the book already resides in
the customer's wishlist, the request is rejected with HTTP 400 Bad Request
("Book is already in your wishlist").Wishlist schema enforces a compound unique index { user: 1, book: 1 } guaranteeing
concurrency-safe deduplication.mutationLimiter: Maximum 10 mutation requests per minute per client IP.| bookId required | string^[0-9a-fA-F]{24}$ Example: 683aab454ae6c56e60594eb7 The 24-character hexadecimal MongoDB ObjectId uniquely identifying the book. |
{- "status": "success",
- "message": "Book successfully added to wishlist",
- "data": {
- "_id": "683b5f104ae6c56e60594eee",
- "user": "683aa9c34ae6c56e60594eb2",
- "book": "683aab454ae6c56e60594eb7",
- "createdAt": "2026-05-12T10:00:00.000Z"
}
}Removes a designated book from the authenticated customer's wishlist.
protect middleware).isEmailVerified: true).validateId("bookId") verifies that bookId is a syntactically
valid 24-character hexadecimal MongoDB ObjectId.Wishlist.findOneAndDelete({ user: req.user._id, book: bookId }).404 Not Found ("Book is not in your wishlist").200 OK.generalRateLimiter: Maximum 15 requests per minute per client IP.| bookId required | string^[0-9a-fA-F]{24}$ Example: 683aab454ae6c56e60594eb7 The 24-character hexadecimal MongoDB ObjectId uniquely identifying the book. |
{- "status": "success",
- "message": "Book successfully removed from wishlist"
}