Back to Projects

FlixNest

Next.jsTypeScriptReactPrismaMongoDBNextAuth.jsSWRTailwind CSSZustandAxiosLodashbcrypt
Sign In (OAuth)1 / 6

Overview

A Netflix-inspired streaming platform with a cinematic UI featuring auto-playing billboard hero, hover-expanding movie cards, a favorites system ('My List'), full-screen video player, and multi-provider authentication. Built with Next.js 14 App Router, Prisma ORM with MongoDB, NextAuth.js (JWT sessions with credentials, Google, and GitHub OAuth), and SWR for client-side data fetching.

Key Features

Browse & Watch
  • Billboard hero section featuring a random movie with auto-playing muted video, title, description, Play button, and More Info button
  • Hover-expanding movie cards with CSS scale and translate animations — reveals play, favorite, and info buttons with genre and duration
  • Full-screen video player page with auto-play and native HTML5 video controls, back navigation overlay
  • Movie info modal with auto-playing video preview, title, description, genre, duration, play and favorite buttons — managed via Zustand store
  • Trending Now and My List movie rows — conditional rendering of favorites section only when user has favorited movies
Favorites & Profile
  • Favorites system ('My List') using MongoDB ObjectId array — toggle add/remove with optimistic SWR cache mutations
  • Profile selection page ('Who is watching?') with user avatar and name pulled from session data
  • Mobile-responsive browse dropdown menu and account menu with sign-out functionality
  • SWR data fetching hooks with aggressive caching — revalidateIfStale, revalidateOnFocus, and revalidateOnReconnect all disabled for performance
Auth & Security
  • Authentication with NextAuth.js supporting credentials (email/password with bcrypt hashing), Google OAuth, and GitHub OAuth — all with JWT sessions
  • Registration flow with duplicate email detection, bcrypt password hashing (salt rounds: 10), and auto-login after sign-up
  • Server-side session check on protected pages — unauthenticated users are redirected to /auth
  • REST API routes with server-side authentication via getServerSession on every protected endpoint
UI & Infrastructure
  • Scroll-reactive navbar — transparent on top, transitions to dark background when scrolled past 66px offset

Architecture Overview

How the system is structured from frontend to external services.

01

Frontend

Next.js 14 (App Router)React 18Tailwind CSSSWRZustand

Server Components for protected page shells with session checks and redirects. Client Components for the interactive browse experience — billboard with auto-play video, hover-expanding movie cards with CSS transforms, and the info modal. SWR handles all client-side data fetching with aggressive caching. Zustand manages the info modal state (open/close with movieId).

02

Backend & API

Next.js API RoutesNextAuth.js (JWT)bcrypt

RESTful API routes handling movie listing, random movie selection, and favorites management. Every protected endpoint calls serverAuth() which uses getServerSession to verify the JWT token and fetch the current user from the database. Registration endpoint hashes passwords with bcrypt (10 salt rounds) and checks for duplicate emails.

03

Database

MongoDBPrisma ORM

Five models: User, Account, Session, VerificationToken, and Movie. Uses MongoDB's ObjectId for primary keys and the User.favoriteIds string array pattern to store favorite movie IDs without a separate join table — leveraging MongoDB's document model for efficient array push/pull operations via Prisma.

04

External Services

Google OAuthGitHub OAuthNextAuth.js Prisma Adapter

NextAuth.js integrates with Google and GitHub OAuth providers alongside credentials authentication. The Prisma adapter auto-manages Account and Session models for OAuth. JWT strategy allows stateless session verification on API routes without database lookups per request.

Data Model

Core entities and how they relate to each other.

User

Has many Sessions, Accounts (cascade delete). favoriteIds references Movie.id values

User account — supports credentials (email/bcrypt password) and OAuth sign-in (Google, GitHub). Stores favorite movie IDs as a MongoDB ObjectId array directly on the document

id (ObjectId, PK)nameemail (unique)emailVerified (DateTime)hashedPassword (optional for OAuth)imagefavoriteIds (String[] ObjectId array)createdAtupdatedAt

Account

Belongs to User (cascade delete)

OAuth provider links — stores Google/GitHub provider tokens and metadata. Created automatically by NextAuth.js Prisma Adapter on first OAuth sign-in

id (ObjectId, PK)userIdtypeproviderproviderAccountIdaccess_tokenrefresh_token@@unique([provider, providerAccountId])

Session

Belongs to User (cascade delete)

Stores active user sessions with unique tokens and expiry — managed by NextAuth.js Prisma Adapter

id (ObjectId, PK)sessionToken (unique)userIdexpires

VerificationToken

Standalone — managed by NextAuth.js internally

Email verification tokens with identifier and expiry — used by NextAuth.js for email verification flows

id (ObjectId, PK)identifiertoken (unique)expires@@unique([identifier, token])

Movie

Referenced by User.favoriteIds array (no foreign key — MongoDB document pattern)

