AniRepo
Background
AniRepo is a personal cataloging application for anime-related media and collectibles. Its primary goal is to help you maintain a structured, queryable record of your collection over time.
AniRepo focuses on seven main collection types:
- Books - light novels, manga, artbooks, guidebooks
- Video Games - console and PC games, especially anime or adjacent franchises
- Movies - anime films, OVAs, and related media
- Autographs - signed items from guests, artists, or voice actors
- Art - prints, commissions, posters, sketches, and other artwork
- Figures - scale, prize, and chibi figures (e.g., Nendoroids)
- Merchandises - miscellaneous goods: keychains, pins, badges, apparel, etc.
The application provides:
- A dashboard showing collection statistics and the most recently added item
- Full CRUD operations for each collection type (create, list, detail, update, delete)
- A filtering API that can search within a specific collection or across all collections
- A consistent React + Material UI frontend with a simple, navigable UI
AniRepo is currently designed as a single-user, private app, but the architecture is structured so it can later be extended with:
- Authentication and authorization
- Multi-user support
- Rich analytics and visualizations
High-Level Overview
AniRepo is split into two main applications:
Backend -
back/- Node.js + Express REST API
- MongoDB via Mongoose
- Layered architecture: models -> services -> controllers -> routes
- Provides CRUD endpoints, dashboard aggregation, and generic filters
Frontend -
front/- React SPA (Create React App)
- React Router for navigation
- Material UI for layout and components
- Axios for talking to the backend API
Conceptual architecture:
- The React client (
front) calls the Express API (back) using JSON over HTTP - The API delegates to service modules, which talk to Mongoose models
- Mongoose manages persistence to MongoDB
Directory Structure
Root
anirepo-main/
.gitignore
README.md # Can be this document or a shorter summary
back/ # Backend API (Node.js / Express)
front/ # Frontend SPA (React / MUI)
Backend - back/
back/
app.js # Express app entry point
package.json
package-lock.json
config/
db.js # MongoDB connection logic
controllers/
artController.js
autographController.js
bookController.js
dashboardController.js
figureController.js
filterController.js
merchandiseController.js
movieController.js
videoGameController.js
models/
Art.js
Autograph.js
Book.js
Figure.js
Merchandise.js
Movie.js
VideoGame.js
routes/
arts.js
autographs.js
books.js
dashboard.js
figures.js
filters.js
merchandises.js
movies.js
videoGames.js
services/
artService.js
autographService.js
bookService.js
dashboardService.js
figureService.js
merchandiseService.js
movieService.js
videoGameService.js
Filenames are illustrative; adjust capitalization to match the actual repository (e.g.,
videoGameController.jsvsvideogameController.js).
Frontend - front/
front/
package.json
package-lock.json
public/
index.html
favicon.ico
manifest.json
...
src/
index.js
index.css
App.js
App.css
components/
Layout/
Navbar.js
Dashboard.js
StatBox.js
Books/
ListBook.js
DetailBook.js
AddBook.js
EditBook.js
VideoGames/
ListVideoGame.js
DetailVideoGame.js
AddVideoGame.js
EditVideoGame.js
Movies/
ListMovie.js
DetailMovie.js
AddMovie.js
EditMovie.js
Autographs/
ListAutograph.js
DetailAutograph.js
AddAutograph.js
EditAutograph.js
Arts/
ListArt.js
DetailArt.js
AddArt.js
EditArt.js
Figures/
ListFigure.js
DetailFigure.js
AddFigure.js
EditFigure.js
Merchandises/
ListMerchandise.js
DetailMerchandise.js
AddMerchandise.js
EditMerchandise.js
Backend Design (back/)
Technologies
- Runtime: Node.js
- Framework: Express
- Database: MongoDB (Atlas or self-hosted)
- ODM: Mongoose
- Config & tooling: dotenv, nodemon
- Middleware:
cors, JSON body parsing (express.json/body-parser)
Key package.json (backend) concepts:
- Dependencies:
express,mongoose,cors,body-parser,dotenv,mongodb - Dev dependency:
nodemon - Example scripts:
"start": "nodemon app.js"
Application Entry - app.js
Responsibilities:
- Load environment variables (
dotenv) - Initialize Express app
- Configure middleware:
cors()to allow cross-origin requests fromfrontexpress.json()orbody-parser.json()for JSON bodies
- Connect to MongoDB via
config/db.js - Register routes:
/books,/videogames,/movies,/autographs,/arts,/figures,/merchandises/dashboard/filters
- Start HTTP server on
process.env.PORT || 3001
Typical route registration (simplified):
app.use('/books', bookRoutes);
app.use('/videogames', videoGameRoutes);
app.use('/movies', movieRoutes);
app.use('/autographs', autographRoutes);
app.use('/arts', artRoutes);
app.use('/figures', figureRoutes);
app.use('/merchandises', merchandiseRoutes);
app.use('/dashboard', dashboardRoutes);
app.use('/filters', filterRoutes);
Database Configuration - config/db.js
Uses
dotenvto load:MONGODB_USERNAMEMONGODB_PASSWORDMONGODB_HOSTMONGODB_COLLECTIONMONGODB_CLUSTER
Builds a connection string like:
mongodb+srv://
: @ / ?retryWrites=true&w=majority Calls
mongoose.connect(...)and logs:- Success: connected message
- Failure: error message and (optionally) process exit
Design choice:
All database credentials and host information live in environment variables so:
- Secrets are not committed to the repo
- Different databases can be used per environment (local/dev/prod) simply by changing the
.envfile
Layered Architecture
The backend uses a layered structure for each domain type:
Model -
models/*.js- Defines Mongoose schema and model for each collection.
Service -
services/*Service.js- Contains data access and business logic.
- Implements operations like
find,create,update,delete, plus aggregates.
Controller -
controllers/*Controller.js- Handles Express
req/res. - Translates HTTP requests to service calls.
- Sets response codes and formats response JSON.
- Handles Express
Route -
routes/*.js- Maps HTTP verbs and paths to controller methods.
- Defines resource URLs (e.g.,
GET /books,POST /books,GET /books/:id).
This separation keeps:
- HTTP concerns (status codes, headers) in controllers
- Data logic (queries, validations) in services
- Schema definitions in models
Data Models
All entities share concepts such as:
item_type- identifies what kind of item it is- A name/title
- Creator/author/artist information
- An
addedtimestamp - Optional
imageUrl - Ownership-related fields (e.g.,
copies)
Each model specializes fields for its domain.
Book - models/Book.js
Common fields:
item_type: String- typically"book"title: String- book or volume titlecreators: String- combined creator stringfirst_name: String,last_name: String- primary creator split nameean_isbn13: String,upc_isbn10: String- identifiersdescription: String- description or blurbpublisher: Stringpublish_date: Datelength: Number- page count or similaradded: Date- defaults toDate.now; used for dashboard and filterscopies: Number- how many copies you ownimageUrl: String- optional cover or reference image
VideoGame - models/VideoGame.js
Typical fields:
item_type: String-"videogame"title: Stringplatform: String- e.g.,PS5,Switch,PCpublisher: Stringrelease_date: Datedescription: String(optional)added: Date,imageUrl: String,copies: Number
Movie - models/Movie.js
Typical fields:
item_type: String-"movie"title: Stringdirector: Stringruntime: Number- runtime in minutesrelease_date: Datedescription: Stringadded: Date,imageUrl: String
Autograph - models/Autograph.js
Typical fields:
item_type: String-"autograph"first_name: String,last_name: Stringseries_or_role: String- what the person is known forevent: String- event where autograph was obtainednotes: String- inscription, condition, etc.added: DateimageUrl: String
Art, Figure, Merchandise - Art.js, Figure.js, Merchandise.js
Common patterns:
item_type: String-"art","figure","merchandise"titleorname: Stringfranchise: String- e.g.,Demon Slayer,fronttomanufacturerorartistrelease_date: Date(optional)notes: Stringadded: DateimageUrl: String
Design note:
Storing item_type in each document (even though it's implied by the collection) allows:
- Easier cross-collection aggregations
- Mixed search results that clearly indicate type
Services
Services expose standard CRUD-style operations:
getX(queryParams)getXById(id)createX(data)updateX(id, data)deleteX(id)
Example service structure (Book):
// services/bookService.js
exports.getBooks = async (queryParams) => {
const query = { item_type: 'book', ...queryParams };
return await Book.find(query);
};
exports.getBookById = async (id) => Book.findById(id);
exports.createBook = async (bookData) => {
bookData.item_type = 'book';
const book = new Book(bookData);
await book.save();
return book;
};
exports.updateBook = async (id, updateData) =>
Book.findByIdAndUpdate(id, updateData, { new: true });
exports.deleteBook = async (id) =>
Book.findByIdAndDelete(id);
Other services for videoGame, movie, autograph, art, figure, merchandise follow the same pattern.
Controllers & Routes
Controllers:
- Wrap service calls in
try/catch - Return appropriate HTTP status codes and JSON responses
Typical controller methods:
// controllers/bookController.js
exports.getBooks = async (req, res) => {
try {
const books = await BookService.getBooks(req.query);
res.status(200).json(books);
} catch (error) {
res.status(500).json({ error: error.message });
}
};
exports.createBook = async (req, res) => {
try {
const newBook = await BookService.createBook(req.body);
res.status(201).json(newBook);
} catch (error) {
res.status(400).json({ error: error.message });
}
};
Routes:
// routes/books.js
router.get('/', bookController.getBooks);
router.get('/:id', bookController.getBookById);
router.post('/', bookController.createBook);
router.put('/:id', bookController.updateBook);
router.delete('/:id', bookController.deleteBook);
Each collection type has a corresponding route file with the same CRUD pattern.
Dashboard Logic
Service: dashboardService.js
Controller: dashboardController.js
Route: dashboard.js
GET /dashboard/counts
Uses
Model.countDocuments()on each collectionReturns:
{ "bookCount": 12, "videoGameCount": 8, "movieCount": 5, "autographCount": 4, "artCount": 6, "figureCount": 10, "merchandiseCount": 3 }
GET /dashboard/recent
High-level algorithm:
- For each model, fetch the latest document sorted by
addeddescending, limit 1 - Combine those "most recent" items from each collection into an array
- Sort the combined array by
addeddescending - Return the single most recent item globally
Used by the frontend to show "Most Recent Item" on the dashboard.
Filtering Logic
Controller: filterController.js
Route: filters.js
Route signature:
// GET /filters/:collectionType?
router.get('/:collectionType?', getFilteredItems);
Core concepts:
A registry mapping collection type -> Mongoose model, e.g.:
const collections = { book: Book, videogame: VideoGame, movie: Movie, autograph: Autograph, art: Art, figure: Figure, merchandise: Merchandise, };
Behavior:
With
:collectionTypepresent (e.g.,/filters/book):- Validate
collectionType - Build a filter object from
req.query - Query only that model
- Return filtered results
- Validate
Without
:collectionType(e.g.,/filters):- Run filter queries on all collections
- Concatenate results into a single list
- Sort by
addeddescending - Apply
limit(default e.g.5)
This endpoint supports both per-collection searches and cross-collection search.
Error Handling
Each controller uses
try/catchand returns JSON errors like:{ "error": "error message here" }
Future enhancement: add centralized Express error middleware for:
- Consistent error format
- Logging
- Mapping internal errors to safe client-facing messages
Frontend Design (front/)
Technologies
- Framework: React (Create React App)
- Routing:
react-router-domv6 - UI Library: Material UI (MUI)
- HTTP Client: Axios
- Styling:
index.css,App.css, plus MUIsxprops
Application Entry - src/App.js
Responsibilities:
- Wrap the app in
BrowserRouter - Render
Navbaron all pages - Provide a layout container (MUI
Container) with top margin to account for the AppBar - Define routes for:
/- Dashboard/books,/add-book,/books/:id,/edit-book/:id- Equivalent patterns for videogames, movies, autographs, arts, figures, merchandises
Typical route structure (simplified):
<Router>
<Navbar />
<Container sx={{ mt: 10 }}>
<Routes>
<Route path="/" element={<Dashboard />} />
<Route path="/books" element={<ListBook />} />
<Route path="/add-book" element={<AddBook />} />
<Route path="/books/:id" element={<DetailBook />} />
<Route path="/edit-book/:id" element={<EditBook />} />
{/* Similar routes for other collections */}
</Routes>
</Container>
</Router>
Layout Components
Navbar - components/Layout/Navbar.js
Uses MUI components (
AppBar,Toolbar,IconButton,Typography,Drawer,List,ListItem)Shows:
- App title: AniRepo
- Navigation links to:
- Dashboard
- Books
- Video Games
- Movies
- Autographs
- Arts
- Figures
- Merchandises
Manages:
- Drawer open/close state for mobile view
- Desktop vs. mobile navigation layout
Dashboard - components/Layout/Dashboard.js
State:
counts- from/dashboard/countsrecent- array with the most recent item from/dashboard/recent
Effects:
- On mount:
- Call
GET /dashboard/counts - Call
GET /dashboard/recent
- Call
Rendering:
A grid of
StatBoxorCardcomponents showing counts for each collection typeA "Most Recent" section displaying:
- Title or name
- Description or notes (if available)
item_typeaddedformatted withtoLocaleString()
StatBox - components/Layout/StatBox.js
Reusable component with props like
labelandvalueRenders a small MUI
Cardwith:- Label as heading
- Value as a large stat number
Used by the Dashboard for consistent display of counts.
Collection Components
Each collection type has four main components:
List<Type>.jsDetail<Type>.jsAdd<Type>.jsEdit<Type>.js
For example, Books:
ListBook,DetailBook,AddBook,EditBook
List Components
Example behavior (ListBook, ListVideoGame, etc.):
- On mount, fetch items:
useEffect(() => {
axios.get('http://localhost:3001/books')
.then(res => setBooks(res.data));
}, []);
Display:
- Table or grid with key fields (title, franchise, added date, etc.)
- "Add" button to navigate to
/add-<type>
Actions per row:
- View details ->
/resource/:id - Edit ->
/edit-<type>/:id - Delete ->
DELETE /resource/:id, then refresh list
- View details ->
Detail Components
Example behavior (DetailBook, etc.):
Read
idfromuseParams()Fetch resource with
GET /resource/:idon mountDisplay all relevant fields in a MUI
CardProvide navigation to:
- Back to list
- Edit page
Add Components
Example behavior (AddBook, etc.):
Maintain form state with
useStatefor each fieldUse MUI
TextField,Select, etc. for controlled inputsOn submit:
- Validate minimal requirements
- Call
POST /resourcewith JSON body - On success, navigate to list or detail
Edit Components
Example behavior (EditBook, etc.):
Fetch existing item via
GET /resource/:idon mountPopulate form with existing values
On submit:
- Call
PUT /resource/:idwith updated fields - Navigate to target view on success
- Call
State Management & Data Flow
- Component-level state only (React
useState) - Data fetching via Axios in
useEffecthooks - No global state managers (Redux, Context) at this stage
- Navigation via
Linkcomponents anduseNavigatefromreact-router-dom
API Summary
Base URL for local development:
http://localhost:3001
CRUD Endpoints
For each resource type (books, videogames, movies, autographs, arts, figures, merchandises):
| Method | Path | Description |
|---|---|---|
| GET | /resource |
List all items (supports query filters) |
| GET | /resource/:id |
Retrieve a single item by ID |
| POST | /resource |
Create a new item |
| PUT | /resource/:id |
Update an existing item |
| DELETE | /resource/:id |
Delete an item by ID |
Example: Books
GET /booksGET /books/:idPOST /booksPUT /books/:idDELETE /books/:id
Dashboard Endpoints
GET /dashboard/counts- Returns counts per collection type
GET /dashboard/recent- Returns the single most recent item across all collections
Filter Endpoints
GET /filters- Filters/searches across all collection types
- Query parameters define filter fields
- Results sorted by
addeddescending limitparameter (if provided) restricts the number of results
GET /filters/:collectionType- Filters/searches within a specific collection type
collectionTypein {book,videogame,movie,autograph,art,figure,merchandise}
Configuration & Environment
Backend - back/
Typical .env file (not committed to source control):
MONGODB_USERNAME=yourUser
MONGODB_PASSWORD=yourPassword
MONGODB_HOST=yourHost.mongodb.net
MONGODB_COLLECTION=yourDatabaseName
MONGODB_CLUSTER=yourClusterName
PORT=3001
These values are used in:
config/db.js- to construct the MongoDB connection stringapp.js- to set the server listen port
Frontend - front/
Axios calls currently use URLs like
http://localhost:3001/...For production, you can introduce:
REACT_APP_API_URLenvironment variable- A central Axios instance that reads
process.env.REACT_APP_API_URL
User Flows
Check Overall Collection (Dashboard)
- User navigates to
/ - Dashboard component calls:
GET /dashboard/countsGET /dashboard/recent
- User sees:
- Count tiles for each collection type
- A "Most Recent Item" card showing latest addition
Browse a Collection (Books example)
- User clicks "Books" in the Navbar
- App navigates to
/books ListBookcallsGET /booksand renders list- From here user can:
- Click an item to view
/books/:id(detail) - Click "Add Book" to go to
/add-book - Click "Edit" to go to
/edit-book/:id - Click "Delete" to remove an item via
DELETE /books/:id
- Click an item to view
Add a New Autograph
- User goes to
/autographs - Clicks "Add Autograph" -> navigates to
/add-autograph AddAutographpresents form (name, event, notes, etc.)- On submit:
- Calls
POST /autographs - On success, navigates to
/autographsor/autographs/:id
- Calls
Edit an Existing Video Game
- User is on
/videogamesand selects a game ->/videogames/:id - On detail page, clicks "Edit"
EditVideoGamefetches data for that ID- User edits fields and submits
- Component calls
PUT /videogames/:id - On success, user is redirected back to detail or list view
Cross-Collection Search (Future UI)
- User types a franchise name in a search bar (future feature)
- Frontend calls
GET /filters?franchise=Evangelion&limit=20 - Backend returns mixed results (books, figures, art, etc.) sorted by
added - UI renders results with labels indicating each item's type
Extensibility & Future Work
Adding a New Collection Type (e.g., "Cards")
Backend steps:
- Create
models/Card.jswith appropriate schema anditem_type: 'card' - Create
services/cardService.jswithgetCards,getCardById,createCard,updateCard,deleteCard - Create
controllers/cardController.js - Create
routes/cards.jsand mount it inapp.js:
app.use('/cards', cardRoutes);
- Update
dashboardService.jsto include card counts and latest item - Update
filterController.jsto addcard: Cardto the model registry
Frontend steps:
- Create
components/Cards/with:ListCard,DetailCard,AddCard,EditCard
- Add routes to
App.js:/cards,/cards/:id,/add-card,/edit-card/:id
- Add a "Cards" entry to
Navbar.js - Optionally update
Dashboard.jsto include cards in stats and recent display
Authentication
Potential enhancements:
- Add a login system using JWT or session-based authentication
- Protect write endpoints (create/update/delete) with authentication middleware
- Support per-user collections
Validation & Error Handling
- Introduce validation using libraries like
Joi,Yup, orZod - Add Express error-handling middleware to:
- Normalize error responses (e.g.,
{ code, message, details }) - Log stack traces on the server
- Avoid leaking internal errors to the client
- Normalize error responses (e.g.,
Search & Tagging
- Add MongoDB text indexes on fields like
title,description,franchise - Support tagging items (e.g.,
signed,limited,wishlist) - Extend
/filtersendpoint to handle tag queries
Deployment & DevOps
- Add Dockerfiles for
backandfront - Use environment variables for:
- API base URLs
- MongoDB host and credentials
- Set up CI/CD pipeline to:
- Run tests
- Lint/format
- Build and deploy automatically