A robust backend API built with Express and TypeScript, designed for managing bicycle inventory and orders with MongoDB and Mongoose. The server supports CRUD operations, advanced error handling, and comprehensive input validation powered by Zod and custom error processors.
- Bicycle and Order Management: Simplified endpoints for adding, updating, and searching bicycles, with order processing and revenue tracking.
- Custom Error Handling: Centralized handling for
schema validation,database operations, and other potential failures using custom utility functions. - Custom Error Instance: Used a class
ErrorWithStatusto create custom instance ofError.
- Create, read, update, and delete bicycles.
- Search for bicycles by
name,brand, orcategory. - Supports categories such as Mountain, Road, Hybrid, BMX, and Electric.
- Place orders with customer
emailand product (bicycle)id. - Automatically adjust stock quantities and availability based on orders.
- Prevent orders if stock is insufficient.
- Calculate total revenue from all orders using MongoDB aggregation.
- Uniform error responses for
validation(mostlyzodandMongoDB),duplication,casting(MongoDBObjectId),parsing,insufficient,not foundand almost every possible types of errors. - Clear and structured error messages to facilitate debugging.
TypeScriptNode.jsExpress.jsMongoosecorsdotenvbcryptjsonwebtokencookie-parser
- Node.js (v20+)
pnpmpackage manager- if you prefer
npmoryarn, deletepnpm-lock.yamlfile and follow the following steps
-
Clone the repository:
git clone https://github.com/nazmul-nhb/bicycle-boulevard-server.git cd bicycle-boulevard-server -
Install dependencies:
pnpm install
for
npm:npm install
for
yarn:yarn install
-
Set up environment variables: Create a
.envfile in the root directory with the following fields:PORT=4242 MONGO_URI=your_mongo_db_uri
-
Start the server:
pnpm start
for
npm:npm start
for
yarn:yarn start
-
Access the API at:
http://localhost:4242
http://localhost:4242
-
Create a Bicycle
-
POST
/api/products -
Request body:
{ "name": "Roadster 5000", "brand": "SpeedX", "price": 300, "category": "Road", "description": "A premium road bike designed for speed and performance.", "quantity": 20, "inStock": true } -
Response:
{ "message": "Bicycle created successfully!", "success": true, "data": { "name": "Roadster 5000", "brand": "SpeedX", "price": 300, "category": "Road", "description": "A premium road bike designed for speed and performance.", "quantity": 20, "inStock": true, "_id": "674339111fb2a11d437591ab", "createdAt": "2024-11-24T14:32:49.261Z", "updatedAt": "2024-11-24T14:32:49.261Z" } } -
-
Get All Bicycles
- GET
/api/products - Query parameters:
searchTerm - Query example:
/api/products?searchTerm=partialValueOfField(searchTermcan be any partial value ofname,brand,category) - Response:
{ "message": "Bicycles retrieved successfully!", "status": true, "data": [ { "_id": "6742c11a49c1956daec11abd", "name": "SpeedKing 500", "brand": "Velocity", "price": 1400, "category": "Road", "description": "Sleek road bike designed for speed enthusiasts.", "quantity": 10, "inStock": true, "createdAt": "2024-11-24T06:00:58.848Z", "updatedAt": "2024-11-24T14:17:00.495Z" }, { "_id": "6742c12549c1956daec11abf", "name": "UrbanSprint", "brand": "CityCyclers", "price": 900, "category": "Road", "description": "Compact road bike perfect for urban commuting.", "quantity": 20, "inStock": true, "createdAt": "2024-11-24T06:01:09.382Z", "updatedAt": "2024-11-24T06:01:09.382Z" }, { "_id": "6742c12d49c1956daec11ac1", "name": "Elite Strider", "brand": "SwiftRiders", "price": 1800, "category": "Road", "description": "Premium road bike for professional cyclists.", "quantity": 4, "inStock": true, "createdAt": "2024-11-24T06:01:17.674Z", "updatedAt": "2024-11-24T06:01:17.674Z" } ] } - GET
-
Get a Specific Bicycle
- GET
/api/products/:productId - Response:
{ "message": "Bicycle retrieved successfully!", "status": true, "data": { "_id": "6742c11a49c1956daec11abd", "name": "SpeedKing 500", "brand": "Velocity", "price": 1400, "category": "Road", "description": "Sleek road bike designed for speed enthusiasts.", "quantity": 12, "inStock": true, "createdAt": "2024-11-24T06:00:58.848Z", "updatedAt": "2024-11-24T06:00:58.848Z" } } - GET
-
Update a Bicycle
- PUT
/api/products/:productId - Request body contains fields to update:
{ "brand": "SpeedX", "price": 700, "category": "Mountain", "quantity": 12, }- Response:
{ "message": "Bicycle deleted successfully!", "status": true, "data": { "name": "Roadster 5000", "brand": "SpeedX", "price": 700, "category": "Mountain", "description": "A premium road bike designed for speed and performance.", "quantity": 12, "inStock": true, "_id": "674339111fb2a11d437591ab", "createdAt": "2024-11-24T14:32:49.261Z", "updatedAt": "2024-11-24T14:58:31.261Z" } } - PUT
-
Delete a Bicycle
-
DELETE
/api/products/:productId -
Response:
{ "message": "Bicycle deleted successfully!", "status": true, "data": {} } -
-
Place an Order
-
POST
/api/orders -
Request body:
{ "email": "customer@example.com", "product": "productId", "quantity": 2, "totalPrice": 600 } -
totalPriceis optional, if not provided, the application will calculate total price from the product (bicycle) collection -
Response:
{ "message": "Order created successfully!", "status": true, "data": { "email": "customer@example.com", "product": "6742c11a49c1956daec11abd", "quantity": 2, "_id": "6743355c1fb2a11d437591a4", "createdAt": "2024-11-24T14:17:00.399Z", "updatedAt": "2024-11-24T14:17:00.399Z", "totalPrice": 2800 } } -
-
Get Revenue
-
GET
/api/orders/revenue -
Response:
{ "message": "Revenue calculated successfully!", "status": true, "data": { "totalRevenue": 1200 } }
-
All error responses follow this structured format:
{
"success": false,
"message": "Error message",
"statusCode": 400,
"errors": [
{
"name": "Error name",
"path": "where error occurred if traced",
"message": "Error message"
},
{...}
],
"stack": "Error stack trace if available" // Only in development
}