University Library Management System - Next.js, PostgreSQL, Redis, Upstash, Resend, ImageKit FullStack Project
A modern, full-stack university library management solution built with Next.js 15, TypeScript, and Drizzle ORM. BookWise provides comprehensive book borrowing, user management, and administrative features for educational institutions.
Built with Next.js, TypeScript, Postgres, the University Library Management System is a production-grade platform featuring a public-facing app and admin interface. It offers advanced functionalities like seamless book borrowing with reminders and receipts, robust user management, automated workflows, optimized tech stack for real-world scalability.
- User Authentication & Authorization - Secure login with role-based access (USER/ADMIN)
- Book Management - Complete CRUD operations for library books
- Borrowing System - Request, approve, and track book borrows
- User Profiles - Personal dashboards with borrowing history
- Review System - Rate and review books with comments
- Admin Dashboard - Comprehensive administrative controls
- Real-time API Monitoring - Live system health and metrics dashboard
- API Documentation - Interactive Swagger-style API docs
- Admin Request System - Users can request admin privileges
- Fine Management - Automated overdue fine calculations
- Email Notifications - Automated reminders and notifications
- Performance Analytics - System performance monitoring
- Export Functionality - Data export for analytics and reporting
- Responsive Design - Mobile-first approach with Tailwind CSS
- Real-time Updates - Live data updates with React Query
- Image Management - Cloud-based image storage with ImageKit
- Rate Limiting - Protection against abuse with Upstash Redis
- Database Migrations - Version-controlled schema changes
- Type Safety - Full TypeScript implementation
- Next.js 15 - React framework with App Router
- TypeScript - Type-safe JavaScript
- Tailwind CSS - Utility-first CSS framework
- Shadcn/ui - Modern component library
- Lucide React - Beautiful icons
- React Hook Form - Form management
- Zod - Schema validation
- TanStack Query - Server state management
- Next.js API Routes - Serverless API endpoints
- NextAuth.js - Authentication framework
- Drizzle ORM - Type-safe database ORM
- PostgreSQL - Primary database (Neon)
- Redis - Caching and rate limiting (Upstash)
- ImageKit - Image storage and optimization
- Resend - Email delivery service
- Upstash - Redis and QStash for background jobs
- Vercel - Deployment platform
- ESLint - Code linting
- Prettier - Code formatting
- Drizzle Kit - Database migrations
- Turbopack - Fast development builds
university-library/
├── app/ # Next.js App Router
│ ├── (auth)/ # Authentication pages
│ │ ├── sign-in/
│ │ └── sign-up/
│ ├── (root)/ # Main application pages
│ │ ├── all-books/
│ │ ├── books/[id]/
│ │ ├── my-profile/
│ │ └── performance/
│ ├── admin/ # Admin dashboard
│ │ ├── books/
│ │ ├── users/
│ │ ├── book-requests/
│ │ └── business-insights/
│ ├── api/ # API endpoints
│ │ ├── auth/
│ │ ├── admin/
│ │ ├── reviews/
│ │ └── status/
│ ├── api-docs/ # API documentation
│ └── api-status/ # System monitoring
├── components/ # Reusable components
│ ├── ui/ # Shadcn/ui components
│ ├── admin/ # Admin-specific components
│ └── [feature-components] # Feature-specific components
├── database/ # Database configuration
│ ├── schema.ts # Drizzle schema
│ ├── drizzle.ts # Database connection
│ └── seed.ts # Database seeding
├── lib/ # Utility libraries
│ ├── actions/ # Server actions
│ ├── admin/ # Admin utilities
│ ├── services/ # External services
│ └── stores/ # State management
├── migrations/ # Database migrations
├── public/ # Static assets
└── styles/ # Global styles
export const users = pgTable("users", {
id: uuid("id").primaryKey().defaultRandom(),
fullName: varchar("full_name", { length: 255 }).notNull(),
email: text("email").notNull().unique(),
universityId: integer("university_id").notNull().unique(),
password: text("password").notNull(),
universityCard: text("university_card").notNull(),
status: STATUS_ENUM("status").default("PENDING"),
role: ROLE_ENUM("role").default("USER"),
lastActivityDate: date("last_activity_date").defaultNow(),
lastLogin: timestamp("last_login", { withTimezone: true }),
createdAt: timestamp("created_at", { withTimezone: true }).defaultNow(),
});
export const books = pgTable("books", {
id: uuid("id").primaryKey().defaultRandom(),
title: varchar("title", { length: 255 }).notNull(),
author: varchar("author", { length: 255 }).notNull(),
genre: text("genre").notNull(),
rating: integer("rating").notNull(),
coverUrl: text("cover_url").notNull(),
coverColor: varchar("cover_color", { length: 7 }).notNull(),
description: text("description").notNull(),
totalCopies: integer("total_copies").notNull().default(1),
availableCopies: integer("available_copies").notNull().default(0),
videoUrl: text("video_url").notNull(),
summary: varchar("summary").notNull(),
// Enhanced fields
isbn: varchar("isbn", { length: 20 }),
publicationYear: integer("publication_year"),
publisher: varchar("publisher", { length: 255 }),
language: varchar("language", { length: 50 }).default("English"),
pageCount: integer("page_count"),
edition: varchar("edition", { length: 50 }),
isActive: boolean("is_active").default(true).notNull(),
updatedAt: timestamp("updated_at", { withTimezone: true }).defaultNow(),
updatedBy: uuid("updated_by").references(() => users.id),
createdAt: timestamp("created_at", { withTimezone: true }).defaultNow(),
});
export const borrowRecords = pgTable("borrow_records", {
id: uuid("id").primaryKey().defaultRandom(),
userId: uuid("user_id")
.references(() => users.id)
.notNull(),
bookId: uuid("book_id")
.references(() => books.id)
.notNull(),
borrowDate: timestamp("borrow_date", { withTimezone: true })
.defaultNow()
.notNull(),
dueDate: date("due_date"),
returnDate: date("return_date"),
status: BORROW_STATUS_ENUM("status").default("BORROWED").notNull(),
// Enhanced tracking
borrowedBy: text("borrowed_by"),
returnedBy: text("returned_by"),
fineAmount: decimal("fine_amount", { precision: 10, scale: 2 }).default(
"0.00"
),
notes: text("notes"),
renewalCount: integer("renewal_count").default(0).notNull(),
lastReminderSent: timestamp("last_reminder_sent", { withTimezone: true }),
updatedAt: timestamp("updated_at", { withTimezone: true }).defaultNow(),
updatedBy: text("updated_by"),
createdAt: timestamp("created_at", { withTimezone: true }).defaultNow(),
});
- Node.js 18+
- PostgreSQL database (Neon recommended)
- Redis instance (Upstash recommended)
git clone https://github.com/your-username/university-library.git
cd university-library
npm install
Create a .env.local
file in the root directory:
# Database
DATABASE_URL="postgresql://username:password@host:port/database"
# NextAuth.js
NEXTAUTH_SECRET="your-secret-key"
NEXTAUTH_URL="http://localhost:3000"
# ImageKit Configuration
NEXT_PUBLIC_IMAGEKIT_PUBLIC_KEY="your-imagekit-public-key"
NEXT_PUBLIC_IMAGEKIT_URL_ENDPOINT="https://ik.imagekit.io/your-id"
IMAGEKIT_PRIVATE_KEY="your-imagekit-private-key"
# Upstash Redis
UPSTASH_REDIS_URL="your-redis-url"
UPSTASH_REDIS_TOKEN="your-redis-token"
# QStash (Background Jobs)
QSTASH_URL="https://qstash.upstash.io/v2"
QSTASH_TOKEN="your-qstash-token"
# Email Service (Resend)
RESEND_TOKEN="your-resend-token"
# API Endpoints
NEXT_PUBLIC_API_ENDPOINT="http://localhost:3000"
NEXT_PUBLIC_PROD_API_ENDPOINT="https://your-domain.vercel.app"
# SMTP Configuration (Optional)
SMTP_HOST="smtp.gmail.com"
SMTP_PORT="587"
SMTP_USER="your-email@gmail.com"
SMTP_PASS="your-app-password"
# Generate migration files
npm run db:generate
# Apply migrations
npm run db:migrate
# Seed the database with sample data
npm run seed
npm run dev
Visit http://localhost:3000
to see the application.
After seeding the database, you can log in with:
- Email:
test@admin.com
- Password:
12345678
- Visit
/sign-up
- Fill in your details
- Wait for admin approval
- Start borrowing books!
- Log in as admin
- Visit
/admin
dashboard - Manage users, books, and requests
- Monitor system performance
POST /api/auth/signin # User login
POST /api/auth/signout # User logout
GET /api/auth/session # Get current session
GET /api/books # Get all books
GET /api/books/[id] # Get book by ID
POST /api/books # Create new book (Admin)
PUT /api/books/[id] # Update book (Admin)
DELETE /api/books/[id] # Delete book (Admin)
POST /api/borrow # Request book borrow
PUT /api/borrow/[id] # Update borrow status
DELETE /api/borrow/[id] # Cancel borrow request
GET /api/reviews/[bookId] # Get book reviews
POST /api/reviews/[bookId] # Create review
PUT /api/reviews/edit # Update review
DELETE /api/reviews/delete # Delete review
GET /api/admin/users # Get all users
PUT /api/admin/users/[id] # Update user status
GET /api/admin/books # Get all books
POST /api/admin/books # Create book
PUT /api/admin/books/[id] # Update book
DELETE /api/admin/books/[id] # Delete book
GET /api/status/health # Overall system health
GET /api/status/database # Database status
GET /api/status/api-server # API server status
GET /api/status/metrics # System metrics
interface BookCardProps {
book: Book;
onBorrow?: (bookId: string) => void;
onViewDetails?: (bookId: string) => void;
showBorrowButton?: boolean;
}
export const BookCard = ({ book, onBorrow, onViewDetails, showBorrowButton }: BookCardProps) => {
return (
<Card className="group hover:shadow-lg transition-shadow">
<CardContent className="p-4">
<div className="flex gap-4">
<BookCover book={book} />
<div className="flex-1">
<h3 className="font-semibold text-lg">{book.title}</h3>
<p className="text-gray-600">{book.author}</p>
<p className="text-sm text-gray-500">{book.genre}</p>
<div className="flex items-center gap-2 mt-2">
<StarRating rating={book.rating} />
<span className="text-sm">{book.rating}/5</span>
</div>
{showBorrowButton && (
<Button onClick={() => onBorrow?.(book.id)} className="mt-3">
Borrow Book
</Button>
)}
</div>
</div>
</CardContent>
</Card>
);
};
interface ReviewFormDialogProps {
bookId: string;
isOpen: boolean;
onClose: () => void;
onReviewSubmitted: () => void;
}
export const ReviewFormDialog = ({ bookId, isOpen, onClose, onReviewSubmitted }: ReviewFormDialogProps) => {
const [rating, setRating] = useState(0);
const [comment, setComment] = useState("");
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
// Submit review logic
await submitReview(bookId, rating, comment);
onReviewSubmitted();
onClose();
};
return (
<Dialog open={isOpen} onOpenChange={onClose}>
<DialogContent>
<DialogHeader>
<DialogTitle>Review This Book</DialogTitle>
</DialogHeader>
<form onSubmit={handleSubmit}>
<div className="space-y-4">
<StarRating value={rating} onChange={setRating} />
<Textarea
placeholder="Write your review..."
value={comment}
onChange={(e) => setComment(e.target.value)}
/>
<div className="flex gap-2">
<Button type="submit">Submit Review</Button>
<Button type="button" variant="outline" onClick={onClose}>
Cancel
</Button>
</div>
</div>
</form>
</DialogContent>
</Dialog>
);
};
export const usePerformance = () => {
const [metrics, setMetrics] = useState<PerformanceMetrics | null>(null);
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
const fetchMetrics = async () => {
try {
const response = await fetch("/api/status/metrics");
const data = await response.json();
setMetrics(data.metrics);
} catch (error) {
console.error("Failed to fetch metrics:", error);
} finally {
setIsLoading(false);
}
};
fetchMetrics();
const interval = setInterval(fetchMetrics, 30000); // Refresh every 30s
return () => clearInterval(interval);
}, []);
return { metrics, isLoading };
};
export const { handlers, signIn, signOut, auth } = NextAuth({
session: { strategy: "jwt" },
providers: [
CredentialsProvider({
async authorize(credentials) {
const user = await db
.select()
.from(users)
.where(eq(users.email, credentials.email))
.limit(1);
if (user.length === 0) return null;
// Password verification with salt
const [saltB64, hashB64] = user[0].password.split(":");
const salt = Uint8Array.from(Buffer.from(saltB64, "base64"));
const expectedHash = Buffer.from(hashB64, "base64");
const passwordBytes = new TextEncoder().encode(credentials.password);
const hashBuffer = sha256(concatUint8Arrays(passwordBytes, salt));
const isPasswordValid = Buffer.from(hashBuffer).equals(expectedHash);
if (!isPasswordValid) return null;
return {
id: user[0].id.toString(),
email: user[0].email,
name: user[0].fullName,
};
},
}),
],
callbacks: {
async jwt({ token, user }) {
if (user) {
token.id = user.id;
token.name = user.name;
// Update last_login timestamp
await db
.update(users)
.set({ lastLogin: new Date() })
.where(eq(users.id, user.id));
}
return token;
},
async session({ session, token }) {
if (session.user) {
session.user.id = token.id as string;
session.user.name = token.name as string;
}
return session;
},
},
});
export default auth((req) => {
const { nextUrl } = req;
const isLoggedIn = !!req.auth;
// Protect admin routes
if (nextUrl.pathname.startsWith("/admin")) {
if (!isLoggedIn) {
return Response.redirect(new URL("/sign-in", nextUrl));
}
// Additional admin role check can be added here
}
// Protect authenticated routes
if (nextUrl.pathname.startsWith("/my-profile")) {
if (!isLoggedIn) {
return Response.redirect(new URL("/sign-in", nextUrl));
}
}
});
The application includes a comprehensive monitoring system accessible at /api-status
:
- API Server - Response time and uptime tracking
- Database - Connection pool and query performance
- File Storage - ImageKit CDN status
- Authentication - NextAuth.js service health
- Email Service - SMTP configuration status
- External APIs - Third-party service monitoring
- Database Performance - Active connections and pool status
- API Performance - Requests per minute tracking
- Error Rate - Failed request percentage
- Storage Usage - Database storage consumption
- Active Users - Currently online users
- SSL Certificate - Certificate validity and expiration
Interactive API documentation is available at /api-docs
with:
- Complete endpoint listings
- Request/response examples
- Authentication requirements
- Interactive testing capabilities
// Request a book borrow
const borrowBook = async (bookId: string, userId: string) => {
const borrowRecord = await db.insert(borrowRecords).values({
userId,
bookId,
status: "PENDING",
borrowDate: new Date(),
});
// Send notification to admin
await sendBorrowRequestNotification(bookId, userId);
return borrowRecord;
};
// Admin approval process
const approveBorrowRequest = async (borrowId: string, dueDate: Date) => {
await db
.update(borrowRecords)
.set({
status: "BORROWED",
dueDate,
updatedAt: new Date(),
})
.where(eq(borrowRecords.id, borrowId));
// Update book availability
await updateBookAvailability(borrowId);
};
// Submit a book review
const submitReview = async (
bookId: string,
userId: string,
rating: number,
comment: string
) => {
const review = await db.insert(bookReviews).values({
bookId,
userId,
rating,
comment,
});
// Update book average rating
await updateBookRating(bookId);
return review;
};
// Calculate average rating
const updateBookRating = async (bookId: string) => {
const reviews = await db
.select({ rating: bookReviews.rating })
.from(bookReviews)
.where(eq(bookReviews.bookId, bookId));
const averageRating =
reviews.reduce((sum, r) => sum + r.rating, 0) / reviews.length;
await db
.update(books)
.set({ rating: Math.round(averageRating) })
.where(eq(books.id, bookId));
};
// Request admin privileges
const createAdminRequest = async (userId: string, reason: string) => {
const request = await db.insert(adminRequests).values({
userId,
requestReason: reason,
status: "PENDING",
});
// Notify existing admins
await notifyAdminsOfNewRequest(request);
return request;
};
// Approve admin request
const approveAdminRequest = async (requestId: string, reviewedBy: string) => {
const request = await db
.select()
.from(adminRequests)
.where(eq(adminRequests.id, requestId))
.limit(1);
if (request.length === 0) return;
// Update request status
await db
.update(adminRequests)
.set({
status: "APPROVED",
reviewedBy,
reviewedAt: new Date(),
})
.where(eq(adminRequests.id, requestId));
// Grant admin role
await db
.update(users)
.set({ role: "ADMIN" })
.where(eq(users.id, request[0].userId));
};
- Connect your GitHub repository to Vercel
- Set environment variables in Vercel dashboard
- Deploy automatically on push to main branch
# Production Database
DATABASE_URL="postgresql://username:password@host:port/database"
# Production URLs
NEXTAUTH_URL="https://your-domain.vercel.app"
NEXT_PUBLIC_PROD_API_ENDPOINT="https://your-domain.vercel.app"
# Production Services
NEXT_PUBLIC_IMAGEKIT_URL_ENDPOINT="https://ik.imagekit.io/your-id"
UPSTASH_REDIS_URL="your-production-redis-url"
RESEND_TOKEN="your-production-resend-token"
# Generate production migration
npm run db:generate
# Apply to production database
npm run db:migrate
# Run all tests
npm test
# Run tests in watch mode
npm run test:watch
# Run tests with coverage
npm run test:coverage
__tests__/
├── components/ # Component tests
├── pages/ # Page tests
├── api/ # API endpoint tests
└── utils/ # Utility function tests
- Indexes - Strategic indexing on frequently queried columns
- Connection Pooling - Efficient database connection management
- Query Optimization - Optimized Drizzle ORM queries
- Code Splitting - Automatic route-based code splitting
- Image Optimization - Next.js Image component with ImageKit
- Caching - React Query for server state caching
- Bundle Analysis - Regular bundle size monitoring
- Rate Limiting - Upstash Redis for API protection
- Response Caching - Strategic caching of API responses
- Background Jobs - QStash for non-blocking operations
// database/schema.ts
export const newTable = pgTable("new_table", {
id: uuid("id").primaryKey().defaultRandom(),
name: varchar("name", { length: 255 }).notNull(),
createdAt: timestamp("created_at", { withTimezone: true }).defaultNow(),
});
npm run db:generate
npm run db:migrate
// app/api/new-feature/route.ts
export async function GET() {
const data = await db.select().from(newTable);
return NextResponse.json(data);
}
export async function POST(request: NextRequest) {
const body = await request.json();
const result = await db.insert(newTable).values(body);
return NextResponse.json(result);
}
// components/NewFeatureComponent.tsx
export const NewFeatureComponent = () => {
const [data, setData] = useState([]);
useEffect(() => {
fetch('/api/new-feature')
.then(res => res.json())
.then(setData);
}, []);
return (
<div>
{data.map(item => (
<div key={item.id}>{item.name}</div>
))}
</div>
);
};
// tailwind.config.ts
module.exports = {
theme: {
extend: {
colors: {
primary: {
50: "#eff6ff",
500: "#3b82f6",
900: "#1e3a8a",
},
},
fontFamily: {
sans: ["Inter", "system-ui", "sans-serif"],
},
},
},
};
- Fork the repository
- Create a feature branch:
git checkout -b feature/new-feature
- Make your changes
- Run tests:
npm test
- Commit changes:
git commit -m 'Add new feature'
- Push to branch:
git push origin feature/new-feature
- Create a Pull Request
- Use TypeScript for all new code
- Follow ESLint configuration
- Write tests for new features
- Update documentation as needed
- Use conventional commit messages
- Clear description of changes
- Reference related issues
- Include screenshots for UI changes
- Ensure all tests pass
- Update README if needed
This project is licensed under the MIT License - see the LICENSE file for details.
- Next.js Team - For the amazing React framework
- Drizzle Team - For the excellent TypeScript ORM
- Shadcn/ui - For beautiful component library
- Vercel - For seamless deployment platform
- Neon - For PostgreSQL hosting
- Upstash - For Redis and background jobs
- ImageKit - For image optimization
- Resend - For email delivery
- 📧 Email: arnob_t78@yahoo.com
- 🌐 Portfolio: https://arnob-mahmud.vercel.app/
- 🐛 Issues: GitHub Issues
- 📚 Next.js Documentation
- 🗄️ Drizzle ORM Documentation
- 🎨 Tailwind CSS Documentation
- 🔐 NextAuth.js Documentation
- Mobile App - React Native mobile application
- Advanced Analytics - Detailed usage analytics
- Multi-language Support - Internationalization
- Advanced Search - Full-text search with filters
- Notification System - Real-time push notifications
- Integration APIs - Third-party library system integration
- Automated Testing - Comprehensive test suite
- Performance Monitoring - Advanced APM integration
- GraphQL API - Alternative to REST API
- Microservices - Service-oriented architecture
- Docker Support - Containerization
- CI/CD Pipeline - Automated deployment
- Security Audit - Comprehensive security review
Free to use this project repository 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://arnob-mahmud.vercel.app/.
Enjoy building and learning! 🚀
Thank you! 😊