Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

11 Commits
 
 
 
 
 
 
 
 
 
 

Repository files navigation

🚗 CarTrade

CarTrade is a full-stack MERN (MongoDB, Express, React, Node.js) used-car marketplace. Anyone can browse and filter available cars without logging in, registered users can save favourites and contact dealers, and dealers can manage their own vehicle listings through a dedicated dashboard.


Table of Contents


Overview

CarTrade provides a centralized marketplace where buyers can compare used vehicles while dealers can publish and manage listings.

Role Capabilities
Guest Browse cars, search / filter / sort, view car details
Registered User Everything a guest can do, plus favourites and contacting dealers
Dealer Browse cars, add listings, edit/delete own listings, manage inquiries

Note: Login is not required to browse cars. There is no admin role — only user and dealer.

Open CarTrade → Browse Cars → Search/Filter/Sort → View Car Details
   → Want an account? → Register → Choose Role
          /                              \
        User                           Dealer
          |                               |
 Favourites / Contact Dealer   Dashboard (Add/Edit/Delete Own Cars)

Features

  • 🔍 Search, filter, and sort car listings (make, colour, price, etc.)
  • 🧾 OEM specification data normalized and linked to individual listings
  • 🔐 JWT-based authentication with bcrypt password hashing
  • 🛡️ Role-based access control (RBAC) for dealer-only actions
  • 🖼️ Vehicle image uploads to AWS S3 via Multer memory storage
  • ❤️ Favourites for registered users
  • ✉️ Contact-dealer / inquiry system
  • ⚙️ Redux + Redux Thunk for shared app state (auth, inventory)

Tech Stack

Frontend: React, Redux, Redux Thunk, Axios Backend: Node.js, Express, Mongoose Database: MongoDB (Atlas) Storage: AWS S3 (via AWS SDK) Auth: bcrypt, JSON Web Tokens (JWT) Security middleware: Helmet, CORS, Multer (upload limits)


Architecture

CarTrade follows a classic three-tier architecture:

Presentation Layer (React)
            |
Application Layer (Node.js + Express)
            |
      Data Layer (MongoDB)

Express -----> AWS S3 (Images)

This gives separation of concerns — the UI has no database logic, and MongoDB is never exposed directly to the browser — which keeps the system easier to maintain, secure, test, and scale.

Request flow:

React → Axios HTTP request → Express REST API → Mongoose → MongoDB
      ← Express returns JSON ← React updates UI

Why MERN?

Using JavaScript across both client and server reduces context switching during development:

  • MongoDB — stores data
  • Express.js — exposes REST APIs, runs on Node.js
  • React — frontend
  • Node.js — runs JS on the server

REST conventions

Method Purpose Example
GET Retrieve data GET /api/cars
POST Create a resource POST /api/cars
PATCH Update a resource PATCH /api/cars/:id
DELETE Delete a resource DELETE /api/cars/:id

Database Design

Factory (OEM) information and individual used-car information are modeled as separate, normalized collections.

  • OEM Specs (factory data): make, model, year, engine size, horsepower, transmission, fuel type
  • Marketplace Listing (per-car data): price, mileage, colour, accident history, condition notes, image URL, dealer — linked to OEM via ObjectId
// Inventory (listing)
{ price: 12000000, oemSpecs: "689abc123..." }

// OEM document
{ _id: "689abc123...", make: "Toyota", model: "Corolla", horsepower: 140 }

.populate("oemSpecs") resolves the reference into the full nested OEM object — conceptually similar to a join in a relational database.

Trade-off: normalizing OEM data avoids duplicating static factory specs across listings, at the cost of more complex read queries (populate / aggregation) when a view needs joined data.

MongoDB vs Mongoose

  • MongoDB — the database itself
  • Mongoose — an ODM sitting between Express and MongoDB, providing schema definitions, validation, ObjectId references, populate(), and CRUD queries

MongoDB's flexible document model suits varied vehicle attributes, while Mongoose still enforces schemas and relationships where needed.


Authentication & Authorization

Registration & Login

Name, Email, Password, Role (User/Dealer)
  → POST /api/auth/register → Validate → Check duplicate email
  → bcrypt.hash(password) → Store user → MongoDB
Email + Password → POST /api/auth/login → MongoDB finds user
  → bcrypt.compare() → Match? → No: 401 | Yes: Generate JWT → React

Passwords are never stored as plaintext — only the bcrypt hash. Login uses bcrypt.compare(); passwords are never decrypted.

JWT

The backend issues a JWT after successful login. The frontend stores it and sends it with protected requests via Authorization: Bearer <token>.

{ id: user._id, role: user.role } // payload, expiresIn: "1d"

Authentication vs Authorization

Question Mechanism
Authentication "Who are you?" Email + password → JWT
Authorization "What are you allowed to do?" role: "user" vs role: "dealer"

Role-Based Access Control (RBAC)

router.post("/cars", authenticate, requireRole("dealer"), createCar);

Hiding an "Add Car" button in React is only a UX affordance — it does not stop a direct POST /api/cars call via a tool like Postman. Authorization must be enforced server-side.

Ownership checks

Role checking alone isn't enough to stop Dealer A from editing Dealer B's car. Each listing stores { owner: dealerId }; edit/delete requests compare the JWT user ID against the listing owner and return 403 Forbidden on mismatch.

