Vaultisse helps you organize all your books — whether they’re at home, school, or work. Easily keep track of where each book is, which ones you’ve read, the ones you’ve lent out, and the ones still waiting to be read. Never lose track of your library again!
2
stars
167
commits
TypeScript
primary language
Sep 11, 2026
updated
The easiest way to track, organize, and share all your books.
Vaultisse is an open-source web application for managing physical book collections — at home, in a school library, or in a small lending library. It tracks where each book lives, who currently has it on loan, and its full catalog metadata (author, category, language, format, cover image), and it can look books up automatically by ISBN.
demo@vaultisse.com / VaultisseDemo!2026

/docs help pages, rendered from Markdown, in the same languagesVaultisse is a classic SPA + REST API monorepo: a Vue 3 single-page app talks to an Express/PostgreSQL backend over a JSON API. In production the backend also serves the built frontend, so the whole app runs as a single Node.js process behind one port.
┌─────────────────────────┐ HTTPS / cookies ┌──────────────────────────┐
│ client/ (Vue 3 SPA) │ ─────────────────────────────▶ │ server/ (Express API) │
│ Vuetify UI components │ ◀───────────────────────────── │ JWT auth, business │
│ built with Vite │ JSON over /api/rest │ logic, PostgreSQL │
└─────────────────────────┘ └────────────┬─────────────┘
│
▼
┌────────────────────┐
│ PostgreSQL DB │
└────────────────────┘
Located in client/. A Vue 3 + Vuetify 3 single-page application built with
Vite.
src/views/ – one folder per feature area (book, authors, categories, customers,
locations, dashboard, search, settings, docs, legal). Each contains the page-level
Vue components for that feature.src/controller/ – view controllers that hold page state/logic and call
services, keeping .vue files focused on markup (BaseController.ts is the shared
base class).src/service/ – one service per resource (book, author, categories, customers,
locations, dashboard, search, user). Services wrap the HTTP calls to the backend
API (ApplicationService.ts is the shared Axios wrapper, configured in
src/plugins/axiosInstance.ts).src/model/ – TypeScript classes/interfaces mirroring the domain entities
(book, author, category, customer, location, format, language, user).src/router/ – Vue Router setup; each feature has its own route file under
src/router/routes/.src/plugins/i18n/ – Vue I18n configuration and the generated label map used
for translations (labels themselves are stored in the database — see
Internationalization).src/components/ – shared/reusable UI components (dialogs, tables, pickers,
the barcode/ISBN scanner, label printing, etc.).The client is served under the /app base path (see vite.config.ts and
router/Router.ts) and never talks to the database directly — everything goes
through the REST API at /api/rest/*.
Located in server/. A Node.js + Express + TypeScript REST API backed by
PostgreSQL.
src/index.ts – process entry point; boots the singleton AppService.src/AppService.ts – the application core. Owns the Express app, the
PostgreSQL connection pool (pg.Pool), configuration read from environment
variables, the logger, and helpers for JWT sessions and password hashing
(bcrypt). It also wires up global middleware: helmet (secure headers),
express-rate-limit (brute-force/DoS mitigation), cors (restricted to the
configured frontend origin), and cookie/body parsing.src/routes/ – one Express router per resource, registered in Routes.ts
under the /api/rest prefix: AppRoute, BooksRoute, LocationRoute,
CustomerRoute, AuthorRoute, CategoriesRoute, UserRoute, DashboardRoute.
AuthRoute is mounted separately at the root (/) and handles login, register,
logout, and serving the built SPA in production.src/middlewares/AuthMiddleware.ts – requireAuth/requireAuthPage
guards. Verify the JWT stored in the token cookie, check the user still
exists and isn't disabled, check the session hasn't been individually
revoked, and silently refresh the cookie when it's close to expiry — see
docs/AUTHENTICATION.md. In development, setting
ALLOW_DEV_AUTH=true bypasses login with a fake session — never enable
this in production.src/types/ – shared TypeScript interfaces (book/stock shapes, search
filters, app error types).src/utils/Logger.ts – lightweight file logger (LOGGER_PATH env var).src/assets/ – static assets shipped with the API: the standalone
login.html/register.html pages, background image, and (in production) the
built client bundle served from assets/app.Authentication is a JWT in an httpOnly cookie, but not purely stateless:
requireAuth also checks a per-user token_version counter (bumped on
password change, invalidating every device at once) and a per-login
user_sessions row (so "log out this device" in Settings can revoke just
one), on every protected request. Passwords are hashed with bcrypt (12 salt
rounds) and never stored or logged in plaintext. See
docs/AUTHENTICATION.md for the full session/revocation model.
npm run dev in client/) and
proxies any request to /api/rest/* to the API (http://localhost:<API_PORT> by
default, see vite.config.ts). The API runs separately via npm run dev in
server/ (nodemon + ts-node).AuthRoute.ts, which serves index.html for /app and
/app/*, guarded by requireAuth) — there is a single process and a single port.For deeper dives into specific parts of the codebase (not to be confused with
the in-app /docs help pages, which are end-user-facing), see
docs/.
vaultisse/
├── client/ # Vue 3 + Vuetify frontend (see client/README.md)
│ └── src/
│ ├── views/ # Page components, grouped by feature
│ ├── controller/ # Page controllers (state + logic)
│ ├── service/ # API clients, one per resource
│ ├── model/ # Domain TypeScript models
│ ├── router/ # Vue Router configuration
│ ├── components/ # Shared UI components
│ └── plugins/ # i18n, Vuetify, Axios setup
├── server/ # Express + TypeScript REST API
│ └── src/
│ ├── routes/ # One Express router per resource
│ ├── middlewares/ # requireAuth (JWT) middleware
│ ├── types/ # Shared TypeScript types
│ ├── utils/ # Logger, etc.
│ └── assets/ # Static login/register pages, prod client bundle
├── assets/
│ ├── db/ # SQL schema (databaseSchema.sql) + upgrade/ (dated upgrade files)
│ └── pm2/ # Sample PM2 ecosystem config for production
├── build.sh # Builds client + server into ./dist and zips it
└── LICENSE
GOOGLE_BOOKS_API_KEY isn't set)git clone https://github.com/AlbertAmat/vaultisse.git
cd vaultisse
Create a PostgreSQL database and load the schema — databaseSchema.sql is always
kept up to date, so this is everything a new install needs:
createdb paperbooks
psql -d paperbooks -f assets/db/databaseSchema.sql
There is no admin UI for the very first user — after loading the schema, either insert a row into
usersdirectly, or start the server withALLOW_DEV_AUTH=trueand use the/registerpage, then turnALLOW_DEV_AUTHback off.
The version-named files in assets/db/upgrade/ (1.0.0/1.sql, 1.0.0/2.sql,
...) are not for new installs — they're incremental upgrades for a
database that's already running an older version of the schema (see
assets/db/upgrade/README.md for the full
convention). Each one's header comment says what it does and confirms new
installs should skip it. If you're upgrading an existing instance instead of
setting one up fresh, apply every version's file(s) newer than whatever
version you're currently on, up through the version you're installing, in
order:
psql -d paperbooks -f assets/db/upgrade/1.0.0/1.sql # example: apply the first v1.0.0 upgrade file
Create server/.env (this file is git-ignored) with:
API_PORT=3000
DB_HOST=localhost
DB_PORT=5432
DB_NAME=paperbooks
DB_USER=your_db_user
DB_PASSWORD=your_db_password
GOOGLE_BOOKS_API_KEY= # optional, see Prerequisites
LOGGER_PATH=./logs.log
FRONT_END_URL=http://localhost:5173
JWT_SECRET=replace_with_a_long_random_string
SESSION_TIME=3600000 # milliseconds
ALLOW_DEV_AUTH=false # NEVER true in production
Generate a strong JWT_SECRET, for example: openssl rand -hex 32.
cd server
npm install
npm run dev # starts the API with nodemon on API_PORT
cd client
npm install
npm run dev # starts Vite; proxies /api/rest to the server
Open the URL Vite prints (typically http://localhost:5173/app) in your browser.
From the repository root:
./build.sh
This installs dependencies, builds the client (Vite) and server (tsc), assembles
everything under ./dist (dist/client, dist/server), copies server assets, and
produces a deployable dist.zip. The server serves the built client itself, so a
single Node.js process is all you need to run in production.
A sample PM2 config is provided at
assets/pm2/ecosystem.config.js.sample. Copy it, fill in your production values
(database credentials, JWT_SECRET, FRONT_END_URL, etc.), and run:
cp assets/pm2/ecosystem.config.js.sample ecosystem.config.js
# edit ecosystem.config.js with your production values
pm2 start ecosystem.config.js --env production
Every push of a vX.Y.Z tag builds a production image and publishes it to GitHub
Container Registry at ghcr.io/albertamat/vaultisse (see
Releasing a new version). The image bundles the compiled
server and the built client into one process, same as the PM2 deployment above — there
is no separate frontend container.
To run it on a server with Docker installed:
curl -O https://raw.githubusercontent.com/AlbertAmat/vaultisse/main/docker-compose.yml
curl -O https://raw.githubusercontent.com/AlbertAmat/vaultisse/main/.env.example
cp .env.example .env
# edit .env: set JWT_SECRET, DB_PASSWORD, FRONT_END_URL, etc.
docker compose up -d
This starts two containers: db (PostgreSQL, seeded from
assets/db/databaseSchema.sql on first run) and app (the image above). Pin APP_TAG
in .env to a specific released version rather than latest if you want upgrades to
be a deliberate step.
A few things worth knowing before pointing this at a real server:
ALLOW_DEV_AUTH must stay false. It bypasses login with a fake session and
exists only for local development.DB_HOST/DB_PORT in .env are ignored for the app container — it always
connects to the db service on the compose network. They only configure the db
container itself./app/logs inside the container (LOGGER_PATH), persisted
via the app-logs volume.docker build -t vaultisse .To build a multi-arch image or push to a different registry, adjust
.github/workflows/docker-release.yml.
For step-by-step instructions covering local-only self-hosting, production behind your own reverse proxy/DNS, production behind Cloudflare Tunnel, and a one-click Unraid template (Docker tab → Add Container, no Compose needed), see docs/DEPLOYMENT.md.
The repository root has a package.json that exists solely to anchor the release
version (the client and server aren't published packages and keep their own internal
version fields). To cut a release:
npm version patch # or: minor / major
git push --follow-tags
npm version bumps package.json, commits it, and creates a vX.Y.Z git tag.
Pushing that tag triggers .github/workflows/docker-release.yml, which builds the
Docker image, pushes ghcr.io/albertamat/vaultisse:X.Y.Z and :latest, and creates
a matching GitHub Release with auto-generated notes.
The running app version is available at GET /api/rest/app/version (unauthenticated),
useful for confirming what's actually deployed.
The first time you release, open the new package under Packages on the GitHub repo
and set its visibility (packages default to private) so docker pull works without
authentication on your server.
The UI currently ships in English, Spanish, Catalan, and Italian. Labels are stored
in the database (app_languages / app_labels tables in databaseSchema.sql) and
loaded into the client's Vue I18n instance — this keeps translations editable
without a redeploy. The /docs help pages are Markdown files rendered per-language
in the client. See Contributing for how to add a new language.
Looking for ways to contribute? docs/ROADMAP.md lists feature ideas that don't have anyone working on them yet — Kindle/Kobo sync, SSO, an admin panel, library sharing for families, and more.
Contributions are welcome! Please read CONTRIBUTING.md for how to set up your environment, coding conventions, and how to submit a pull request. All participants are expected to follow the Code of Conduct.
Please do not open a public issue for security vulnerabilities. See SECURITY.md for how to report them responsibly.
This project is licensed under the MIT License.
books reading organization tracking lending library collection management open-source inventory personal-library cataloging
167 commits
TypeScript
62.9%
Vue
32.0%
HTML
3.7%
Vaultisse helps you organize all your books — whether they’re at home, school, or work. Easily keep track of where each book is, which ones you’ve read, the ones you’ve lent out, and the ones still waiting to be read. Never lose track of your library again!
2
stars
167
commits
TypeScript
primary language
Sep 11, 2026
updated
The easiest way to track, organize, and share all your books.
Vaultisse is an open-source web application for managing physical book collections — at home, in a school library, or in a small lending library. It tracks where each book lives, who currently has it on loan, and its full catalog metadata (author, category, language, format, cover image), and it can look books up automatically by ISBN.
demo@vaultisse.com / VaultisseDemo!2026

/docs help pages, rendered from Markdown, in the same languagesVaultisse is a classic SPA + REST API monorepo: a Vue 3 single-page app talks to an Express/PostgreSQL backend over a JSON API. In production the backend also serves the built frontend, so the whole app runs as a single Node.js process behind one port.
┌─────────────────────────┐ HTTPS / cookies ┌──────────────────────────┐
│ client/ (Vue 3 SPA) │ ─────────────────────────────▶ │ server/ (Express API) │
│ Vuetify UI components │ ◀───────────────────────────── │ JWT auth, business │
│ built with Vite │ JSON over /api/rest │ logic, PostgreSQL │
└─────────────────────────┘ └────────────┬─────────────┘
│
▼
┌────────────────────┐
│ PostgreSQL DB │
└────────────────────┘
Located in client/. A Vue 3 + Vuetify 3 single-page application built with
Vite.
src/views/ – one folder per feature area (book, authors, categories, customers,
locations, dashboard, search, settings, docs, legal). Each contains the page-level
Vue components for that feature.src/controller/ – view controllers that hold page state/logic and call
services, keeping .vue files focused on markup (BaseController.ts is the shared
base class).src/service/ – one service per resource (book, author, categories, customers,
locations, dashboard, search, user). Services wrap the HTTP calls to the backend
API (ApplicationService.ts is the shared Axios wrapper, configured in
src/plugins/axiosInstance.ts).src/model/ – TypeScript classes/interfaces mirroring the domain entities
(book, author, category, customer, location, format, language, user).src/router/ – Vue Router setup; each feature has its own route file under
src/router/routes/.src/plugins/i18n/ – Vue I18n configuration and the generated label map used
for translations (labels themselves are stored in the database — see
Internationalization).src/components/ – shared/reusable UI components (dialogs, tables, pickers,
the barcode/ISBN scanner, label printing, etc.).The client is served under the /app base path (see vite.config.ts and
router/Router.ts) and never talks to the database directly — everything goes
through the REST API at /api/rest/*.
Located in server/. A Node.js + Express + TypeScript REST API backed by
PostgreSQL.
src/index.ts – process entry point; boots the singleton AppService.src/AppService.ts – the application core. Owns the Express app, the
PostgreSQL connection pool (pg.Pool), configuration read from environment
variables, the logger, and helpers for JWT sessions and password hashing
(bcrypt). It also wires up global middleware: helmet (secure headers),
express-rate-limit (brute-force/DoS mitigation), cors (restricted to the
configured frontend origin), and cookie/body parsing.src/routes/ – one Express router per resource, registered in Routes.ts
under the /api/rest prefix: AppRoute, BooksRoute, LocationRoute,
CustomerRoute, AuthorRoute, CategoriesRoute, UserRoute, DashboardRoute.
AuthRoute is mounted separately at the root (/) and handles login, register,
logout, and serving the built SPA in production.src/middlewares/AuthMiddleware.ts – requireAuth/requireAuthPage
guards. Verify the JWT stored in the token cookie, check the user still
exists and isn't disabled, check the session hasn't been individually
revoked, and silently refresh the cookie when it's close to expiry — see
docs/AUTHENTICATION.md. In development, setting
ALLOW_DEV_AUTH=true bypasses login with a fake session — never enable
this in production.src/types/ – shared TypeScript interfaces (book/stock shapes, search
filters, app error types).src/utils/Logger.ts – lightweight file logger (LOGGER_PATH env var).src/assets/ – static assets shipped with the API: the standalone
login.html/register.html pages, background image, and (in production) the
built client bundle served from assets/app.Authentication is a JWT in an httpOnly cookie, but not purely stateless:
requireAuth also checks a per-user token_version counter (bumped on
password change, invalidating every device at once) and a per-login
user_sessions row (so "log out this device" in Settings can revoke just
one), on every protected request. Passwords are hashed with bcrypt (12 salt
rounds) and never stored or logged in plaintext. See
docs/AUTHENTICATION.md for the full session/revocation model.
npm run dev in client/) and
proxies any request to /api/rest/* to the API (http://localhost:<API_PORT> by
default, see vite.config.ts). The API runs separately via npm run dev in
server/ (nodemon + ts-node).AuthRoute.ts, which serves index.html for /app and
/app/*, guarded by requireAuth) — there is a single process and a single port.For deeper dives into specific parts of the codebase (not to be confused with
the in-app /docs help pages, which are end-user-facing), see
docs/.
vaultisse/
├── client/ # Vue 3 + Vuetify frontend (see client/README.md)
│ └── src/
│ ├── views/ # Page components, grouped by feature
│ ├── controller/ # Page controllers (state + logic)
│ ├── service/ # API clients, one per resource
│ ├── model/ # Domain TypeScript models
│ ├── router/ # Vue Router configuration
│ ├── components/ # Shared UI components
│ └── plugins/ # i18n, Vuetify, Axios setup
├── server/ # Express + TypeScript REST API
│ └── src/
│ ├── routes/ # One Express router per resource
│ ├── middlewares/ # requireAuth (JWT) middleware
│ ├── types/ # Shared TypeScript types
│ ├── utils/ # Logger, etc.
│ └── assets/ # Static login/register pages, prod client bundle
├── assets/
│ ├── db/ # SQL schema (databaseSchema.sql) + upgrade/ (dated upgrade files)
│ └── pm2/ # Sample PM2 ecosystem config for production
├── build.sh # Builds client + server into ./dist and zips it
└── LICENSE
GOOGLE_BOOKS_API_KEY isn't set)git clone https://github.com/AlbertAmat/vaultisse.git
cd vaultisse
Create a PostgreSQL database and load the schema — databaseSchema.sql is always
kept up to date, so this is everything a new install needs:
createdb paperbooks
psql -d paperbooks -f assets/db/databaseSchema.sql
There is no admin UI for the very first user — after loading the schema, either insert a row into
usersdirectly, or start the server withALLOW_DEV_AUTH=trueand use the/registerpage, then turnALLOW_DEV_AUTHback off.
The version-named files in assets/db/upgrade/ (1.0.0/1.sql, 1.0.0/2.sql,
...) are not for new installs — they're incremental upgrades for a
database that's already running an older version of the schema (see
assets/db/upgrade/README.md for the full
convention). Each one's header comment says what it does and confirms new
installs should skip it. If you're upgrading an existing instance instead of
setting one up fresh, apply every version's file(s) newer than whatever
version you're currently on, up through the version you're installing, in
order:
psql -d paperbooks -f assets/db/upgrade/1.0.0/1.sql # example: apply the first v1.0.0 upgrade file
Create server/.env (this file is git-ignored) with:
API_PORT=3000
DB_HOST=localhost
DB_PORT=5432
DB_NAME=paperbooks
DB_USER=your_db_user
DB_PASSWORD=your_db_password
GOOGLE_BOOKS_API_KEY= # optional, see Prerequisites
LOGGER_PATH=./logs.log
FRONT_END_URL=http://localhost:5173
JWT_SECRET=replace_with_a_long_random_string
SESSION_TIME=3600000 # milliseconds
ALLOW_DEV_AUTH=false # NEVER true in production
Generate a strong JWT_SECRET, for example: openssl rand -hex 32.
cd server
npm install
npm run dev # starts the API with nodemon on API_PORT
cd client
npm install
npm run dev # starts Vite; proxies /api/rest to the server
Open the URL Vite prints (typically http://localhost:5173/app) in your browser.
From the repository root:
./build.sh
This installs dependencies, builds the client (Vite) and server (tsc), assembles
everything under ./dist (dist/client, dist/server), copies server assets, and
produces a deployable dist.zip. The server serves the built client itself, so a
single Node.js process is all you need to run in production.
A sample PM2 config is provided at
assets/pm2/ecosystem.config.js.sample. Copy it, fill in your production values
(database credentials, JWT_SECRET, FRONT_END_URL, etc.), and run:
cp assets/pm2/ecosystem.config.js.sample ecosystem.config.js
# edit ecosystem.config.js with your production values
pm2 start ecosystem.config.js --env production
Every push of a vX.Y.Z tag builds a production image and publishes it to GitHub
Container Registry at ghcr.io/albertamat/vaultisse (see
Releasing a new version). The image bundles the compiled
server and the built client into one process, same as the PM2 deployment above — there
is no separate frontend container.
To run it on a server with Docker installed:
curl -O https://raw.githubusercontent.com/AlbertAmat/vaultisse/main/docker-compose.yml
curl -O https://raw.githubusercontent.com/AlbertAmat/vaultisse/main/.env.example
cp .env.example .env
# edit .env: set JWT_SECRET, DB_PASSWORD, FRONT_END_URL, etc.
docker compose up -d
This starts two containers: db (PostgreSQL, seeded from
assets/db/databaseSchema.sql on first run) and app (the image above). Pin APP_TAG
in .env to a specific released version rather than latest if you want upgrades to
be a deliberate step.
A few things worth knowing before pointing this at a real server:
ALLOW_DEV_AUTH must stay false. It bypasses login with a fake session and
exists only for local development.DB_HOST/DB_PORT in .env are ignored for the app container — it always
connects to the db service on the compose network. They only configure the db
container itself./app/logs inside the container (LOGGER_PATH), persisted
via the app-logs volume.docker build -t vaultisse .To build a multi-arch image or push to a different registry, adjust
.github/workflows/docker-release.yml.
For step-by-step instructions covering local-only self-hosting, production behind your own reverse proxy/DNS, production behind Cloudflare Tunnel, and a one-click Unraid template (Docker tab → Add Container, no Compose needed), see docs/DEPLOYMENT.md.
The repository root has a package.json that exists solely to anchor the release
version (the client and server aren't published packages and keep their own internal
version fields). To cut a release:
npm version patch # or: minor / major
git push --follow-tags
npm version bumps package.json, commits it, and creates a vX.Y.Z git tag.
Pushing that tag triggers .github/workflows/docker-release.yml, which builds the
Docker image, pushes ghcr.io/albertamat/vaultisse:X.Y.Z and :latest, and creates
a matching GitHub Release with auto-generated notes.
The running app version is available at GET /api/rest/app/version (unauthenticated),
useful for confirming what's actually deployed.
The first time you release, open the new package under Packages on the GitHub repo
and set its visibility (packages default to private) so docker pull works without
authentication on your server.
The UI currently ships in English, Spanish, Catalan, and Italian. Labels are stored
in the database (app_languages / app_labels tables in databaseSchema.sql) and
loaded into the client's Vue I18n instance — this keeps translations editable
without a redeploy. The /docs help pages are Markdown files rendered per-language
in the client. See Contributing for how to add a new language.
Looking for ways to contribute? docs/ROADMAP.md lists feature ideas that don't have anyone working on them yet — Kindle/Kobo sync, SSO, an admin panel, library sharing for families, and more.
Contributions are welcome! Please read CONTRIBUTING.md for how to set up your environment, coding conventions, and how to submit a pull request. All participants are expected to follow the Code of Conduct.
Please do not open a public issue for security vulnerabilities. See SECURITY.md for how to report them responsibly.
This project is licensed under the MIT License.
books reading organization tracking lending library collection management open-source inventory personal-library cataloging
167 commits
TypeScript
62.9%
Vue
32.0%
HTML
3.7%