A modern, performant web application for browsing and managing a comprehensive book database. Built with Next.js 15, TypeScript, Prisma, and PostgreSQL.
- Book Management: Browse, search, and view detailed information about books
- Author Profiles: Comprehensive author pages with their complete bibliography
- ISBN Lookup: Direct ISBN-based book search and information retrieval
- Duplicate Detection: Advanced author duplicate detection and merging system
- Infinite Scrolling: Smooth, performant browsing experience for large datasets
- Search Functionality: Full-text search across books and authors
- Admin Tools: Duplicate management interface for data quality maintenance
- Framework: Next.js 15.1.3 with App Router
- Language: TypeScript
- Database: PostgreSQL (via Neon)
- ORM: Prisma 6.1.0
- Styling: Tailwind CSS 3.4
- Authentication: NextAuth.js v5
For comprehensive documentation, see the docs/ directory:
- Complete Documentation - Overview and quick start guide
- API Reference - Detailed API documentation with examples
- Component Reference - React component documentation with usage
- Development Guide - Setup, workflow, and contribution guidelines
- Deployment Guide - Production deployment instructions
- Database Schema - Database design and relationships
- Project Structure - Codebase architecture overview
npm install # Install dependencies
cp .env.example .env.local # Configure environment
npx prisma generate # Generate database client
npx prisma migrate dev # Set up database
npm run dev # Start development server
/
- Home page with infinite scroll of books/book/[id]
- HTML view of book details/isbn/[isbn13]
- HTML view of book data by ISBN/author/[id]
- Author profile page with book list/authors
- Authors listing with alphabetical filtering/books?q=
- Search results page
/search?q=
- Search for books by title/author; returnsSearchResult
JSON (legacy endpoint, always returns JSON)/api/search?q=
- Search for books by title/author; returnsSearchResult
JSON (preferred endpoint)/book/[id].json
- Book data asBookResponse
JSON/isbn/[isbn13].json
- Book data by ISBN asIsbnResponse
JSON/api/authors
- List all authors (supports?letter=
for filtering)/api/authors/[id]
- Author details with books
/book-json/[id]
- Redirects to/book/[id].json
/isbn-json/[isbn13]
- Redirects to/isbn/[isbn13].json
type ApiBook = {
id: string;
createdAt: number|Date;
updatedAt: number|Date;
title: string;
isbn13: string; // @deprecated use the editions isbn13
authors: ApiAuthor[];
longTitle?: string|null;
synopsis?: string|null;
publicationDate?: string|null; // @deprecated use the editions publication date
publisher?: string|null; // @deprecated use the editions publisher
binding: 'Unknown'|'Hardcover'|'Paperback'|'Ebook'|'Audiobook'; // @deprecated see the edition bindings
image?: ApiImage|null; // @deprecated use an edition image
editions: ApiEdition[];
openLibraryId?: string|null;
goodReadsId?: string|null;
hardcoverId?: number|null;
}
type ApiBinding = 'Unknown'|'Hardcover'|'Paperback'|'Ebook'|'Audiobook';
type ApiEdition = {
id: string;
createdAt: number|Date;
updatedAt: number|Date;
isbn13: string;
publicationDate?: string|null;
publisher?: string|null;
binding: ApiBinding;
image?: ApiImage|null;
openLibraryId?: string|null;
goodReadsId?: string|null;
hardcoverId?: number|null;
}
type ApiAuthor = {
id: string;
createdAt: number|Date;
updatedAt: number|Date;
name: string;
openLibraryId?: string|null;
goodReadsId?: string|null;
hardcoverId?: number|null;
}
type ApiImage = {
id: string;
createdAt: number|Date;
updatedAt: number|Date;
url: string;
width: number;
height: number;
}
type SearchResultSuccess = {
status: 'ok';
books: ApiBook[];
};
type SearchResultError = {
status: 'error';
message: string;
};
type SearchResult = SearchResultSuccess|SearchResultError;
type BookResponseError = {
status: 'error';
message: string;
};
type BookResponseSuccess = {
status: 'ok';
book: ApiBook;
};
type BookResponse = BookResponseError|BookResponseSuccess;
type IsbnResponseError = {
status: 'error';
message: string;
};
type IsbnResponseSuccess = {
status: 'ok';
book: ApiBook;
};
type IsbnResponse = IsbnResponseError|IsbnResponseSuccess;