Ownership (and any user-identifying field) is always derived from the verified JWT — never trusted from the request body:

// ❌ Bad — client-controlled
{ owner: req.body.owner }

// ✅ Good — derived from verified token
{ owner: req.user.id }

Image Uploads

User selects image → React → FormData → POST /upload/image → Express
  → Multer (memoryStorage) → in-memory buffer → AWS SDK → S3 bucket
  → Image URL → React → Car listing → MongoDB
  • FormData is used instead of JSON because binary files need multipart/form-data.
  • Multer (memoryStorage()) keeps the image as an in-memory buffer rather than writing a temporary file to disk, keeping the server stateless. An upload size limit (5 MB) guards against excessive memory use under concurrent uploads.
  • AWS S3 stores the actual image object; MongoDB stores only the resulting URL, keeping database records small and separating structured data from binary storage.
  • Filenames use crypto.randomUUID() to avoid collisions (e.g. cars/a3c8...-car.jpg).

S3 was chosen over a managed media service like Cloudinary to get direct control over validation, authentication, naming, access policy, and future processing — and hands-on experience with the AWS SDK, S3, and IAM.


API Reference

Base URL: /api

Auth

Method Endpoint Description
POST /auth/register Register a new user or dealer
POST /auth/login Authenticate and receive a JWT

Cars

Method Endpoint Description Access
GET /cars List/search/filter/sort cars Public
GET /cars/:id Get car details Public
POST /cars Create a listing Dealer
PATCH /cars/:id Update own listing Dealer (owner)
DELETE /cars/:id Delete own listing Dealer (owner)

Example filter query:

GET /api/cars?make=Toyota&color=Black&sortPrice=asc

Favourites & Inquiries

Method Endpoint Description Access
POST /favourites/:carId Save a car to favourites Registered user
POST /inquiries Contact a dealer about a car Registered user

Uploads

Method Endpoint Description Access
POST /upload/image Upload a vehicle image to S3 Dealer

Status codes

Code Meaning
200 Successful request
201 Resource created
400 Bad input
401 Not authenticated
403 Authenticated but not permitted
404 Resource not found
409 Conflict (e.g. duplicate email)
500 Server error

Getting Started

Prerequisites

  • Node.js
  • MongoDB (local or Atlas)
  • An AWS account with an S3 bucket configured

Installation

# Clone the repo
git clone https://github.com/<your-username>/cartrade.git
cd cartrade

# Install backend dependencies
cd server
npm install

# Install frontend dependencies
cd ../client
npm install

Running locally

# Start the backend (default: localhost:5000)
cd server
npm run dev

# Start the frontend (default: localhost:3000)
cd client
npm start

Environment Variables

Create a .env file in the server directory:

PORT=5000
MONGO_URI=your_mongodb_connection_string
JWT_SECRET=your_jwt_secret
JWT_EXPIRES_IN=1d

AWS_ACCESS_KEY_ID=your_aws_access_key
AWS_SECRET_ACCESS_KEY=your_aws_secret_key
AWS_REGION=your_aws_region
AWS_S3_BUCKET=your_bucket_name

CLIENT_ORIGIN=http://localhost:3000

Project Structure

cartrade/
├── client/                 # React frontend
│   ├── src/
│   │   ├── components/
│   │   ├── pages/
│   │   ├── redux/          # store, actions, reducers, thunks
│   │   └── services/       # axios instances / API calls
│   └── package.json
├── server/                  # Express backend
│   ├── models/              # Mongoose schemas (User, Car, OEMSpec, Inquiry...)
│   ├── routes/
│   ├── controllers/
│   ├── middleware/          # authenticate, requireRole, upload (multer)
│   ├── config/               # db connection, S3 client
│   └── package.json
└── README.md

Known Limitations & Roadmap

CarTrade has several scalable characteristics: images are offloaded to S3, authentication is stateless (JWT), MongoDB Atlas can scale independently, and the frontend/backend are cleanly separated. That said, before this is production-grade, the following are planned or in progress:

  • Centralized request validation (reject invalid payloads, e.g. negative price/mileage, with 400)
  • Refactor the current branching filter logic into a dynamic aggregation pipeline
  • Database indexes and pagination for listing queries
  • Caching where appropriate
  • Stronger token management (refresh tokens, revocation strategy)
  • Rate limiting
  • Automated tests
  • CI/CD and deployment automation
  • Monitoring / logging in production

Design trade-off — JWT vs sessions: JWT enables stateless authentication, so the backend doesn't need a central session store, which simplifies horizontal scaling. The trade-off is that token revocation is harder, making expiry and refresh-token strategy important.


Learnings

Building CarTrade involved full-stack data flow, REST API design, MongoDB relationships via Mongoose (ObjectId references, populate(), aggregation with $lookup / $unwind / $match / $sort), JWT authentication, authorization/ownership concerns, cloud object storage with S3, file handling with Multer, and the trade-offs between normalized data and query complexity.

One of the more challenging areas was filtering and sorting listings when some attributes lived on the inventory collection while others came from referenced OEM specs — solved using Mongoose populate combined with MongoDB aggregation.


About

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages