Skip to content

nazmul-nhb/bicycle-boulevard-server

Repository files navigation

Bicycle Boulevard Server 🚲

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.

Key Highlights

  • 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 ErrorWithStatus to create custom instance of Error.

Features

🚴 Bicycle Management

  • Create, read, update, and delete bicycles.
  • Search for bicycles by name, brand, or category.
  • Supports categories such as Mountain, Road, Hybrid, BMX, and Electric.

πŸ“¦ Order Management

  • Place orders with customer email and product (bicycle) id.
  • Automatically adjust stock quantities and availability based on orders.
  • Prevent orders if stock is insufficient.

πŸ“Š Revenue Insights

  • Calculate total revenue from all orders using MongoDB aggregation.

βš™οΈ Error Handling

  • Uniform error responses for validation (mostly zod and MongoDB), duplication, casting (MongoDB ObjectId), parsing, insufficient, not found and almost every possible types of errors.
  • Clear and structured error messages to facilitate debugging.

Technologies (Packages) Used

  • TypeScript
  • Node.js
  • Express.js
  • Mongoose
  • cors
  • dotenv
  • bcrypt
  • jsonwebtoken
  • cookie-parser

Run the Server Locally

Prerequisites

  • Node.js (v20+)
  • pnpm package manager
  • if you prefer npm or yarn, delete pnpm-lock.yaml file and follow the following steps

Installation

  1. Clone the repository:

    git clone https://github.com/nazmul-nhb/bicycle-boulevard-server.git
    cd bicycle-boulevard-server
  2. Install dependencies:

    pnpm install

    for npm:

    npm install

    for yarn:

    yarn install
  3. Set up environment variables: Create a .env file in the root directory with the following fields:

    PORT=4242
    MONGO_URI=your_mongo_db_uri
  4. Start the server:

    pnpm start

    for npm:

    npm start

    for yarn:

    yarn start
  5. Access the API at:

    http://localhost:4242

API Documentation

Base URL

http://localhost:4242

Endpoints

Products (Bicycles)

  1. 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"
        }
    }
  2. Get All Bicycles

    • GET /api/products
    • Query parameters: searchTerm
    • Query example: /api/products?searchTerm=partialValueOfField (searchTerm can be any partial value of name, 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"
            }
        ]
    }
  3. 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"
        }
    }
  4. 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"
        }
    }
  5. Delete a Bicycle

    • DELETE /api/products/:productId

    • Response:

    {
        "message": "Bicycle deleted successfully!",
        "status": true,
        "data": {}
    }

Orders

  1. Place an Order

    • POST /api/orders

    • Request body:

      {
        "email": "customer@example.com",
        "product": "productId",
        "quantity": 2,
        "totalPrice": 600
      }
    • totalPrice is 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
        }
    }
  2. Get Revenue

    • GET /api/orders/revenue

    • Response:

      {
        "message": "Revenue calculated successfully!",
        "status": true,
        "data": { "totalRevenue": 1200 }
      }

Error Responses

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
}