Movie content — stores all metadata and media URLs for the streaming catalog. Referenced by User.favoriteIds for the My List feature

id (ObjectId, PK)titledescriptionvideoUrlthumbnailUrlgenreduration

Key Flows

Step-by-step walkthrough of the main user and system flows.

Authentication Flow (Credentials + OAuth)

  1. 1User visits /auth — sees sign-in form with email/password inputs and Google/GitHub OAuth buttons
  2. 2Toggle between 'Sign in' and 'Register' variants on the same page
  3. 3Register: POST /api/register checks for duplicate email, hashes password with bcrypt (10 rounds), creates User, auto-calls login
  4. 4Login (credentials): NextAuth signIn('credentials') validates email, fetches user from MongoDB, compares bcrypt hash
  5. 5Login (OAuth): NextAuth signIn('google'/'github') — Prisma Adapter auto-creates Account + User on first sign-in
  6. 6On success: JWT session created, user redirected to /profiles (profile selection page)
  7. 7User clicks their profile → redirected to / (main browse page)

Browse & Watch Flow

  1. 1Main page checks getServerSession — redirects to /auth if not authenticated
  2. 2Billboard fetches a random movie via GET /api/random (Prisma count + skip random index) and auto-plays its video muted in the hero
  3. 3Movie component fetches all movies (GET /api/movies) and user favorites (GET /api/favorites) via SWR hooks
  4. 4Movies rendered in 'Trending Now' row. If user has favorites, 'My List' row appears below
  5. 5Hovering a movie card triggers CSS scale/translate animation revealing play, favorite, and info buttons
  6. 6Clicking Play navigates to /watch/[movieId] — full-screen HTML5 video player with back arrow overlay
  7. 7Clicking More Info opens the Zustand-managed info modal with video preview and movie details

Favorites (My List) Toggle

  1. 1User clicks the + button on a movie card or in the info modal
  2. 2FavoriteButton checks if movieId is in currentUser.favoriteIds to determine current state
  3. 3If not favorited: POST /api/favorite pushes movieId to the User.favoriteIds array in MongoDB
  4. 4If already favorited: DELETE /api/favorite uses lodash.without to remove movieId from the array
  5. 5API returns updated User with new favoriteIds — SWR cache mutated optimistically for instant UI update
  6. 6Both useCurrentUser and useFavorites SWR caches are mutated to keep the UI in sync
  7. 7Icon toggles between + (add) and checkmark (remove) to reflect current state

Challenges I Solved

01

MongoDB Favorites Without Join Table

The Problem

In a relational database, a favorites feature would require a separate UserFavorites join table. MongoDB doesn't have native join tables, and creating a separate collection for a simple many-to-many relationship adds unnecessary complexity.

How I Solved It

Leveraged MongoDB's document model by storing favoriteIds as a String[] array directly on the User document. Adding a favorite uses Prisma's push operation, and removing uses lodash.without to filter the array. Fetching favorites uses Prisma's findMany with where: { id: { in: favoriteIds } } — no joins needed.

02

Optimistic UI Updates for Favorites

The Problem

Toggling a favorite requires an API call, but waiting for the response before updating the UI creates a noticeable delay. The favorite icon flickers and the My List section lags behind the user's action.

How I Solved It

After the API call returns, both SWR caches (useCurrentUser and useFavorites) are mutated directly with the response data. The currentUser cache gets the updated favoriteIds array, and the favorites list is re-fetched. This keeps the + / checkmark icon and the My List section in sync without a full page reload.

03

Random Billboard Selection

The Problem

MongoDB doesn't have a native 'random document' query. Using findMany and shuffling in JavaScript loads all movies into memory, which doesn't scale.

How I Solved It

Used a two-step approach: first get the total count via prismaDB.movie.count(), then generate a random index with Math.floor(Math.random() * count) and use Prisma's skip + take(1) to fetch a single random movie efficiently. This only loads one document regardless of collection size.

04

Scroll-Reactive Navbar with CSS Transition

The Problem

The Netflix-style navbar needs to be transparent when at the top of the page and transition to a dark background when the user scrolls down — without any flickering or jarring visual change.

How I Solved It

Used a scroll event listener with a TOP_OFFSET threshold of 66px. When window.scrollY exceeds the threshold, a showBackground state toggles the bg-zinc-900 bg-opacity-90 classes with Tailwind's transition duration-500 for a smooth 500ms fade. The event listener is cleaned up on unmount to prevent memory leaks.

Summary

FlixNest demonstrates a cinematic streaming UI with Netflix-style interactions: auto-playing billboard hero, hover-expanding movie cards with CSS transforms, optimistic favorites toggling with SWR cache mutations, and a full-screen video player. The project showcases multi-provider authentication via NextAuth.js (credentials + Google + GitHub), MongoDB's document model for efficient favorites storage without join tables, and client-side data fetching patterns with SWR and Zustand.