Students should first follow the experiment guide to proceed with the project
This project demonstrates a production-grade, full-stack application built with Next.js 16 (App Router), complete with secure HTTP-only cookie authentication, RESTful APIs, business logic services, and a comprehensive database schema.
This guide will walk you through implementing the entire architecture from scratch, explaining the why and how behind the changes.
- Framework: Next.js (App Router, v16)
- Styling: Tailwind CSS v4 + shadcn/ui
- Database: MongoDB (via
mongoose) - State Management: Zustand
- Authentication: JWT (
jose), HTTP-only Cookies (cookie), Password Hashing (argon2) - API Flow & Validation:
axios,http-status-codes,zod,react-hook-form - Architecture: Separated Business Logic (
services/), Utility Helpers (lib/api-utils.ts)
We use pnpm for faster, strictly-linked dependency management.
pnpm create next-app@latest 10b-meConfiguration Choices:
- TypeScript: Yes
- ESLint: Yes
- Tailwind CSS: Yes
- App Router: Yes
Add all required libraries:
# Database & Auth
pnpm add mongoose argon2 jose cookie jsonwebtoken
pnpm add -D @types/cookie
# API Request & Standards
pnpm add axios http-status-codes
# State Management
pnpm add zustand
# Forms & Validation
pnpm add react-hook-form zod @hookform/resolvers
# UI Utilities
pnpm add lucide-react sonner clsx tailwind-merge next-themes @base-ui/react tw-animate-cssInitialize your UI system and add the required raw components.
pnpm dlx shadcn@latest init
# Accept default prompts
pnpm dlx shadcn@latest add badge button card field input label separator sonner spinnerCreate .env.local to securely store your keys:
MONGO_URI=mongodb+srv://<db_user>:<db_password>@cluster0.abcde.mongodb.net/10b_fullstack
JWT_SECRET=your_long_super_secure_random_string_hereThis project implements relational data conceptually linking Users, Posts, and Comments.
Your Task:
Create three Mongoose models (models/User.ts, models/Post.ts, models/Comment.ts).
- User: Requires
name(String),email(String, unique),password(String, hashed). - Post: Requires
userId(String withindex: true),title(String),description(String), plus timestamps. - Comment: Requires
postId(String withindex: true),userId(String), andcontent(String), plus timestamps.
Important: Always ensure you export via mongoose.models.<Name> ?? mongoose.model('<Name>', Schema) to prevent hot-reload crashes.
Instead of writing JWT sign/verify logic in every route, centralize it to keep your APIs DRY (Don't Repeat Yourself).
lib/db.ts: Write a standard cached Mongoose connection (connectDB()).lib/jwt.ts: Usejoseto writesignToken(payload)andverifyToken(token).lib/api-utils.ts:createAuthCookie(response, user): Given aNextResponse, generate a JWT and attach it to theSet-Cookieheader via thecookiepackage (httpOnly: true,secure: truein production).getSession(): Extracts and decrypts the JWT token either from theAuthorizationheader or the Next.jsheaders().get("cookie"). Returnsnullif unauthorized.
Do not clutter your API routes with complex database queries. Separate the "business logic" from the HTTP logic.
Your Task:
Create services/posts.ts and services/comments.ts.
posts.tsshould export reusable functions likepaginatePosts,getAllPostsExcluding(),createPost(),deletePost(). Note thatgetAllPostsExcludingqueries Posts, then maps theuserIdarrays to manually fetchUserdocuments and attach"authorName".comments.tsshould containgetCommentsByPost(postId),createComment(), etc.
Notice how clean the routes are when logic is abstracted! Use http-status-codes (e.g. StatusCodes.OK) to remove magic numbers.
- Parse the body.
- Handle password hashing via
argon2. - Call
createAuthCookie(NextResponse.json(...))before returning the response.
- First line:
const session = await getSession(); if (!session) return unauthorized(); - Then parse query parameters (
req.nextUrl.searchParams.get("page")) or slugs. - Call your
services/posts.tsmethods and return JSON.
Create a Zustand store (useAuthStore) that holds user: { id, email, name } and token.
- Keep the
persistmiddleware. - You still need
_hasHydratedboolean logic to prevent SSR mismatches, but remember that the server relies on the HttpOnly Cookie, while the client uses Zustand to drive instant UI reactions (like showing the user's name inNavUser.tsx).
Use axios for data fetching on the client side since it elegantly handles catching errors.
Components:
- AppHeader & NavUser: Drives navigation, clears the
useAuthStore, and handles logout visually. - Login / Register Forms: React Hook Form connected to Zod schemas hitting
/api/auth/*. - PostsList & PostsPagination: Consumes the
/api/postsAPI with page and limit queries. Use state variablespageand trigger anaxios.getwheneverpagechanges. - CommentsSection: Renders recursively/linearly based on what
/api/posts/[id]/commentsyields.
Structure your application routing:
app/layout.tsx: Wrap the app in<Toaster />for globalsonnernotifications.app/page.tsx: A dashboard that only renders<PostsList />if the user is hydrated and logged in.app/my-posts/page.tsx: Shows only the user's personal posts (/api/posts/mine).app/posts/[id]/page.tsx: Single post view loading the Post header +<CommentsSection />.
Run your application:
pnpm run dev- Try registering a user.
- Monitor your browser's DevTools checking the Application > Cookies tab to confirm
httpOnlyflags set properly. - Try creating posts, paginating, and dropping comments!