Library management system
Catalogue books, issue and return copies, charge for overdue ones. The classic final-year project, and the one that teaches the two modelling decisions people most often get wrong.
Read lost and found first if you have not — it explains the conventions these pages share.
The two decisions
Section titled “The two decisions”A book is not a copy. The library owns three copies of one title. A
loan is of a copy, a reservation is of a title, and the catalogue
searches titles. Collapse the two and you cannot answer “is it available?”
without a bug. So: Book for the bibliographic record, Copy for the
physical thing on the shelf with its own barcode.
A book has many authors, and an author has many books. Flare has no
many-to-many field, deliberately — that relationship always grows a column
eventually (billing order, “with contributions by”), and a junction you
cannot add a column to has to be rebuilt the day it does. So you write the
junction yourself, as an ordinary resource with two belongsTo fields. It
is three more words on the command line and it never needs undoing.
| Resource | What it holds |
|---|---|
Shelf |
Where in the building |
Author |
A person who wrote things |
Book |
The title: ISBN, publisher, cover |
BookAuthor |
The junction, one row per pairing |
Copy |
One physical book, with a barcode and a condition |
Loan |
A copy, out with somebody, due back |
Fine |
What they owe, and whether it is paid |
1. The app
Section titled “1. The app”pnpm create flare-framework librarycd librarydocker compose up -dpnpm create flare-framework library --stack cloudflarecd library2. The catalogue
Section titled “2. The catalogue”npx flare gen resource Shelf --icon library --group Catalogue \ --fields 'name:string!'
npx flare gen resource Author --icon user-pen --group Catalogue \ --fields 'name:string!, bio:text?'
npx flare gen resource Book --icon book --group Catalogue \ --fields 'isbn:string!, title:string, publisher:string?, year:int?, description:markdown?, cover:file:[image]:2mb?, shelf:belongsTo(Shelf)?'isbn:string! is unique, which is the whole of “do not catalogue the same
title twice” — enforced by the database rather than by whoever is typing.
description:markdown? gives a Markdown editor in the form and renders the
result on the detail page. A blurb wants a paragraph break and the odd
italic; a plain text? would show them as literal asterisks.
3. The junction
Section titled “3. The junction”npx flare gen resource BookAuthor --icon link --group Catalogue \ --fields 'book:belongsTo(Book), author:belongsTo(Author)'That is the many-to-many. Both sides get a searchable picker, both foreign
keys get an index, and when you later need role:enum(author,editor,translator)
on it, you add a field to a table that already exists.
4. Circulation
Section titled “4. Circulation”npx flare gen resource Copy --icon copy --group Circulation \ --fields 'barcode:string!, shelfMark:string?, condition:enum(new,good,worn,damaged), status:enum(available,onLoan,reserved,lost), book:belongsTo(Book)'
npx flare gen resource Loan --icon arrow-right-left --group Circulation \ --fields 'issuedAt:date, dueDate:date, returnedAt:date?, renewals:int, borrowerId:string, copy:belongsTo(Copy)'barcode:string! is what the scanner types. Unique, because two copies with
the same barcode is the one failure a scanner cannot recover from.
returnedAt:date? nullable is the open-loan flag. Resist adding
isReturned:boolean beside it — two columns that must agree will one day
disagree, and then you have to decide which is telling the truth.
borrowerId:string is the account id, as on every page here. See
lost and found for why it is not a
relation.
5. Fines, and the thing to get right about money
Section titled “5. Fines, and the thing to get right about money”npx flare gen resource Fine --icon banknote --group Circulation \ --fields 'amount:money, reason:enum(overdue,damage,loss), paid:boolean, paidAt:datetime?, loan:belongsTo(Loan)'money is not a float. It is stored as whole minor units — 1500 for
UGX 1,500, 250 for $2.50 — because 0.1 + 0.2 is not 0.3 in binary, and a
fines ledger that drifts by a cent a row is a fines ledger nobody trusts.
The form shows and accepts the amount the way a person writes it; the
column holds an integer.
The exponent comes from the currency, so a zero-decimal currency like UGX or JPY stores the whole number and a two-decimal one stores cents. You do not configure that.
6. Run it
Section titled “6. Run it”npx flare migratenpx flare seed:resource Shelf 6npx flare seed:resource Author 20npx flare seed:resource Book 60npx flare seed:resource Copy 120npx flare seed:resource Loan 50npx flare seed:resource Fine 20npx flare devParents before children, or the command tells you which table is empty.
What you still owe
Section titled “What you still owe”- The fine calculation. Days overdue times a rate, capped, waived for
some member types. A hook on
Loanupdate, or a scheduled job. This is your algorithm and the thing to write about. - Availability. “Is this title on the shelf” is a count of its copies
with
status = available. A computed field onBook, or a view. - Reservations and renewals, with their own rules: how many, how long, who jumps the queue.
- The barcode scanner. A scanner is a keyboard; the work is a form that listens for a fast burst of keystrokes ending in Enter.
- Search. The generated table filters and sorts. Real catalogue search across title, author and ISBN wants an index built for it — Postgres full-text on the Next.js stack, FTS5 on Cloudflare.
Make it yours
Section titled “Make it yours”- Members see only their own loans.
npx flare gen policy Loan --roles '*' --own borrowerId --own-except librarian,admin - E-books. Add
file:file:[pdf]:50mb?toBook, or a separateEbookresource if a title can have several formats. - Never lose a withdrawn title. Generate
Bookwith--soft-deleteand a removed record goes to a Trash view instead of away.