docs: add developer onboarding guide and replace boilerplate READMEs

This commit is contained in:
neon
2026-02-07 10:28:00 -06:00
parent 9923d3871f
commit e232c44d88
5 changed files with 425 additions and 122 deletions
+332
View File
@@ -0,0 +1,332 @@
# ProjectX
A book and library management app with Kobo device support. Multi-user, self-hostable.
**Tech stack:** NestJS 11 (Fastify) + Vue 3 + PostgreSQL + Drizzle ORM
---
## Quick Start
### Prerequisites
| Tool | Version | Install |
|---------|---------|--------------------------------------------------------------|
| Node.js | >= 24 | [nodejs.org](https://nodejs.org) |
| pnpm | >= 9 | `corepack enable && corepack prepare pnpm@latest --activate` |
| Docker | latest | [docker.com](https://www.docker.com/products/docker-desktop) |
### Setup
```bash
git clone <repo-url> projectx
cd projectx
docker compose up -d --wait # start Postgres
cp server/.env.example server/.env # create env config (edit if needed)
pnpm install # install all dependencies
cd server && pnpm db:migrate && cd .. # apply database migrations
```
Docker is only used for PostgreSQL. The server and client run directly on your machine.
### Start developing
```bash
pnpm dev
```
This starts the NestJS server and Vue client concurrently:
- **Client:** http://localhost:5173
- **API:** http://localhost:3000/api
If you prefer separate terminals (cleaner logs):
```bash
# Terminal 1
cd server && pnpm start:dev
# Terminal 2
cd client && pnpm dev
```
Both must be running. The client proxies all `/api` and `/socket.io` requests to the server, so API calls will fail if only the client is running.
### First steps after setup
1. **Log in** - a default admin account is created automatically:
- Username: `admin`
- Password: `admin`
2. **Change password** - you'll be prompted to set a new password on first login. Use `Admin123!` for local dev.
3. **Create a library** - open Settings (gear icon) > Libraries > Create Library. Add a folder path pointing to some books on your machine, e.g. any directory containing `.epub`, `.pdf`, or `.cbz` files.
4. **Scan** - the library scans automatically after creation. You should see books appear on the home page once the scan completes.
> **Where does data go?** `BOOKS_PATH` in `server/.env` defaults to `../local/data` (resolves to `<project-root>/local/data`). This is the app's data directory where extracted cover images and thumbnails are stored (`local/data/covers/`). It is **not** where your book files live - those paths are configured per-library in the UI. The `local/` folder is gitignored.
---
## Project Structure
```
projectx/
├── client/ Vue 3 frontend (Vite + Tailwind CSS v4)
├── server/ NestJS 11 backend (Fastify + Drizzle ORM)
├── packages/
│ └── types/ Shared TypeScript types (@projectx/types)
├── docker/
│ └── postgres/ Postgres init scripts (extensions)
├── local/ Local dev data (covers, docs) - gitignored
├── docker-compose.yml Dev: runs Postgres only (app runs natively)
├── Dockerfile Production multi-stage build
└── Dockerfile.dev Runs entire stack in Docker (not used for normal dev)
```
### Monorepo layout
This is a **pnpm workspace**. The three packages are:
- **`server/`** - NestJS API. Modules live in `src/modules/`, one folder per feature. Database schema is in `src/db/schema/`.
- **`client/`** - Vue 3 SPA. Feature code lives in `src/features/`, shared components in `src/components/ui/`.
- **`packages/types/`** - Shared types imported as `@projectx/types` by both server and client. Add any type that crosses the API boundary here.
---
## Common Commands
All commands run from the **repo root** unless noted otherwise.
### Development
| Command | Description |
|-------------------------------|--------------------------------------|
| `pnpm dev` | Start server + client (concurrently) |
| `cd server && pnpm start:dev` | Server only (watch mode) |
| `cd client && pnpm dev` | Client only (Vite dev server) |
### Testing
| Command | Description |
|----------------------------------------------------|---------------------------|
| `cd server && pnpm test` | Server unit tests |
| `cd server && pnpm test -- --testPathPattern=scan` | Run matching server tests |
| `cd client && pnpm test:unit` | Client unit tests |
| `cd client && pnpm test:unit -- BookCover` | Run matching client tests |
| `cd server && pnpm test:e2e` | Server E2E tests |
### Database
| Command | Description |
|----------------------------------------|------------------------------------------|
| `cd server && pnpm db:migrate` | Apply pending migrations |
| `cd server && pnpm db:generate <name>` | Generate a migration from schema changes |
| `cd server && pnpm db:studio` | Open Drizzle Studio (DB browser) |
### Linting & formatting
| Command | Description |
|---------------------------------------|--------------------|
| `cd server && npx prettier --write .` | Format server code |
| `cd client && npx prettier --write .` | Format client code |
| `cd server && pnpm lint` | Lint server |
| `cd client && pnpm lint` | Lint client |
---
## Development Workflow
Once `pnpm dev` is running, here's how the feedback loop works for each type of change:
### Frontend (`client/`)
Save a file → Vite HMR hot-swaps the module → browser updates instantly. No restart needed.
### Backend (`server/`)
Save a file → NestJS watch mode (SWC) recompiles and restarts the server automatically. Takes about a second.
### Shared types (`packages/types/`)
The types package points directly at `.ts` source files. Edit a type → both client and server pick it up on their next rebuild cycle. No build step needed.
### Database schema
Schema changes are the one thing that isn't automatic. See [Database Workflow](#database-workflow) for the generate + migrate steps.
### Environment variables
The `start:dev` script reads `server/.env` once at startup. If you change a value in `.env`, you must restart `pnpm dev` for it to take effect.
### After `git pull`
If other people have pushed changes, you may need to sync dependencies and migrations:
```bash
pnpm install # pick up lockfile changes
cd server && pnpm db:migrate # apply any new migrations
```
### Adding a dependency
Always install into the correct workspace, not the root:
```bash
cd server && pnpm add <pkg> # backend dependency
cd client && pnpm add <pkg> # frontend dependency
```
### Adding a new feature
The fastest way to learn the patterns is to follow an existing module:
- **Backend:** Look at `server/src/modules/bookmark/` for a minimal example (controller, service, repository, DTO, module). Copy it, rename, and adapt.
- **Frontend:** Look at `client/src/features/collection/` for a full feature (composables for state/API, components for UI).
- **Shared types:** If the feature adds API request/response shapes, add them in `packages/types/src/` and import via `@projectx/types`.
---
## Environment Variables
Server environment is configured in `server/.env` (created from `.env.example` during setup).
| Variable | Default | Description |
|--------------------------|--------------------------------------------------------|------------------------------------------------------------|
| `DATABASE_URL` | `postgres://projectx:projectx@localhost:5432/projectx` | PostgreSQL connection string |
| `PORT` | `3000` | Server port |
| `NODE_ENV` | `development` | Environment mode |
| `JWT_SECRET` | `change-me-in-production` | JWT signing secret |
| `JWT_EXPIRES_IN` | `15m` | Access token lifetime |
| `JWT_REFRESH_EXPIRES_IN` | `7d` | Refresh token lifetime |
| `BOOKS_PATH` | `../local/data` | App data directory for cover images (not where books live) |
| `SMTP_*` | (empty) | Optional SMTP config for password-reset emails |
| `APP_URL` | `http://localhost:5173` | Client URL (used in emails) |
---
## Database Workflow
The database schema is defined in `server/src/db/schema/` using Drizzle ORM. Each domain has its own schema file (e.g. `books.ts`, `libraries.ts`, `auth.ts`).
**Making schema changes:**
1. Edit the relevant file in `server/src/db/schema/`
2. Generate a migration: `cd server && pnpm db:generate describe_your_change`
3. Apply it: `cd server && pnpm db:migrate`
Never hand-write migration SQL. Always generate from schema diffs.
### Resetting the database from scratch
```bash
# 1. Drop and recreate the public schema
docker exec $(docker ps --filter "name=postgres" --format "{{.Names}}" | head -1) \
psql -U projectx -d projectx -c "DROP SCHEMA public CASCADE; CREATE SCHEMA public;"
# 2. Clear Drizzle's migration tracking (it lives in a separate schema)
docker exec $(docker ps --filter "name=postgres" --format "{{.Names}}" | head -1) \
psql -U projectx -d projectx -c "DELETE FROM drizzle.__drizzle_migrations;"
# 3. Re-apply all migrations
cd server && pnpm db:migrate
# 4. (Optional) Wipe cover images
rm -rf local/data/covers
```
Step 2 is necessary because Drizzle tracks applied migrations in `drizzle.__drizzle_migrations`, which lives outside the `public` schema and survives the drop.
---
## Architecture Overview
```
┌──────────────┐ ┌──────────────────────┐ ┌────────────┐
│ Vue 3 SPA │──────>│ NestJS API (/api) │──────>│ PostgreSQL │
│ port 5173 │<──────│ port 3000 │<──────│ port 5432 │
└──────────────┘ └──────────────────────┘ └────────────┘
│ │
│ WebSocket │ Drizzle ORM
│ (socket.io) │ (schema in src/db/schema/)
└────────────────────────┘
```
- **Backend:** NestJS 11 on Fastify. Global prefix `/api`. Auth via JWT (access + refresh tokens in httpOnly cookies). RBAC with permissions system.
- **Frontend:** Vue 3 Composition API (`<script setup>`). Tailwind CSS v4 for styling. Feature-local state in composables, not global stores.
- **Database:** PostgreSQL 16 via Drizzle ORM. Migrations generated by Drizzle Kit.
- **Shared types:** `@projectx/types` package ensures API contracts stay in sync.
---
## Troubleshooting
### Port 5432 already in use
Another PostgreSQL instance is running. Either stop it or change the port mapping in `docker-compose.yml`:
```yaml
ports:
- "5433:5432" # use 5433 on host
```
Then update `DATABASE_URL` in `server/.env` to use port `5433`.
### Port 3000 or 5173 already in use
Kill the process occupying the port:
```bash
lsof -ti:3000 | xargs kill -9 # server port
lsof -ti:5173 | xargs kill -9 # client port
```
### `pnpm db:migrate` does nothing after a schema change
You need to generate a migration first:
```bash
cd server && pnpm db:generate describe_your_change
cd server && pnpm db:migrate
```
### `pnpm db:generate` fails with a connection error
Drizzle Kit auto-loads `server/.env` for the `DATABASE_URL`. If the file is missing or the URL is wrong, generation fails. Verify `server/.env` exists and that PostgreSQL is running (`docker compose ps`).
The fallback URL in `drizzle.config.ts` uses `postgres:5432` (the Docker service hostname), which only works inside Docker, not from the host machine.
### Docker container won't start
```bash
docker compose down
docker compose up -d --wait
```
If the volume is corrupted, wipe it (destroys all data):
```bash
docker compose down -v
docker compose up -d --wait
cd server && pnpm db:migrate
```
### `Cannot find module '@projectx/types'`
The shared types package needs to be built or the workspace link is broken:
```bash
pnpm install
```
### CORS errors in the browser
Make sure you're accessing the client at `http://localhost:5173` (not port 3000). The Vite dev server proxies API requests to the backend automatically.
---
## Further Reading
| Doc | What it covers |
|----------------------------------------|--------------------------------------------------------------|
| [`server/README.md`](server/README.md) | Backend module map, DB commands, NestJS conventions |
| [`client/README.md`](client/README.md) | Frontend project layout, IDE setup, Vue/Tailwind conventions |
| [`packages/types/`](packages/types/) | Shared type definitions between server and client |
+41 -39
View File
@@ -1,54 +1,56 @@
# client
# ProjectX - Client
This template should help get you started developing with Vue 3 in Vite.
Vue 3 SPA built with Vite and Tailwind CSS v4.
## Recommended IDE Setup
## Running
[VS Code](https://code.visualstudio.com/) + [Vue (Official)](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur).
From the repo root: `pnpm dev` (starts both server and client).
## Recommended Browser Setup
Client only: `pnpm dev` (from this directory). Runs at http://localhost:5173.
- Chromium-based browsers (Chrome, Edge, Brave, etc.):
- [Vue.js devtools](https://chromewebstore.google.com/detail/vuejs-devtools/nhdogjmejiglipccpnnnanhbledajbpd)
- [Turn on Custom Object Formatter in Chrome DevTools](http://bit.ly/object-formatters)
- Firefox:
- [Vue.js devtools](https://addons.mozilla.org/en-US/firefox/addon/vue-js-devtools/)
- [Turn on Custom Object Formatter in Firefox DevTools](https://fxdx.dev/firefox-devtools-custom-object-formatters/)
The Vite dev server proxies `/api` and `/socket.io` requests to the backend at `localhost:3000`.
## Type Support for `.vue` Imports in TS
## IDE setup
TypeScript cannot handle type information for `.vue` imports by default, so we replace the `tsc` CLI with `vue-tsc` for type checking. In editors, we need [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) to make the TypeScript language service aware of `.vue` types.
- **VS Code:** Install the [Vue (Official)](https://marketplace.visualstudio.com/items?itemName=Vue.volar) extension. Disable Vetur if installed.
- **WebStorm / IntelliJ:** Vue support is built in. Enable TypeScript service for `.vue` files.
- **Browser:** Install [Vue.js devtools](https://chromewebstore.google.com/detail/vuejs-devtools/nhdogjmejiglipccpnnnanhbledajbpd) and enable Custom Object Formatters in DevTools settings.
## Customize configuration
## Project layout
See [Vite Configuration Reference](https://vite.dev/config/).
## Project Setup
```sh
npm install
```
src/
├── features/ Feature modules (one folder per domain)
│ ├── auth/ Login, registration, password reset
│ ├── book/ Book detail, covers, metadata
│ ├── library/ Library management
│ ├── collection/ Book collections
│ ├── lens/ Saved search views
│ ├── scanner/ Scan progress UI
│ ├── reader/ Book reader (epub, pdf, cbz)
│ ├── settings/ User settings
│ └── admin/ Admin panel (users, roles, settings)
├── components/ Shared components (header, sidebar, icons)
│ └── ui/ Reusable UI primitives (button, dialog, etc.)
├── composables/ Global composables
├── views/ Route-level page components
├── router/ Vue Router config and guards
├── lib/ Utilities (api client, helpers)
├── stores/ Pinia stores (theme only - prefer composables)
└── assets/ CSS, fonts, static assets
```
### Compile and Hot-Reload for Development
## Testing
```sh
npm run dev
```bash
pnpm test:unit # run Vitest
```
### Type-Check, Compile and Minify for Production
## Key conventions
```sh
npm run build
```
### Run Unit Tests with [Vitest](https://vitest.dev/)
```sh
npm run test:unit
```
### Lint with [ESLint](https://eslint.org/)
```sh
npm run lint
```
- All components use `<script setup lang="ts">` (Composition API only).
- Feature state lives in composables (`features/<name>/composables/use*.ts`), not global stores.
- HTTP calls use the native `fetch` API via `src/lib/api.ts`.
- Styling with Tailwind CSS v4 utility classes. Theme tokens are CSS variables in `src/assets/main.css`.
- Icons from `lucide-vue-next` only.
- Shared types imported from `@projectx/types`.
+5 -1
View File
@@ -11,7 +11,11 @@ JWT_EXPIRES_IN=15m
JWT_REFRESH_EXPIRES_IN=7d
# Storage
BOOKS_PATH=/books
# App data directory for cover images and generated files.
# This path resolves relative to server/ (where the process runs).
# ../local/data points to <project-root>/local/data.
# In production/Docker this is typically an absolute path like /data.
BOOKS_PATH=../local/data
# OIDC (optional — configure via admin settings UI; these are just for reference)
# OIDC config is stored in the database (app_settings table, key: oidc_config)
+46 -81
View File
@@ -1,98 +1,63 @@
<p align="center">
<a href="http://nestjs.com/" target="blank"><img src="https://nestjs.com/img/logo-small.svg" width="120" alt="Nest Logo" /></a>
</p>
# ProjectX - Server
[circleci-image]: https://img.shields.io/circleci/build/github/nestjs/nest/master?token=abc123def456
[circleci-url]: https://circleci.com/gh/nestjs/nest
NestJS 11 backend running on Fastify.
<p align="center">A progressive <a href="http://nodejs.org" target="_blank">Node.js</a> framework for building efficient and scalable server-side applications.</p>
<p align="center">
<a href="https://www.npmjs.com/~nestjscore" target="_blank"><img src="https://img.shields.io/npm/v/@nestjs/core.svg" alt="NPM Version" /></a>
<a href="https://www.npmjs.com/~nestjscore" target="_blank"><img src="https://img.shields.io/npm/l/@nestjs/core.svg" alt="Package License" /></a>
<a href="https://www.npmjs.com/~nestjscore" target="_blank"><img src="https://img.shields.io/npm/dm/@nestjs/common.svg" alt="NPM Downloads" /></a>
<a href="https://circleci.com/gh/nestjs/nest" target="_blank"><img src="https://img.shields.io/circleci/build/github/nestjs/nest/master" alt="CircleCI" /></a>
<a href="https://discord.gg/G7Qnnhy" target="_blank"><img src="https://img.shields.io/badge/discord-online-brightgreen.svg" alt="Discord"/></a>
<a href="https://opencollective.com/nest#backer" target="_blank"><img src="https://opencollective.com/nest/backers/badge.svg" alt="Backers on Open Collective" /></a>
<a href="https://opencollective.com/nest#sponsor" target="_blank"><img src="https://opencollective.com/nest/sponsors/badge.svg" alt="Sponsors on Open Collective" /></a>
<a href="https://paypal.me/kamilmysliwiec" target="_blank"><img src="https://img.shields.io/badge/Donate-PayPal-ff3f59.svg" alt="Donate us"/></a>
<a href="https://opencollective.com/nest#sponsor" target="_blank"><img src="https://img.shields.io/badge/Support%20us-Open%20Collective-41B883.svg" alt="Support us"></a>
<a href="https://twitter.com/nestframework" target="_blank"><img src="https://img.shields.io/twitter/follow/nestframework.svg?style=social&label=Follow" alt="Follow us on Twitter"></a>
</p>
<!--[![Backers on Open Collective](https://opencollective.com/nest/backers/badge.svg)](https://opencollective.com/nest#backer)
[![Sponsors on Open Collective](https://opencollective.com/nest/sponsors/badge.svg)](https://opencollective.com/nest#sponsor)-->
## Running
## Description
From the repo root: `pnpm dev` (starts both server and client).
[Nest](https://github.com/nestjs/nest) framework TypeScript starter repository.
Server only: `pnpm start:dev` (from this directory).
## Project setup
The server loads environment variables from `.env` in this directory. See `.env.example` for all available options.
```bash
$ pnpm install
## Module structure
Each feature is a self-contained NestJS module under `src/modules/`:
```
src/modules/
├── auth/ Authentication (JWT, OIDC, password reset)
├── book/ Book CRUD, cover images, file serving
├── library/ Library management
├── scanner/ File system scanning, metadata extraction
├── collection/ User book collections
├── lens/ Saved search/filter views
├── user/ User management
├── role/ Roles and permissions (RBAC)
├── metadata/ Book metadata providers
├── kobo/ Kobo device sync
├── annotation/ Highlights and annotations
├── bookmark/ Reading position bookmarks
├── reader-preferences/ Per-user reader settings
├── cbz/ CBZ archive handling
├── path/ File path utilities
├── seed/ Database seeding
└── app-settings/ Global application settings
```
## Compile and run the project
## Database
Schema files live in `src/db/schema/`, one per domain. Migrations are generated by Drizzle Kit.
```bash
# development
$ pnpm run start
# watch mode
$ pnpm run start:dev
# production mode
$ pnpm run start:prod
pnpm db:generate add_some_column # generate migration from schema diff
pnpm db:migrate # apply pending migrations
pnpm db:studio # open Drizzle Studio (DB browser)
```
## Run tests
## Testing
```bash
# unit tests
$ pnpm run test
# e2e tests
$ pnpm run test:e2e
# test coverage
$ pnpm run test:cov
pnpm test # unit tests
pnpm test:watch # unit tests in watch mode
pnpm test:e2e # end-to-end tests
pnpm test:cov # unit tests with coverage
```
## Deployment
## Key conventions
When you're ready to deploy your NestJS application to production, there are some key steps you can take to ensure it runs as efficiently as possible. Check out the [deployment documentation](https://docs.nestjs.com/deployment) for more information.
If you are looking for a cloud-based platform to deploy your NestJS application, check out [Mau](https://mau.nestjs.com), our official platform for deploying NestJS applications on AWS. Mau makes deployment straightforward and fast, requiring just a few simple steps:
```bash
$ pnpm install -g @nestjs/mau
$ mau deploy
```
With Mau, you can deploy your application in just a few clicks, allowing you to focus on building features rather than managing infrastructure.
## Resources
Check out a few resources that may come in handy when working with NestJS:
- Visit the [NestJS Documentation](https://docs.nestjs.com) to learn more about the framework.
- For questions and support, please visit our [Discord channel](https://discord.gg/G7Qnnhy).
- To dive deeper and get more hands-on experience, check out our official video [courses](https://courses.nestjs.com/).
- Deploy your application to AWS with the help of [NestJS Mau](https://mau.nestjs.com) in just a few clicks.
- Visualize your application graph and interact with the NestJS application in real-time using [NestJS Devtools](https://devtools.nestjs.com).
- Need help with your project (part-time to full-time)? Check out our official [enterprise support](https://enterprise.nestjs.com).
- To stay in the loop and get updates, follow us on [X](https://x.com/nestframework) and [LinkedIn](https://linkedin.com/company/nestjs).
- Looking for a job, or have a job to offer? Check out our official [Jobs board](https://jobs.nestjs.com).
## Support
Nest is an MIT-licensed open source project. It can grow thanks to the sponsors and support by the amazing backers. If you'd like to join them, please [read more here](https://docs.nestjs.com/support).
## Stay in touch
- Author - [Kamil Myśliwiec](https://twitter.com/kammysliwiec)
- Website - [https://nestjs.com](https://nestjs.com/)
- Twitter - [@nestframework](https://twitter.com/nestframework)
## License
Nest is [MIT licensed](https://github.com/nestjs/nest/blob/master/LICENSE).
- Constructor injection with `@Inject(DB)` for the Drizzle instance.
- Config via `@nestjs/config` named configs (`appConfig`, `dbConfig`, `authConfig`, `storageConfig`, `mailerConfig`). Never read `process.env` directly in services.
- DTOs use `class-validator` decorators. Global `ValidationPipe` rejects unknown fields.
- Throw NestJS `HttpException` subclasses (`NotFoundException`, `ForbiddenException`, etc.) - never raw `Error`.
- All user-owned data is scoped by `userId`. Service methods check ownership.
+1 -1
View File
@@ -5,7 +5,7 @@
{
"idx": 0,
"version": "7",
"when": 1770522775823,
"when": 1770528090812,
"tag": "0000_add_rating_and_book_card_fields",
"breakpoints": true
}