University Library Management System — Next.js, TypeScript, PostgreSQL, Drizzle ORM, Auth.js, TanStack Query, Upstash Redis, ImageKit, QStash, Brevo, Resend Full-Stack Project (RBAC + Workflows + Admin + Analytics + Reviews + Reservations + Recommendations + Fines + Borrow Lifecycle & more)
A production-oriented full-stack university library platform (BookWise) built with the Next.js App Router, React 19, strict TypeScript, PostgreSQL + Drizzle ORM, Auth.js (NextAuth v5), TanStack Query, Upstash Redis (rate limits only), ImageKit, and multi-provider email. It is designed as both a deployable product and an educational lab: server-first pages, client hydration, typed mutations, domain invalidation, secure admin operations, and a real borrow + reservation lifecycle.
- Live demo: https://university-library-managment.vercel.app/
- Security: private reports → SECURITY.md · contact@arnobmahmud.com
- Author: Arnob Mahmud · GitHub @arnobt78
- What You Will Learn
- Features Overview
- Technology Stack
- Keywords Glossary
- Architecture Walkthrough
- Project Structure
- Database Schema
- Routes (Pages)
- API Endpoints
- Backend Layers (Services, Actions, Circulation)
- Reusable Components
- Hooks, Cache & Invalidation
- Authentication & Security Model
- Getting Started
- Environment Variables
- How to Obtain Each Secret
- Demo Test Accounts
- Scripts & Tooling
- Educational Code Snippets
- Important Libraries (Beginner Notes)
- Reusing Pieces in Other Projects
- Security
- Contributing & Support
- Conclusion
- License
This repository is both a working library product and a full-stack learning lab. As you explore the code, you will practice:
- App Router server components (
page.tsx) for fast first paint vs"use client"for interactivity - Drizzle ORM schema design, SQL migrations, and typed PostgreSQL access
- Auth.js v5 credentials login, JWT sessions, and DB-backed role/status authority
- scrypt password hashing with safe legacy SHA-256 verify + non-fatal rehash-on-login
- TanStack Query with SSR
initialData,initialDataUpdatedAt, typed query keys, and mutation-domain invalidation - Zod + React Hook Form for validated auth and book forms
- shadcn/ui + Tailwind for public (dark) and admin (light) UI surfaces
- External services: ImageKit, Upstash Redis (rate limit only), optional QStash workflows, Brevo / Resend
- Library domain logic: featured hero, borrow lifecycle, FIFO reservations / waitlist / renewals, reviews, fines, analytics, user 360
- Operational patterns: cron-secured outbox recovery, health/status routes, Vitest unit + disposable-PostgreSQL integration tests
| Feature | What it does |
|---|---|
| Auth | Sign up / sign in; new accounts often start PENDING until admin approval |
| Demo dropdown | Sign-in Select shows Test User / Test Admin with avatar + email (seeded) |
| Catalog | Browse, search/filter, open book detail |
| Featured hero | Homepage curated isFeatured book (else newest active) |
| Borrow | Request → admin approve → due dates / return / renew |
| Reservations | Hold / waitlist / READY notifications (outbox + cron recovery) |
| Borrow History | /my-profile — active, pending, returned loans + reservations panel |
| Reviews | Create / edit / delete own reviews (eligibility rules) |
| API docs & status | In-app API documentation and health/metrics pages |
| Feature | What it does |
|---|---|
| Books | Create / edit / soft-deactivate / hard-delete (secret) / feature on homepage |
| Users | Approve / reject, roles; user 360 detail at /admin/users/[id] |
| Borrow requests | Approve / reject / return lifecycle (transactional) |
| Account requests | Review “become admin” requests |
| Automation | Fine config, overdue fines, due/overdue reminders |
| Analytics | Business insights, charts, exports |
| Recommendations | Generate / refresh trending & personalized data |
- Instant UI updates via typed mutation → domain invalidation (+
router.refresh()where needed) - Same-origin tab coherence via data-free
BroadcastChannel(not cross-device push) - Rate limiting with Upstash Redis (Redis is not a business-data cache)
- Atomic inventory/lifecycle writes with row locks where required
- Vitest unit + disposable-PostgreSQL integration tests
- Vercel-friendly Node runtime (recommend Node 24.x on Vercel; app requires
>=20.9.0)
| Layer | Choice | Role |
|---|---|---|
| Framework | Next.js 16.2.12 (App Router) | Pages, layouts, Route Handlers, server actions |
| UI | React 19.2.8 | Components and hooks |
| Language | TypeScript ~5.9 | Static typing across app / API / schema |
| Styling | Tailwind CSS 3 + shadcn/ui (Radix) | Utility CSS + accessible primitives |
| Auth | next-auth 5.0.0-beta.32 | Credentials provider, JWT session |
| Passwords | Node scrypt (lib/auth/password.ts) |
Memory-hard hashes + legacy upgrade |
| ORM | Drizzle ORM + pg | PostgreSQL access |
| Client cache | TanStack Query 5 | Lists, details, mutations, invalidation |
| Forms | react-hook-form + Zod 4 | Validated forms / inputs |
| Media | @imagekit/next | Covers, ID cards, trailers |
| Rate limit | @upstash/redis + ratelimit | Abuse protection |
| Jobs | @upstash/workflow / QStash | Optional onboarding workflows |
| Brevo (primary) + Resend (fallback / outbox) | Receipts, reminders, READY mail | |
| Charts | Recharts | Admin analytics |
| Icons | lucide-react | UI icons |
| Tests | Vitest | Unit / integration runner |
Runtime: Node.js >= 20.9.0 (package.json engines). On Vercel, prefer 24.x project + production Node settings.
| Keyword | Meaning here |
|---|---|
| App Router | File-based routing under app/ (page.tsx, layout.tsx, route.ts) |
| RSC | React Server Component — can query the DB on the server |
| Client component | "use client" — hooks, browser APIs, interactive UI |
| Server action | "use server" callable from the client (e.g. book CRUD, circulation) |
| SSR hydration | Pass initialData (and often initialDataUpdatedAt) from page.tsx into React Query |
| Invalidation | Mark query keys stale so active observers refetch after CRUD |
| Mutation registry | Typed family → query domains + RSC paths (queryInvalidation / revalidateMutation) |
| Featured book | books.is_featured — at most one curated homepage hero |
| Soft delete | isActive = false — hide without removing rows |
| Hard delete | Physically delete book + related rows (needs ADMIN_DELETE_SECRET) |
| RBAC | USER vs ADMIN (DB role is authoritative over JWT claims alone) |
| BorrowRecordFull | Borrow row + nested book from an INNER JOIN (profile / API cache type) |
| Reservation outbox | Retry-safe READY email delivery with lease / dead-letter / cron |
| Drizzle | TypeScript ORM mapping schema → SQL |
| Zod | Runtime schema validation |
Browser
│
├─ App Router pages (RSC) ──► PostgreSQL via Drizzle
│ │
│ └─ initialData ──► Client components + TanStack Query
│ ├─ fetch /api/* (Node runtime)
│ └─ server actions (admin / circulation)
│
├─ Auth.js session (JWT) + DB role/status checks on privileged writes
├─ ImageKit (uploads / CDN)
├─ Upstash Redis (rate limit only)
├─ Brevo / Resend (+ reservation outbox worker / cron)
└─ QStash workflows (optional)
Teaching rule used in this codebase:
- Load data that can run on the server in
page.tsx. - Put interactive UI in client components.
- After mutations, use the typed mutation registry so related domains and RSC paths stay fresh (active refetch; inactive keys stale until next visit / focus / back navigation).
- Never trust browser-supplied actor IDs for privileged writes — resolve the current user from the session + database.
- Keep inventory and lifecycle writes transactional so
availableCopiescannot drift from borrow state.
university-library/
├── app/
│ ├── (auth)/ # Sign-in / sign-up layouts
│ ├── (root)/ # Public app (home, books, Borrow History, performance)
│ ├── admin/ # Admin dashboard (+ users/[id] 360)
│ ├── api/ # Route Handlers (REST-style JSON)
│ │ ├── auth/ # NextAuth + ImageKit auth
│ │ ├── books/ # Catalog, featured, recommendations
│ │ ├── borrow-records/
│ │ ├── reviews/
│ │ ├── users/
│ │ ├── admin/ # Stats, fines, exports, analytics, …
│ │ ├── cron/ # Reservation notification recovery
│ │ ├── status/ # Health / metrics
│ │ └── workflows/ # Optional QStash onboarding
│ ├── api-docs/ · api-status/
│ ├── fonts/ # next/font/local (IBM Plex, Bebas)
│ ├── layout.tsx · globals.css
├── components/ # Feature UI + components/ui (shadcn)
├── constants/ # Nav, FIELD_*, TEST_ACCOUNTS
├── database/ # schema.ts, drizzle.ts, redis.ts, seed.ts (retired → seed:reset)
├── hooks/ # useQueries, useMutations, useSafeMedia, …
├── lib/
│ ├── auth/ # password (scrypt), authorization, route helpers
│ ├── admin/actions/ # Privileged server operations
│ ├── circulation/ # Reservations, outbox, renewals
│ ├── media/ # universityCard resolver, upload validation
│ ├── query/keys.ts # Query-key factory
│ ├── utils/queryInvalidation.ts · revalidateMutation.ts
│ ├── validations.ts # Zod schemas
│ └── services/ # Fetch helpers for hooks
├── migrations/ # SQL history (featured, audit, reservations, …)
├── scripts/ # reset-and-seed.ts (canonical demo reset)
├── docs/ # PROJECT_WALKTHROUGH and guides
├── .agile-v/ # Requirements / gates / decisions (agents)
├── auth.ts · proxy.ts
├── dummybooks.json # 17-book seed catalog
├── .env.example # Env template (copy → .env)
├── SECURITY.md
└── package.json
Defined in database/schema.ts:
| Table | Purpose |
|---|---|
users |
Accounts, password hash, status, role, university_card, audit fields |
books |
Catalog, copies, is_active, is_featured, cover/video URLs |
borrow_records |
PENDING → BORROWED → RETURNED, fines, due dates, renewals |
reservations |
Waitlist / hold / READY lifecycle (FIFO) |
reservation_events |
Outbox / event history for READY delivery |
circulation_commands |
Idempotent circulation command ledger |
operation_telemetry |
Bounded ops / SLO telemetry |
book_reviews |
Ratings + comments |
admin_requests |
Become-admin requests |
system_config |
Fine amounts and runtime knobs (preserved across seed:reset) |
Enums include account status, role, borrow status, and reservation status.
Apply SQL under migrations/ (including 0010_reservations.sql for circulation and 0009_users_audit_fields.sql for user audit columns) before expecting matching production behavior.
| Path | Description |
|---|---|
/ |
Featured hero + recommendations |
/all-books |
Full catalog with filters |
/books/[id] |
Detail, borrow, reserve, reviews |
/my-profile |
Borrow History — loans + reservations |
| Path | Description |
|---|---|
/sign-in |
Credentials login + test-account Select (avatar / name / email) |
/sign-up |
Registration + university ID card upload |
| Path | Description |
|---|---|
/admin |
Dashboard home |
/admin/books · /new · /[id]/edit |
Book CRUD + featured checkbox |
/admin/users · /admin/users/[id] |
Users list + 360 profile |
/admin/book-requests |
Borrow approvals |
/admin/account-requests |
Pending user registration approvals (Sign-up Requests) |
/admin/automation |
Fines & reminders |
/admin/business-insights |
Analytics |
| Path | Description |
|---|---|
/api-docs |
API documentation UI |
/api-status |
Service health + client performance metrics |
/too-fast |
Rate-limit friendly page |
/make-admin |
Request elevated access |
Route Handlers live under app/api/**/route.ts (Node.js runtime where DB access is required). Many writes use server actions instead of REST PUT/DELETE — that is intentional.
| Method | Path | Notes |
|---|---|---|
GET |
/api/books |
List + search / filter / pagination |
GET |
/api/books/[id] |
Single book |
GET |
/api/books/featured |
Featured first, then newest active |
GET |
/api/books/genres |
Distinct genres |
GET |
/api/books/recommendations |
Personalized / fallback |
GET |
/api/books/[id]/borrow-stats |
Borrow counters |
| Method | Path | Notes |
|---|---|---|
* |
/api/auth/[...nextauth] |
Auth.js handlers |
GET |
/api/auth/imagekit |
Upload auth params for ImageKit |
| Method | Path | Notes |
|---|---|---|
GET |
/api/borrow-records |
Authenticated borrow list (INNER JOIN → nested book) |
GET |
/api/users |
Authorized user listing (no passwords) |
GET / POST |
/api/reviews/[bookId] |
List / create reviews |
GET |
/api/reviews/eligibility/[bookId] |
Can current user review? |
PUT / DELETE |
/api/reviews/edit/[reviewId] |
Update / delete |
DELETE |
/api/reviews/delete/[reviewId] |
Alternate delete path |
Under /api/admin/: stats, analytics, fine-config, update-overdue-fines, send-due-reminders, send-overdue-reminders, export/*, export-stats, reminder-stats, generate-recommendations, update-trending-books, refresh-recommendation-cache, borrow-requests, admin-requests.
| Method | Path | Notes |
|---|---|---|
GET / POST |
/api/cron/reservation-notifications |
Secured by CRON_SECRET Bearer |
Under /api/status/: health, database, authentication, email-service, file-storage, external-apis, api-server, metrics.
| Method | Path | Notes |
|---|---|---|
POST… |
/api/workflows/onboarding |
Upstash workflow when ENABLE_WORKFLOWS=true |
Understanding these layers helps you navigate the codebase faster:
| Layer | Location | Beginner meaning |
|---|---|---|
| Services | lib/services/*.ts |
Pure fetch helpers used by React Query hooks (no React inside) |
| Hooks | hooks/useQueries.ts, useMutations.ts |
Wire services into TanStack Query |
| Server actions | lib/admin/actions/*, lib/actions/* |
Privileged DB writes with auth + transactions |
| Authorization | lib/auth/authorization.ts |
Resolve current actor from DB, not from the browser |
| Borrow lifecycle | lib/admin/borrowLifecycle.ts |
Atomic approve / return / reject with inventory updates |
| Circulation | lib/circulation/* |
Reservations, renewals, outbox delivery |
| Query keys | lib/query/keys.ts |
Single factory so invalidation stays consistent |
| Invalidation | lib/utils/queryInvalidation.ts |
Mutation family → which caches to refresh |
Example flow — student returns a book:
- UI calls a mutation / action.
- Server verifies session + ownership/admin policy.
- Transaction updates
borrow_recordsand incrementsavailableCopies. - Typed invalidation refreshes borrow, book, profile, and admin list queries.
- Same-origin tabs hear a BroadcastChannel ping and stay coherent.
| Component | Kind | Purpose | Reuse tip |
|---|---|---|---|
BookCard / BookCover / BookList |
Mixed | Catalog tiles & covers | Pass cover URL + color; keep sizes consistent |
HomeFeaturedHero / HomeRecommendations |
Client | Homepage + RQ hydration | Feed SSR initialData |
BookBorrowButton / ReturnBookButton / ReserveBookButton |
Client | Circulation actions | Pair with mutation invalidation |
ReservationsPanel |
Client | Queue / READY UI | Works with SSR reservation summary |
ReviewsSection / ReviewFormDialog |
Client | Reviews CRUD | Check eligibility first |
AuthForm / SignInFormPage / SignUpFormPage |
Client | Auth UX + demo Select | Swap Zod schemas for other products |
UserAvatar |
Client | Local /images, remote URL, or ImageKit |
Use with resolveUniversityCard |
MyProfileTabs |
Client | Borrow History tabs | Needs BorrowRecordFull + initialDataUpdatedAt |
FileUpload |
Client | ImageKit picker | Needs /api/auth/imagekit |
QueryProvider |
Client | App-wide React Query | Mount once in a layout |
AdminBooksList / DeleteBookDialog |
Client | Admin grid + secret-gated delete | Pattern for any destructive op |
PerformanceDashboard / AnalyticsCharts |
Client | Ops & charts | Swap data sources freely |
components/ui/* |
shadcn | Button, Form, Dialog, Select, Avatar, Toast, … | Copy via shadcn CLI |
Composition tip: Keep shells/static chrome in RSC pages; only the interactive data islands need "use client". That makes navigation feel instant even before network data arrives.
| Piece | Role |
|---|---|
hooks/useQueries.ts |
Typed readers with optional SSR initialData / initialDataUpdatedAt |
hooks/useMutations.ts |
Central mutations, rollback, toasts, registry invalidation |
lib/query/keys.ts |
Query-key factory / prefixes |
lib/utils/queryInvalidation.ts |
Mutation → domains + BroadcastChannel tab sync |
lib/utils/revalidateMutation.ts |
RSC path revalidation consumers |
Learner model:
- RSC paints with server data (layout, labels, tables shells appear immediately).
- Query hooks reuse that data as fresh for
staleTimewheninitialDataUpdatedAtis set. - A successful mutation picks a typed family (e.g.
book.write,borrow.lifecycle). - Active queries refetch; inactive ones go stale and refresh on navigation / back / focus.
- Book CRUD may also call
router.refresh()for the current RSC tree.
Profile / Borrow History note: Prefer React Query borrow rows only when nested book.title is real — otherwise fall back to SSR props. That avoids the “Unknown Book by Unknown Author” flash from a stale cache missing the JOIN.
Redis note: Upstash Redis is for rate limiting only. PostgreSQL is the source of truth for catalog and circulation data. There is no Redis business-data invalidation to maintain.
- Credentials sign-in via Auth.js; passwords hashed with versioned scrypt (
hashPassword/verifyPasswordinlib/auth/password.ts). - Legacy
salt:hashSHA-256 still verifies and upgrades on successful login (upgrade write is non-fatal so missing audit columns cannot lock users out). - Unknown emails still run a verify against a fixed dummy scrypt encoding (equal-cost / anti-enumeration). GitGuardian may flag that string — it is not a live secret.
- Privileged actions use
lib/auth/authorization.ts— current DB role/status, not stale JWT claims alone. - User writes enforce ownership; admin actor IDs are server-derived.
- Borrow / fine / hard-delete paths use transactions and row locks where required.
- Hard delete requires
ADMIN_DELETE_SECRET(never reuseAUTH_SECRET). - Apply migration
0009_users_audit_fields.sqlbefore code that selectsusers.updated_at/updated_by.
Private vulnerability reporting: SECURITY.md.
- Node.js 20.9+ (24.x recommended on Vercel)
- npm
- PostgreSQL (local Docker, Neon, Supabase, Railway, etc.)
- Recommended for full features: ImageKit, Upstash Redis, Brevo and/or Resend
Yes for a real run. This project talks to PostgreSQL and signs sessions with Auth.js, so at minimum you need DATABASE_URL and AUTH_SECRET (plus URL vars). There is no zero-config offline mode that skips those.
Optional services (uploads, rate limits, email, workflows, cron) can stay empty until you enable them — those features simply will not work until configured. Start from the template:
cp .env.example .envNever commit .env. Only .env.example with placeholders belongs in git.
git clone https://github.com/arnobt78/Library-Management-System--NextJS-FullStack.git
cd Library-Management-System--NextJS-FullStack
npm install
# Fill .env (see Environment Variables below)
cp .env.example .env
# Push schema / apply migrations as needed
# (also apply SQL under migrations/, e.g. 0009 + 0010, on each environment)
npm run db:migrate
# Wipe transactional data and seed 17 books + Test User / Test Admin
npm run seed:reset
npm run devOpen http://localhost:3000.
npm run build
npm startnpm run typecheck
npm run lint
npm test
# Optional real DB suite (disposable DB only — never production):
TEST_DATABASE_URL=postgresql://… npm run test:integration
npm audit --audit-level=lowCopy .env.example and fill values. Variables marked REQUIRED are needed for their feature group.
| Variable | Public? | Purpose |
|---|---|---|
DATABASE_URL |
No | PostgreSQL URI |
AUTH_SECRET |
No | Auth.js signing secret |
NEXTAUTH_URL |
No | App origin, e.g. http://localhost:3000 |
NEXT_PUBLIC_API_ENDPOINT |
Yes | Browser API base (often same origin) |
NEXT_PUBLIC_PROD_API_ENDPOINT |
Yes | Production origin |
| Variable | Public? | Purpose |
|---|---|---|
NEXT_PUBLIC_IMAGEKIT_PUBLIC_KEY |
Yes | Client upload / display |
NEXT_PUBLIC_IMAGEKIT_URL_ENDPOINT |
Yes | CDN endpoint |
IMAGEKIT_PRIVATE_KEY |
No | Server upload auth |
| Variable | Purpose |
|---|---|
UPSTASH_REDIS_REST_URL / UPSTASH_REDIS_REST_TOKEN |
Preferred REST credentials |
UPSTASH_REDIS_URL / UPSTASH_REDIS_TOKEN |
Compatibility aliases used by lib/config.ts |
| Variable | Purpose |
|---|---|
BREVO_API_KEY · BREVO_SENDER_EMAIL · BREVO_SENDER_NAME |
Primary transactional email |
RESEND_TOKEN · RESEND_SENDER_EMAIL |
Fallback / reservation READY delivery |
| Variable | Purpose |
|---|---|
QSTASH_URL · QSTASH_TOKEN |
QStash API |
ENABLE_WORKFLOWS |
"true" to enable locally |
| Variable | Purpose |
|---|---|
ADMIN_DELETE_SECRET |
Typed in delete dialog (distinct from AUTH_SECRET) |
CRON_SECRET |
Bearer for /api/cron/reservation-notifications |
| Variable | Purpose |
|---|---|
AUTH_TRUST_HOST |
"true" only behind a trusted proxy |
GOOGLE_CLIENT_ID / GOOGLE_CLIENT_SECRET |
If Google OAuth is enabled in auth.ts |
TEST_DATABASE_URL |
Disposable DB for integration tests only |
Platform-injected values (NODE_ENV, VERCEL, VERCEL_URL) should not be hand-set in .env.
-
AUTH_SECRET/ADMIN_DELETE_SECRET/CRON_SECRETopenssl rand -base64 32 # stronger cron example: openssl rand -base64 48 # or: npx auth secret
Use different values for each secret.
-
DATABASE_URL- Local: Docker / Homebrew Postgres →
postgresql://user:pass@localhost:5432/library - Cloud: Neon / Supabase / Railway → copy URI (
sslmode=requirewhen required)
- Local: Docker / Homebrew Postgres →
-
ImageKit — imagekit.io → Developer → API keys + URL endpoint
-
Upstash Redis — console.upstash.com/redis → REST URL + token
-
QStash — Upstash → QStash → URL + token (local QStash docs)
-
Brevo — app.brevo.com → API keys; verify sender
-
Resend — resend.com/api-keys + domain verification for production
-
Vercel — Project → Settings → Environment Variables → paste the same keys for Production / Preview; set Node.js to 24.x (project + overrides) and redeploy. Cron schedules must respect plan limits (this repo uses a daily reservation recovery cron for Hobby-friendly schedules).
Shared constants live in constants/index.ts as TEST_ACCOUNTS. Reset the database and seed books + both accounts with:
npm run seed:resetThat script:
- Deletes transactional tables in FK-safe order (
reservation_events→ … →users) - Preserves
system_config - Inserts 17 books from
dummybooks.jsonwithavailableCopies = totalCopies(Algorithms is featured) - Inserts Test User + Test Admin with scrypt-hashed passwords and local avatar paths
| Role | Password | Avatar (university_card) |
|
|---|---|---|---|
| Test User | test@user.com |
12345678 |
/images/profile-img1.png |
| Test Admin | test@admin.com |
12345678 |
/images/profile-img2.png |
The sign-in Select shows a circle avatar + name + email. After login, UserAvatar + resolveUniversityCard render local /images/*, remote http(s), or ImageKit paths correctly.
Demo passwords are for local/shared demo DBs only — never reuse them as real user credentials in production.
| Script | Command | Use |
|---|---|---|
| Dev | npm run dev |
Local development |
| Build / Start | npm run build · npm start |
Production |
| Typecheck / Lint / Test | npm run typecheck · lint · test |
Quality gates |
| Integration tests | npm run test:integration |
Disposable Postgres only |
| Reset + seed | npm run seed:reset |
17 books + Test User / Admin |
| Drizzle generate / migrate | npm run db:generate · db:migrate |
Schema tooling |
| Drizzle studio | npm run db:studio |
Browse data |
Older one-off scripts (
seed:test-profiles,delete-book, verify/fix helpers) were retired in favor ofseed:resetand in-app admin flows.
// app/(root)/page.tsx (Server Component)
const heroBook = await getHomepageHeroBook();
return (
<HomeFeaturedHero
initialHero={heroBook ? JSON.parse(JSON.stringify(heroBook)) : null}
userId={session?.user?.id}
/>
);// components/HomeFeaturedHero.tsx ("use client")
const { data } = useFeaturedBooks(1, initialHero ? [initialHero] : []);
const hero = data?.[0] ?? initialHero;// components/MyProfileTabs.tsx (concept)
const [ssrTimestamp] = React.useState(() => Date.now());
useUserBorrows(
userId,
undefined,
ssrInitialData, // BorrowRecordFull[] with nested book
ssrInitialData ? ssrTimestamp : undefined // initialDataUpdatedAt
);// Concept from hooks/useMutations.ts + lib/utils/queryInvalidation.ts
onSuccess: async () => {
await invalidateMutation(queryClient, "book.write");
};
// Book forms may also call router.refresh() for RSC syncimport { hashPassword, verifyPassword } from "@/lib/auth/password";
const encoded = await hashPassword("correct horse battery staple");
const ok = await verifyPassword("correct horse battery staple", encoded);import { resolveUniversityCard } from "@/lib/media/universityCard";
const resolved = resolveUniversityCard("/images/profile-img1.png");
// { kind: "local", src: "/images/profile-img1.png" }// lib/validations.ts
export const bookSchema = z.object({
title: z.string().trim().min(2).max(100),
isActive: z.boolean().optional(),
isFeatured: z.boolean().optional(),
// …
});| Library | What it is | How this project uses it |
|---|---|---|
| Next.js | React meta-framework | App Router pages, API routes, server actions |
| React | UI library | Components; Server vs Client split |
| TypeScript | Typed JavaScript | Catch contract bugs before runtime |
| Drizzle | Type-safe ORM | database/schema.ts → PostgreSQL |
| Auth.js | Auth toolkit | Credentials login + JWT session |
| TanStack Query | Server-state cache | Lists/details + invalidation after mutations |
| Zod | Schema validation | Forms and trusted input boundaries |
| React Hook Form | Form state | Auth + Book forms with Zod resolver |
| ImageKit | Media CDN | Covers, ID cards, video |
| Upstash Redis | Serverless Redis | Rate limits only |
| Resend / Brevo | Transactional email | Reminders + reservation READY mail |
| Vitest | Test runner | Unit + integration tests |
| Tailwind / shadcn | Styling + primitives | Consistent, accessible UI |
| Piece | How to reuse |
|---|---|
components/ui/* |
Copy via shadcn CLI; keep cn() in lib/utils.ts |
QueryProvider + invalidation registry |
Drop into any App Router app; standardize mutation onSuccess |
UserAvatar + resolveUniversityCard |
Any profile image that may be local, URL, or ImageKit |
FileUpload + /api/auth/imagekit |
Needs ImageKit env vars |
AuthForm + lib/auth/password.ts |
Adapt Zod schemas; keep scrypt verify/rehash pattern |
Drizzle schema.ts |
Start a new DB module; snake_case DB / camelCase TS |
DeleteBookDialog pattern |
Any destructive action gated by an env secret |
| Circulation outbox pattern | Retry-safe side effects with lease + cron recovery |
seed:reset pattern |
One atomic wipe + reseed script beats many one-off maintenance scripts |
When porting, keep SSR initialData + typed invalidation so navigation stays fast and data stays coherent across lists, detail pages, and back-button returns.
- See SECURITY.md for private vulnerability reporting (
contact@arnobmahmud.com). - Never expose
DATABASE_URL,AUTH_SECRET,ADMIN_DELETE_SECRET,IMAGEKIT_PRIVATE_KEY, Redis/email/cron tokens to the browser. - Prefer soft-deactivate (
isActive) for normal catalog removal; hard-delete only for junk/test cleanup. - Rotate secrets if they leak into git history, logs, or chat.
- Do not store real student PII on a public demo without institutional review.
- Repo: https://github.com/arnobt78/Library-Management-System--NextJS-FullStack
- Portfolio: https://www.arnobmahmud.com
- Security contact: contact@arnobmahmud.com · policy: SECURITY.md
Issues and PRs that improve docs, tests, accessibility, or performance are welcome.
BookWise is a complete, learning-friendly university library platform: catalog, borrow + reservation lifecycle, reviews, admin operations, featured homepage hero, monitoring pages, scrypt auth, and a modern Next.js data architecture (RSC + TanStack Query + typed invalidation). Use it as a deployable demo, a study reference, or a toolkit of reusable forms, uploads, avatars, and cache-coherence patterns for your next project.
This project is licensed under the MIT License. Feel free to use, modify, and distribute the code as per the terms of the license.
This is an open-source project - feel free to use, enhance, and extend this project further!
If you have any questions or want to share your work, reach out via GitHub or my portfolio at https://www.arnobmahmud.com.




















