The universal entertainment hub. Discover, rate, and track every movie, TV show, anime, manga, novel, comic, video game, and documentary — all in one place.
PickWise is a full-stack MERN platform that aggregates content from four external APIs (TMDB, Jikan/MAL, RAWG, Google Books) into a unified catalog. Users can search across all media types simultaneously, write reviews, manage a personal watchlist, explore shared universes via a Cross-IP mapping system, and follow upcoming releases on a live timeline.
| Layer | URL |
|---|---|
| Frontend | https://pickwise.vercel.app |
| Backend API | https://pickwise-server.up.railway.app |
- Unified Global Search — A single search bar queries TMDB (movies, TV, documentaries), Jikan (anime, manga), RAWG (video games), and Google Books (novels, comics) simultaneously. Local DB results are prioritized and scored by exact-title matching and popularity; external APIs fill the gaps intelligently.
- Cross-IP Universe Mapping — Content items store explicit
crossIprelationships (prequel, sequel, adaptation, spin-off, same-universe, etc.) allowing users to navigate from a movie to its manga origin, anime adaptation, or companion novel from a single detail page. - Live Review Aggregation — Each content detail page merges internally-written PickWise reviews with external reviews fetched on-demand from the source API (e.g. TMDB critic reviews), displayed in a unified feed with clear source attribution.
- Personal Watchlist & Progress Tracking — Authenticated users can add any content to their watchlist with a status label (Watching, Completed, On Hold, Dropped, Plan to Watch, Playing, Beaten, Reading) and a numeric progress field.
- Upcoming Releases Timeline — A curated feed of upcoming projects grouped by release window, each with a rich event timeline (trailer releases, casting announcements, platform confirmations, delays, etc.) and Cross-IP linking.
- Role-Based Admin Dashboard — Admins can view platform-wide content/user statistics broken down by category, browse and delete content with pagination, all behind a double-middleware guard (
auth+admin).
| Technology | Version | Purpose |
|---|---|---|
| React | 18.2.0 | UI component library |
| Vite | 5.2.0 | Build tool & dev server |
| React Router DOM | 6.12.1 | Client-side routing |
| TanStack React Query | 4.34.8 | Server state management & caching |
| Axios | 1.6.0 | HTTP client |
| Tailwind CSS | 3.4.10 | Utility-first styling |
| Headless UI | 1.7.13 | Accessible UI primitives |
| Heroicons | 2.0.18 | SVG icon set |
| Technology | Version | Purpose |
|---|---|---|
| Node.js | ≥ 18.x | JavaScript runtime |
| Express | 4.18.2 | HTTP server framework |
| Mongoose | 7.0.3 | MongoDB ODM |
| JSON Web Token | 9.0.0 | Stateless authentication |
| bcryptjs | 2.4.3 | Password hashing |
| express-rate-limit | 6.7.0 | API rate limiting (150 req/min) |
| Morgan | 1.10.0 | HTTP request logging |
| Slugify | 1.6.6 | URL-safe slug generation |
| dotenv | 16.0.3 | Environment variable management |
| API | Data Sourced |
|---|---|
| TMDB | Movies, TV shows, documentaries, cast, backdrops |
| Jikan (MAL) | Anime, manga, light novels |
| RAWG | Video games, platform details, system requirements |
| Google Books | Novels, comics, books |
| Service | Role |
|---|---|
| MongoDB Atlas | Cloud database (Cluster0) |
| Vercel | React SPA hosting |
| Railway (Nixpacks) | Express API hosting |
pickwise/
├── client/ # React + Vite frontend
│ ├── public/ # Static assets (posters, icons)
│ ├── src/
│ │ ├── api/ # Axios API client instances
│ │ ├── components/ # Reusable UI components (Header, Cards, Modals…)
│ │ ├── config/ # App-wide constants (API base URLs)
│ │ ├── hooks/ # Custom React hooks
│ │ ├── pages/
│ │ │ ├── Home.jsx # Hero + category carousels
│ │ │ ├── SearchResults.jsx # Per-category search results page
│ │ │ ├── ContentDetail.jsx # Main detail page (reviews, cross-IP, similar)
│ │ │ ├── Universe.jsx # Franchise / universe browser
│ │ │ ├── Upcoming.jsx # Upcoming releases grouped by month
│ │ │ ├── UpcomingDetail.jsx # Event timeline for a single upcoming project
│ │ │ ├── Profile.jsx # User profile & review history
│ │ │ ├── Settings.jsx # Account settings
│ │ │ ├── AdminDashboard.jsx # Admin stats & content management
│ │ │ └── Login.jsx # Auth page (login + register)
│ │ ├── services/ # Business-logic helpers
│ │ ├── utils/ # Utility functions
│ │ ├── App.jsx # Root router
│ │ └── main.jsx # React entry point
│ ├── vercel.json # Vercel SPA rewrite rules
│ └── package.json
│
├── server/ # Express + MongoDB backend
│ ├── src/
│ │ ├── config/ # DB connection config
│ │ ├── controllers/
│ │ │ ├── globalSearch.js # Hybrid local+external search logic
│ │ │ ├── search.js # Per-category search controller
│ │ │ └── universe.js # Universe aggregation controller
│ │ ├── middleware/
│ │ │ ├── auth.middleware.js # JWT verification middleware
│ │ │ ├── admin.js # Role guard (admin only)
│ │ │ └── apiLimiter.js # Per-route rate limiter
│ │ ├── models/
│ │ │ ├── ContentItem.js # Master content schema
│ │ │ ├── User.js # User account schema
│ │ │ ├── Review.js # User review schema
│ │ │ ├── Watchlist.js # Watchlist entry schema
│ │ │ ├── Comment.js # Comment schema
│ │ │ ├── UpcomingProject.js # Upcoming release schema
│ │ │ └── LoginEvent.js # Login audit schema
│ │ ├── routes/ # Express route definitions (11 route files)
│ │ ├── services/
│ │ │ ├── tmdb.service.js # TMDB API integration
│ │ │ ├── jikan.service.js # Jikan/MAL API integration
│ │ │ ├── rawg.service.js # RAWG API integration
│ │ │ └── googleBooks.service.js # Google Books integration
│ │ ├── seeders/ # DB seed scripts
│ │ ├── scripts/ # Maintenance & data-migration scripts
│ │ └── index.js # Express app entry point
│ ├── railway.json # Railway deployment config
│ └── package.json
│
├── diagrams/ # Architecture diagrams
└── README.md
The global search controller (src/controllers/globalSearch.js) implements a two-phase strategy:
- Local-first phase — queries MongoDB across all 8 categories in parallel using a regex title match. Results are scored: exact title match (+100), prefix match (+50), partial match (+10), then boosted by
popularityScore. Top 5 per category are returned. - External fallback phase — if the total local result count is < 10, all four external APIs are queried via
Promise.allSettled()(TMDB, Jikan anime + manga, RAWG, Google Books novels + comics). External results are deduped against local results byexternalIdor lowercase title before merging.
When a user selects an external search result, the client calls POST /api/content/import. The server checks for an existing document matching { source, externalId } using a unique sparse compound index — returning the existing _id immediately on hit, or creating a new ContentItem document otherwise. This prevents duplicate entries across multiple users discovering the same content.
GET /api/content/:id/details first loads the local MongoDB document, then routes to the appropriate service (tmdb.service, jikan.service, rawg.service, googleBooks.service) based on content.source to fetch real-time enriched data (full cast, external reviews, trailers, etc.). Internal PickWise reviews are merged with external reviews before the response, internal reviews appearing first.
Every time a Review is posted to POST /api/reviews/:contentId, the server runs a MongoDB aggregation pipeline ($group → $avg) over all reviews for that content to recompute and persist averageRating and ratingCount on the ContentItem document atomically.
Client → POST /api/auth/login
→ bcrypt.compare(password, hash)
→ jwt.sign({ id, role }, JWT_SECRET, { expiresIn: '7d' })
→ token returned to client (localStorage)
Protected Route → Authorization: Bearer <token>
→ auth.middleware: jwt.verify() → User.findById()
→ req.user attached → next()
Admin Route → auth.middleware → admin.middleware (role === 'admin' check)
| Field | Type | Description |
|---|---|---|
title |
String | Content title (indexed) |
slug |
String | URL-safe identifier |
category |
String | movie | tv | anime | manga | novel | comic | videogame | documentary |
source |
String | Source API: tmdb | mal | rawg | google_books |
externalId |
String | ID from source API (unique + source pair) |
universe |
String | Franchise label for grouping |
crossIp |
Array | Cross-media relationships (relationType, targetId ref, label) |
genres |
[String] | Genre tags |
casts |
Array | Cast list (name, role, photoUrl) |
ratings |
Array | External ratings (source, value, raw) |
averageRating |
Number | Computed PickWise average (1–10) |
ratingCount |
Number | Total PickWise review count |
platforms |
[String] | Streaming / gaming platforms |
popularityScore |
Number | Weighted popularity for ranking |
posterUrl / backdropUrl |
String | Media URLs |
| Field | Type | Description |
|---|---|---|
email |
String | Unique, required |
username |
String | Unique display name |
password |
String | bcrypt hash (select: false) |
role |
String | user | admin |
watchlist / favorites / reviewed |
[ObjectId] | Refs to ContentItem |
reviewCount |
Number | Incremented on each review |
| Field | Type | Description |
|---|---|---|
user |
ObjectId | Ref to User |
content |
ObjectId | Ref to ContentItem |
rating |
Number | 1–10 scale |
text |
String | Review body |
| Compound Index | { user, content } unique |
One review per user per content |
| Field | Type | Description |
|---|---|---|
user |
ObjectId | Ref to User |
content |
ObjectId | Ref to ContentItem |
status |
String | Plan to Watch | Watching | Completed | Dropped | On Hold | Playing | Beaten | Reading |
progress |
Number | Generic progress counter |
rating |
Number | Personal rating override |
| Field | Type | Description |
|---|---|---|
title / slug |
String | Title & URL key |
category |
String | movie | tv | anime | videogame | novel |
releaseWindow |
String | e.g. "September 2026" |
timeline |
Array | Ordered events (announcement, trailer, delay, etc.) |
crossIp |
Array | Links to related existing content |
platforms |
[String] | Target platforms |
| Mechanism | Implementation |
|---|---|
| Password Hashing | bcryptjs with salt rounds = 10, applied via Mongoose pre('save') hook |
| JWT Authentication | 7-day signed tokens; password field has select: false to prevent leakage |
| Role-Based Access | Admin routes use double middleware: auth (identity) → admin (role check) |
| Rate Limiting | Global limiter: 150 requests / 60 seconds per IP via express-rate-limit |
| CORS Policy | Allowlist-based: only localhost:5173, localhost:5174, and FRONTEND_URL env var |
| Duplicate Review Guard | Unique compound index { user, content } on Review collection prevents re-submission |
- Node.js ≥ 18.x
- npm ≥ 9.x
- MongoDB Atlas account or a local MongoDB instance
git clone https://github.com/Artyologist/PickWise.git
cd PickWisecd server
npm installCreate a .env file in the server/ directory:
# MongoDB
MONGO_URI=mongodb+srv://<user>:<password>@cluster0.xxxxx.mongodb.net/pickwise
# Server
PORT=4000
# Auth
JWT_SECRET=your_super_secret_key_here
# Frontend (for CORS)
FRONTEND_URL=http://localhost:5173
# External APIs
TMDB_KEY=your_tmdb_api_key
RAWG_KEY=your_rawg_api_key
GOOGLE_BOOKS_KEY=your_google_books_api_keyStart the backend server:
npm run dev # development (nodemon)
# or
npm start # productionThe API will be available at: http://localhost:4000
cd ../client
npm install
npm run devThe app will be available at: http://localhost:5173
cd server
node create-admin.jscd server
npm run seed| Metric | Value |
|---|---|
| API Route Groups | 11 (auth, content, search, global-search, reviews, comments, watchlist, users, universe, upcoming, admin) |
| Frontend Pages | 13 (Home, SearchResults, ContentDetail, Universe, Upcoming, UpcomingDetail, Profile, Settings, Login, About, AdminDashboard) |
| MongoDB Collections | 7 (ContentItem, User, Review, Watchlist, Comment, UpcomingProject, LoginEvent) |
| External API Integrations | 4 (TMDB, Jikan, RAWG, Google Books) |
| Content Categories | 8 (movie, tv, anime, manga, novel, comic, videogame, documentary) |
| Rate Limit | 150 requests / minute / IP |
| Frontend Deployment | Vercel (SPA with rewrite rules) |
| Backend Deployment | Railway (Nixpacks builder) |
This project is licensed under the MIT License. See the LICENSE file for details.
Built with ❤️ using the MERN stack