Full rental marketplace with 6 categories (apartment, house, car, motorcycle, bicycle, ebike). Booking workflow: create → confirm → pay → active → complete → payout. Landlord dashboard, admin moderation, availability calendar, Stripe Connect payouts. 14 QA bugs found and fixed including validator schemas, API response types, HTTP methods. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
18 KiB
Architecture
High-Level Overview
┌──────────────────────────────────────────────────────────────┐
│ Client (React) │
│ http://localhost:5173 (Vite) │
│ │
│ ┌──────────┐ ┌──────────┐ ┌───────────┐ ┌────────────┐ │
│ │ Pages │ │Components│ │ Context │ │ API Layer │ │
│ └──────────┘ └──────────┘ └───────────┘ └─────┬──────┘ │
│ │ │
└────────────────────────────────────────────────────┼─────────┘
REST API (HTTP) │ WebSocket │
│ (Socket.io) │
┌──────────────────────────────────┼─────────────────┼─────────┐
│ Server (Express) │ │
│ http://localhost:3000 │ │
│ │ │
│ ┌───────────┐ ┌────────────┐ ┌──────────────┐ │ │
│ │ Middleware │ │ Routes │ │ Socket.io │◄─┘ │
│ │ (auth, │ │ (REST API) │ │ (real-time) │ │
│ │ upload, │ └─────┬──────┘ └──────┬───────┘ │
│ │ validate)│ │ │ │
│ └───────────┘ │ │ │
│ ┌────┴────────────────┴──────┐ │
│ │ Prisma ORM │ │
│ └────────────┬───────────────┘ │
│ │ │
└───────────────────────────────┼──────────────────────────────┘
│
┌───────────────────────────────┼──────────────────────────────┐
│ PostgreSQL 17 (Docker) │
│ marketplace-postgres container │
│ Volume: marketplace-pgdata │
└──────────────────────────────────────────────────────────────┘
External Services:
┌─────────┐ ┌──────────────┐
│ Stripe │ │ Google Maps │
│ (pay) │ │ (location) │
└─────────┘ └──────────────┘
Client-Server Communication
REST API
The client communicates with the server via a JSON REST API. All endpoints are prefixed with /api/.
| Prefix | Purpose |
|---|---|
/api/auth |
Registration, login, token refresh, logout |
/api/users |
User profiles, settings, privacy |
/api/listings |
CRUD for buy/sell listings, search, favorites |
/api/offers |
Create, counter, accept, decline offers |
/api/chat |
Conversations and message history |
/api/notifications |
Fetch and mark notifications as read |
/api/payments |
Stripe payment intents, webhooks |
/api/location |
Google Maps autocomplete proxy |
/api/reports |
Content/user reporting |
/api/subscriptions |
Seller subscription management |
/api/promotions |
Listing promotion/boost |
/api/admin |
Admin dashboard, user/listing management |
/api/rentals |
Rental listing CRUD and search |
/api/bookings |
Booking lifecycle management |
/api/rental-payments |
Rental payment processing and webhooks |
/api/payouts |
Landlord payout management |
/api/rental-reviews |
Tenant/landlord reviews |
/api/health |
Health check endpoint |
Socket.io (WebSocket)
Socket.io provides the real-time communication layer. The server creates a Socket.io instance attached to the same HTTP server as Express.
Events flow:
- The client connects with an auth token after login
- The server authenticates the socket connection and associates it with a user
- Real-time events are emitted for: new messages, notifications (offers, bookings, moderation), typing indicators, and online status
Database Layer
Prisma ORM
The database schema is defined in server/prisma/schema.prisma. Prisma generates a type-safe client used throughout the server routes.
Key models:
| Model | Purpose |
|---|---|
User |
Accounts with roles, privacy settings, landlord status |
Session |
JWT refresh token sessions |
Listing |
Buy/sell items with category, condition, price |
ListingImage |
Image attachments for listings |
Offer |
Price negotiation between buyer and seller |
Conversation |
Chat threads between two users |
Message |
Individual chat messages |
Favorite |
User bookmarks on listings |
Notification |
In-app notifications (19 distinct types) |
Payment |
Payment records (8 payment types) |
Report |
Content/user reports for moderation |
Subscription |
Seller tier subscriptions |
PromotedListing |
Boosted listing placements |
ModerationLog |
Audit trail for moderation actions |
BlockedUser |
User blocking relationships |
RentalListing |
Rental items (apartments, cars, bikes, etc.) |
Booking |
Rental booking with status lifecycle |
RentalReview |
Reviews for completed rentals |
Payout |
Landlord payout tracking |
Enums
The schema uses PostgreSQL enums extensively for type safety:
- User roles:
USER,MODERATOR,ADMIN,SUPER_ADMIN - Listing categories:
ELECTRONICS,FURNITURE,CLOTHING,HOME_GARDEN,SPORTS,BOOKS,GAMES,VEHICLES,OTHER - Rental categories:
APARTMENT,HOUSE,CAR,MOTORCYCLE,BICYCLE,EBIKE - Booking status:
PENDING->CONFIRMED->ACTIVE->COMPLETED(orCANCELLED_*/REJECTED/EXPIRED) - Cancellation policy:
FLEXIBLE,MODERATE,STRICT
Authentication Flow
The application uses JWT-based authentication with access and refresh tokens.
┌────────┐ ┌────────┐
│ Client │ │ Server │
└───┬────┘ └───┬────┘
│ POST /api/auth/login │
│ { email, password } │
├──────────────────────────────────────►│
│ │ Verify password (bcryptjs)
│ │ Generate access token (short-lived)
│ │ Generate refresh token (long-lived)
│ │ Store session in DB
│ { accessToken, user } │
│ Set-Cookie: refreshToken (httpOnly) │
│◄──────────────────────────────────────┤
│ │
│ GET /api/listings │
│ Authorization: Bearer <accessToken> │
├──────────────────────────────────────►│
│ │ auth middleware verifies JWT
│ │ checkBanned middleware
│ { listings: [...] } │
│◄──────────────────────────────────────┤
│ │
│ POST /api/auth/refresh │
│ Cookie: refreshToken │
├──────────────────────────────────────►│
│ │ Validate refresh token
│ │ Issue new access token
│ { accessToken } │
│◄──────────────────────────────────────┤
Middleware Chain
Requests pass through several middleware layers:
helmet-- Security headerscors-- Restricts origins toCLIENT_URLcookieParser-- Parses refresh token cookiesexpress-rate-limit-- Rate limits auth endpoints (20 requests / 15 min)auth-- Verifies JWT access token, attaches user to requestcheckBanned-- Blocks banned usersrequireRole-- Role-based access control (e.g., admin-only routes)upload-- Multer file upload handlingvalidate-- Zod schema validation for request bodies
Payment Flow
Marketplace Payments (Listings)
Stripe PaymentIntents are used for buy/sell transactions:
┌────────┐ ┌────────┐ ┌────────┐
│ Buyer │ │ Server │ │ Stripe │
└───┬────┘ └───┬────┘ └───┬────┘
│ Create PI │ │
├───────────────►│ POST /api/ │
│ │ payments │
│ ├───────────────►│ stripe.paymentIntents.create()
│ │ clientSecret │
│ │◄───────────────┤
│ clientSecret │ │
│◄───────────────┤ │
│ │ │
│ Confirm (Stripe.js) │
├────────────────────────────────►│
│ │ │
│ │ Webhook event │
│ │◄───────────────┤ payment_intent.succeeded
│ │ Update DB │
│ │ Notify seller │
│ │ │
Rental Payments
Rental bookings follow a similar flow with additional handling for:
- Security deposits -- Held and refunded after completion
- Landlord payouts -- Via Stripe Connect (
stripeAccountIdon User model) - Commissions -- Platform fee deducted from rental payments
- Separate webhook endpoint --
/api/rental-payments/webhook
Payment Types
The system tracks eight distinct payment types:
| Type | Description |
|---|---|
LISTING_FEE |
Fee to publish a listing |
COMMISSION |
Platform commission on sales |
PROMOTION |
Listing boost/promotion fee |
SUBSCRIPTION |
Seller subscription payment |
RENTAL_BOOKING |
Tenant rental payment |
RENTAL_COMMISSION |
Platform fee on rentals |
RENTAL_DEPOSIT |
Security deposit hold |
RENTAL_DEPOSIT_REFUND |
Deposit refund after completion |
RENTAL_PAYOUT |
Payment to landlord via Connect |
File Uploads
File uploads are handled by Multer with local disk storage.
- Configuration:
server/src/middleware/upload.ts - Storage directory: Configurable via
UPLOAD_DIRenv var (default:./uploads) - Serving: Static files served at
/uploads/*viaexpress.static - Usage: Listing images, rental listing photos, user avatars
Files are stored on the local filesystem. In production, this should be replaced with cloud storage (e.g., S3, Cloudflare R2).
Real-Time Communication
Socket.io is configured in server/src/socket/index.ts and attached to the HTTP server alongside Express.
Connection Flow
- Client establishes WebSocket connection after successful login
- Server authenticates the connection using the JWT token
- User is registered as "online" and associated with their socket ID
Event Types
| Category | Events |
|---|---|
| Chat | New message, typing indicator, read receipts |
| Notifications | New offer, offer response, booking updates, payout status, moderation actions |
| Presence | User online/offline status |
Architecture
Client A ──WebSocket──┐
├──► Socket.io Server ──► Emit to Client B
Client B ──WebSocket──┘ │
│
┌─────┴─────┐
│ Express │
│ routes │
│ can emit │
│ via io │
└───────────┘
Express routes access the Socket.io instance via app.get('io') to emit events from REST handlers (e.g., sending a notification when an offer is created via the REST API).
Security
| Concern | Implementation |
|---|---|
| HTTP headers | Helmet (XSS, clickjacking, MIME sniffing) |
| CORS | Restricted to CLIENT_URL |
| Rate limiting | 20 req/15 min on /api/auth/login and /register |
| Input validation | Zod schemas on all mutation endpoints |
| Password hashing | bcryptjs |
| Token storage | Refresh token in httpOnly cookie |
| Role-based access | requireRole middleware for admin/mod routes |
| User banning | checkBanned middleware on protected routes |
| Webhook verification | Stripe webhook signature verification |
Deployment Considerations
Database
- Use a managed PostgreSQL service (e.g., AWS RDS, Supabase, Neon) in production
- Run
npx prisma migrate deployto apply migrations - The Docker setup is intended for local development only
File Storage
- Replace local Multer storage with S3-compatible storage (AWS S3, Cloudflare R2, MinIO)
- Update the upload middleware and static file serving accordingly
Environment
- Set strong, unique values for
JWT_SECRETandJWT_REFRESH_SECRET - Use production Stripe keys and configure webhook endpoints
- Set
CLIENT_URLto the deployed frontend URL
Scaling
- Horizontal scaling: Socket.io requires a Redis adapter (
@socket.io/redis-adapter) for multi-instance deployments - Static assets: Serve the built React app via a CDN or reverse proxy (Nginx, Cloudflare)
- API: The Express server is stateless (JWT-based auth) and can be scaled horizontally behind a load balancer
Recommended Production Stack
┌───────────┐
│ CDN / │
│ Nginx │
└─────┬─────┘
│
┌───────────┴───────────┐
│ │
┌────────▼─────┐ ┌─────────▼────────┐
│ React Static │ │ Express API x N │
│ (built) │ │ (load balanced) │
└──────────────┘ └────────┬──────────┘
│
┌───────────────┼───────────────┐
│ │ │
┌──────▼──┐ ┌──────▼──┐ ┌───────▼──┐
│PostgreSQL│ │ Redis │ │ S3 / R2 │
│(managed) │ │(sockets)│ │ (files) │
└──────────┘ └─────────┘ └──────────┘