A small web app to store, tag, search, preview and download your web templates.
For detailed Vietnamese documentation, see TAI_LIEU.md.
- Upload a template as a
.ziparchive with an optional thumbnail image - Add a title, description and comma-separated tags
- Edit a template later: change title/description/tags or swap the zip/thumbnail
- Search by title/description, filter by tag, sort (newest/oldest/title/downloads)
- Pagination for large collections
- Detail view with full description, large thumbnail and the file list
- View, download or copy the URL of individual files inside a template
- Live preview: the archive is extracted and its entry
index.htmlis served - Download the original
.zip(tracked as a download count) - Delete a template (removes DB row + files)
- Optional token auth: protect writes with
ADMIN_TOKEN(reads stay public) - Backup: export everything to one
.zipand import it back (merge or replace) - Docker support (Dockerfile + docker-compose with persistent volumes)
- SQLite database via
better-sqlite3(no external DB server needed)
- Node.js + Express (ES modules)
better-sqlite3for storagemulterfor file uploadsadm-zipfor archive extraction (with zip-slip protection)- Vanilla HTML/CSS/JS frontend
npm install
npm run dev # or: npm startThen open http://localhost:4000
Set a custom port with the PORT env var (see .env.example).
To keep the server running without a visible terminal window:
- Start: double-click
scripts/start-template-vault.vbs(launches hidden). Logs are written tologs/server.out.logandlogs/server.err.log. - Stop: run
scripts/stop-template-vault.cmd(kills whatever listens on port 4000). - Foreground (see console): run
scripts/run-template-vault.cmddirectly.
To start automatically on login, put a shortcut to start-template-vault.vbs in your
Startup folder (press Win+R, type shell:startup, drop the shortcut there).
By default the app is fully open, which is fine for local use. To require a token
for all write operations (create/edit/delete), set ADMIN_TOKEN in .env:
ADMIN_TOKEN=your-long-random-secret
When set, the API expects Authorization: Bearer your-long-random-secret on
POST, PATCH and DELETE requests. Reading, previewing and downloading stay
public. In the UI a "🔒 Đăng nhập" button appears — enter the token once and it is
stored in the browser. GET /api/auth reports whether a token is required.
- Export: the "⬇ Export" button (or
GET /api/backup/export) downloads a single.zipcontainingmanifest.jsonplus every archive and thumbnail. - Import: the "⬆ Import" button (or
POST /api/backup/import) restores from that zip. Choose replace (wipe existing first,?replace=1) or merge (add on top). Previews are re-extracted automatically from the restored archives.
docker compose up --buildThe app is served on http://localhost:4000. data/ and storage/ are bind-mounted
so your templates persist across rebuilds. Set ADMIN_TOKEN in .env to require auth.
npm testTests use node --test and run against an in-memory-ish throwaway data directory
(test/setup.js redirects DATA_DIR/STORAGE_DIR to a temp folder), so they never
touch your real data/ or storage/.
src/
index.js Express server + static hosting (createApp factory)
db.js SQLite schema/connection + migrations
store.js Data access (templates, tags, search, backup)
paths.js Storage directory setup
archive.js Zip extraction, preview file helpers (zip-slip safe)
middleware/
auth.js Optional token auth
routes/
templates.js REST API + upload/extract/download
backup.js Export / import endpoints
public/
index.html Gallery UI
style.css
app.js
scripts/ Windows background start/stop helpers
test/
setup.js Redirects storage to a temp dir for tests
templates.test.js API tests (node --test)
data/ SQLite file (gitignored)
storage/ uploads / thumbnails / extracted previews (gitignored)
| Method | Path | Description |
|---|---|---|
| GET | /api/templates?q=&tag=&sort=&page=&pageSize= |
Paginated list. sort: newest | oldest | title | downloads |
| GET | /api/templates/:id |
Get one template |
| POST | /api/templates |
Create (multipart form) |
| PATCH | /api/templates/:id |
Update fields and/or replace files |
| GET | /api/templates/:id/files |
List files inside the archive |
| GET | /api/templates/:id/file?path=&download=1 |
View or download a single file |
| GET | /api/templates/:id/download |
Download the original archive (bumps download count) |
| DELETE | /api/templates/:id |
Delete a template |
| GET | /api/tags |
List tags with counts |
| GET | /api/auth |
Whether a token is required for writes |
| GET | /api/backup/export |
Download a full backup zip |
| POST | /api/backup/import |
Restore from a backup zip (?replace=1 to wipe first) |
{ "items": [ ... ], "total": 4, "page": 1, "pageSize": 12, "pages": 1, "sort": "newest" }title(required)descriptiontags— comma separated, e.g.landing, dark, tailwindarchive— a.zipfile (optional)thumbnail— an image file (optional)
