19 KiB
ByRachel — Clothing Business Management
A full-featured web application for managing a small clothing business. Built with Next.js, optimized for deployment on a single VPS with Docker.
Features
- Product Catalog — Products with variants (size/color), images, traffic light inventory system
- Sales Management — Full sales workflow: create, confirm, deliver, cancel, returns
- Purchase Orders — Supplier purchases with receive/confirm/cancel workflow
- Inventory — Movement-based stock tracking, restock suggestions, adjustments
- Customers — Customer management with tags and sale history
- Suppliers — Supplier management with product associations
- Categories — Hierarchical categories with color coding
- Analytics — Sales trends, revenue/COGS/profit KPIs, best sellers, seasonality, cost evolution, inventory health
- Reports — PDF generation for sales, purchases, inventory, customers, brand, analytics
- Public Catalog — Customer-facing product catalog at
/catalogo - Settings — Brand configuration, pricing rules, password management, TOTP 2FA
- Audit Logging — All critical operations are logged with before/after data
Tech Stack
| Layer | Technology |
|---|---|
| Framework | Next.js 16 (App Router) |
| Language | TypeScript (strict mode) |
| Styling | Tailwind CSS 4 |
| UI Components | shadcn/ui (Radix UI primitives) |
| Database | SQLite (better-sqlite3) |
| ORM | Drizzle ORM |
| Authentication | JWT (jose) + TOTP (otplib) + Argon2 password hashing |
| Charts | Recharts |
| PDF Generation | pdf-lib (client-side) |
| Image Processing | Sharp |
| Testing | Vitest |
| Drag & Drop | @dnd-kit |
| Validation | Zod |
| Container | Docker (multi-stage build) |
Prerequisites
- Node.js 22+ and npm (for development)
- Docker & Docker Compose (for container deployment)
- A domain name (for production HTTPS, optional for local use)
Quick Start (Docker)
The fastest way to get ByRachel running:
# 1. Clone the repository
git clone <your-repo-url> byrachel
cd byrachel
# 2. Create environment file
cp .env.example .env
# Edit .env and change JWT_SECRET to a random secure value:
# openssl rand -base64 32
# 3. Start the application
docker compose up -d
# 4. Open in browser
# http://localhost:3000
# You'll be redirected to /setup to create your admin account
Development Setup
# 1. Clone and install
git clone <your-repo-url> byrachel
cd byrachel
npm install
# 2. Set up environment
cp .env.example .env
# 3. Initialize the database
npm run db:push
# 4. (Optional) Seed sample data
npm run seed
# 5. Start development server
npm run dev
# Open http://localhost:3000
Development Commands
| Command | Description |
|---|---|
npm run dev |
Start development server with hot reload |
npm run build |
Create production build |
npm run start |
Start production server |
npm run test |
Run all tests |
npm run test:watch |
Run tests in watch mode |
npm run test:coverage |
Run tests with coverage report |
npm run db:push |
Push schema changes to database |
npm run db:generate |
Generate migration files |
npm run db:migrate |
Run pending migrations |
npm run db:studio |
Open Drizzle Studio (database GUI) |
npm run seed |
Seed database with sample data |
Environment Variables
| Variable | Description | Default | Required |
|---|---|---|---|
DATABASE_URL |
SQLite database file path. Dev: ./data/db.sqlite, Docker: /app/data/db.sqlite |
./data/db.sqlite |
Yes |
JWT_SECRET |
Secret key for JWT signing. MUST be changed in production. Generate with: openssl rand -base64 32 |
change-me-to-a-256-bit-secret |
Yes |
UPLOAD_DIR |
Directory for uploaded images. Dev: ./public/uploads, Docker: /app/public/uploads |
./public/uploads |
Yes |
STORAGE_ADAPTER |
Storage backend. Currently only local (filesystem) is supported |
local |
Yes |
NODE_ENV |
Node environment: development or production |
production |
Yes |
NEXT_TELEMETRY_DISABLED |
Disable Next.js telemetry. Set to 1 to disable |
1 |
No |
API Routes Reference
All API routes are under /api/. Admin routes require a valid JWT cookie (except /api/health and /api/auth/*).
Authentication
| Method | Route | Description |
|---|---|---|
| POST | /api/auth/setup |
Initial admin account creation (first run only) |
| POST | /api/auth/login |
Login with email/password |
| POST | /api/auth/verify-totp |
Verify TOTP code |
| GET | /api/auth/me |
Get current user info |
| POST | /api/auth/logout |
Logout (clear session) |
| PUT | /api/auth/change-password |
Change password |
| POST | /api/auth/regenerate-recovery-codes |
Generate new recovery codes |
Products
| Method | Route | Description |
|---|---|---|
| GET/POST | /api/products |
List / Create products |
| GET/PUT/DELETE | /api/products/[id] |
Get / Update / Delete product |
| POST | /api/products/[id]/duplicate |
Duplicate a product |
| GET/POST | /api/products/[id]/images |
List / Upload product images |
| DELETE | /api/products/[id]/images/[imageId] |
Delete product image |
| PUT | /api/products/[id]/images/reorder |
Reorder product images |
| GET/POST | /api/products/[id]/variants |
List / Create variants |
| PUT/DELETE | /api/products/[id]/variants/[variantId] |
Update / Delete variant |
| GET | /api/products/[id]/variants/[variantId]/movements |
Get variant movements |
Categories
| Method | Route | Description |
|---|---|---|
| GET/POST | /api/categories |
List / Create categories |
| PUT/DELETE | /api/categories/[id] |
Update / Delete category |
Sales
| Method | Route | Description |
|---|---|---|
| GET/POST | /api/sales |
List / Create sales |
| GET/PUT/DELETE | /api/sales/[id] |
Get / Update / Delete sale |
| POST | /api/sales/[id]/confirm |
Confirm sale |
| POST | /api/sales/[id]/deliver |
Mark sale as delivered |
| POST | /api/sales/[id]/cancel |
Cancel sale |
| POST | /api/sales/[id]/return |
Process sale return |
Purchases
| Method | Route | Description |
|---|---|---|
| GET/POST | /api/purchases |
List / Create purchases |
| GET/PUT/DELETE | /api/purchases/[id] |
Get / Update / Delete purchase |
| POST | /api/purchases/[id]/receive |
Receive purchase items |
| POST | /api/purchases/[id]/confirm |
Confirm purchase |
| POST | /api/purchases/[id]/cancel |
Cancel purchase |
Inventory
| Method | Route | Description |
|---|---|---|
| GET | /api/inventory |
Get inventory status |
| POST | /api/inventory/restock |
Restock variants |
| POST | /api/inventory/adjust |
Manual stock adjustment |
| GET | /api/inventory/movements |
List stock movements |
Customers
| Method | Route | Description |
|---|---|---|
| GET/POST | /api/customers |
List / Create customers |
| GET/PUT/DELETE | /api/customers/[id] |
Get / Update / Delete customer |
| GET/POST | /api/customers/[id]/tags |
List / Add customer tags |
| DELETE | /api/customers/[id]/tags/[tagId] |
Remove customer tag |
| GET | /api/customer-tags |
List all customer tags |
Suppliers
| Method | Route | Description |
|---|---|---|
| GET/POST | /api/suppliers |
List / Create suppliers |
| GET/PUT/DELETE | /api/suppliers/[id] |
Get / Update / Delete supplier |
| GET | /api/suppliers/[id]/products |
Get supplier's products |
Analytics
| Method | Route | Description |
|---|---|---|
| GET | /api/analytics/summary |
Sales summary KPIs |
| GET | /api/analytics/sales-trend |
Revenue/units over time |
| GET | /api/analytics/categories |
Sales by category |
| GET | /api/analytics/products |
Product performance |
| GET | /api/analytics/customers |
Customer analytics |
| GET | /api/analytics/cost-evolution |
Cost price evolution |
| GET | /api/analytics/inventory-health |
Inventory health metrics |
| GET | /api/analytics/seasonality |
Seasonal sales patterns |
Reports (PDF Generation)
| Method | Route | Description |
|---|---|---|
| GET | /api/reports/sales |
Sales report data |
| GET | /api/reports/sales/[id] |
Single sale report |
| GET | /api/reports/sales/profits |
Profit report |
| GET | /api/reports/purchases |
Purchases report data |
| GET | /api/reports/purchases/[id] |
Single purchase report |
| GET | /api/reports/inventory |
Inventory report |
| GET | /api/reports/customers |
Customers report |
| GET | /api/reports/customers/[id] |
Single customer report |
| GET | /api/reports/brand |
Brand/catalog report |
| GET | /api/reports/analytics/best-sellers |
Best sellers report |
| GET | /api/reports/analytics/low-rotation |
Low rotation report |
Settings
| Method | Route | Description |
|---|---|---|
| GET/PUT | /api/settings/brand |
Get / Update brand settings |
| GET/PUT | /api/settings/pricing |
Get / Update pricing settings |
Admin
| Method | Route | Description |
|---|---|---|
| GET | /api/admin/backup |
Download full backup (tar.gz) |
Other
| Method | Route | Description |
|---|---|---|
| GET | /api/health |
Health check (public, no auth) |
| POST | /api/upload |
Upload image file |
Backup & Restore
Automated Backup (Admin Endpoint)
Admin users can download a full backup from the app:
GET /api/admin/backup
This returns a tar.gz archive containing:
data/— SQLite database files (main DB, WAL, SHM)public/uploads/— All uploaded images
Requirements: Must be authenticated as admin. Download over HTTPS only.
Manual SQLite Backup
SQLite with WAL mode requires a safe backup procedure to avoid corruption:
# Option 1: Using SQLite .backup command (safest)
docker compose exec app sh -c 'sqlite3 /app/data/db.sqlite ".backup /app/data/backup.db"'
# Then copy backup.db from the container
# Option 2: Copy WAL-safe (stop writes first)
# 1. Temporarily stop the application
docker compose stop app
# 2. Copy the database files
docker compose cp app:/app/data/db.sqlite ./backup/db.sqlite
docker compose cp app:/app/data/db.sqlite-wal ./backup/db.sqlite-wal
docker compose cp app:/app/data/db.sqlite-shm ./backup/db.sqlite-shm
# 3. Restart the application
docker compose start app
# Option 3: Using docker volume backup
docker run --rm -v byrachel_db-data:/data -v $(pwd)/backup:/backup alpine \
tar czf /backup/byrachel-db-$(date +%Y%m%d).tar.gz -C /data .
Image Volume Backup
docker run --rm -v byrachel_uploads:/uploads -v $(pwd)/backup:/backup alpine \
tar czf /backup/byrachel-uploads-$(date +%Y%m%d).tar.gz -C /uploads .
Restore Procedure
# 1. Stop the application
docker compose stop app
# 2. Restore database
docker run --rm -v byrachel_db-data:/data -v $(pwd)/backup:/backup alpine \
sh -c 'rm -rf /data/* && tar xzf /backup/byrachel-db-YYYYMMDD.tar.gz -C /data'
# 3. Restore uploads
docker run --rm -v byrachel_uploads:/uploads -v $(pwd)/backup:/backup alpine \
sh -c 'rm -rf /uploads/* && tar xzf /backup/byrachel-uploads-YYYYMMDD.tar.gz -C /uploads'
# 4. Start the application
docker compose start app
# 5. Verify
docker compose ps # Check health status
curl http://localhost:3000/api/health # Should return {"status":"ok"}
Production Deployment
Docker Deployment
# 1. Clone on your server
git clone <your-repo-url> byrachel
cd byrachel
# 2. Configure environment
cp .env.example .env
nano .env
# Set JWT_SECRET to a random value: openssl rand -base64 32
# 3. Build and start
docker compose up -d --build
# 4. Verify
docker compose ps
docker compose logs -f app
Nginx Proxy Manager Setup
Nginx Proxy Manager is the recommended reverse proxy for HTTPS and domain routing.
1. Install Nginx Proxy Manager
# Add to your docker-compose.yml or run separately
docker run -d \
--name nginx-proxy-manager \
-p 80:80 -p 81:81 -p 443:443 \
-v ./npm-data:/data \
-v ./npm-le:/etc/letsencrypt \
jc21/nginx-proxy-manager:latest
2. Configure Proxy Host
- Open NPM admin:
http://your-server-ip:81 - Default login:
admin@example.com/changeme - Go to Hosts → Proxy Hosts → Add Proxy Host
- Configure:
- Domain Names:
yourdomain.com - Scheme:
http - Hostname:
byrachel-app(Docker container name) or your server's Docker IP - Port:
3000 - Common Settings:
- Enable:
Block Common Exploits - Enable:
Websockets Support
- Enable:
- SSL Tab:
- SSL Certificate: Request a new Let's Encrypt certificate
- Enable:
Force SSL - Enable:
HTTP/2 Support
- Domain Names:
3. Docker Network (recommended)
If both NPM and ByRachel are in the same Docker network:
# docker-compose.yml
services:
app:
# ... existing config ...
networks:
- proxy-net
networks:
proxy-net:
external: true
Then in NPM, use the container name byrachel-app as the hostname.
HTTPS Configuration
HTTPS is configured through Nginx Proxy Manager:
- Request Certificate: In the Proxy Host SSL tab, select "Request a new SSL Certificate"
- Let's Encrypt: NPM handles Let's Encrypt automatically
- Force SSL: Enable to redirect HTTP → HTTPS
- Auto-renewal: Certificates renew automatically
Domain Configuration
-
DNS Record: Create an A record pointing your domain to your server's IP
Type: A Name: yourdomain.com (or @) Value: YOUR_SERVER_IP TTL: 300 -
Wait for DNS propagation (usually minutes, up to 48h)
-
Configure in NPM: Add the domain in the Proxy Host settings (see above)
Auto-restart & Log Management
Auto-restart
The Docker Compose configuration includes restart: unless-stopped, which means:
- Container restarts automatically on crash
- Container starts on Docker daemon startup (server reboot)
- Container only stays stopped if you explicitly
docker compose stop
For additional server-level auto-start, create a systemd service:
# /etc/systemd/system/byrachel.service
[Unit]
Description=ByRachel Docker Compose
Requires=docker.service
After=docker.service
[Service]
Type=oneshot
RemainAfterExit=yes
WorkingDirectory=/path/to/byrachel
ExecStart=/usr/bin/docker compose up -d
ExecStop=/usr/bin/docker compose down
TimeoutStartSec=0
[Install]
WantedBy=multi-user.target
sudo systemctl enable byrachel.service
sudo systemctl start byrachel.service
Log Management
# View live logs
docker compose logs -f app
# View last 100 lines
docker compose logs --tail=100 app
# Configure log rotation (in docker-compose.yml, add to app service):
# logging:
# driver: json-file
# options:
# max-size: "10m"
# max-file: "3"
SQLite → PostgreSQL Migration Plan
ByRachel uses Drizzle ORM, which supports multiple database dialects. Migration to PostgreSQL is possible when needed:
Steps
-
Install PostgreSQL driver:
npm install pg npm install @types/pg -
Update Drizzle config (
drizzle.config.ts):export default defineConfig({ schema: './src/lib/db/schema.ts', out: './src/lib/db/migrations', dialect: 'postgresql', // Change from 'sqlite' dbCredentials: { host: process.env.PGHOST, port: Number(process.env.PGPORT), user: process.env.PGUSER, password: process.env.PGPASSWORD, database: process.env.PGDATABASE, }, }); -
Update database connection (
src/lib/db/index.ts):- Replace
drizzle(betterSqlite3(...))withdrizzle(pgPool)ordrizzle(nodePostgres(...))
- Replace
-
Update environment variables:
DATABASE_URL=postgresql://user:password@localhost:5432/byrachel -
Generate and run migrations:
npm run db:generate npm run db:migrate -
Data migration: Export data from SQLite and import into PostgreSQL using a migration script.
Considerations
- SQLite uses
integerprimary keys with auto-increment — PostgreSQL usesserialoridentity - SQLite
boolean(integer 0/1) maps directly to PostgreSQLboolean - SQLite
texttimestamps work in both, but PostgreSQL has nativetimestamptypes - Test thoroughly in a staging environment before migrating production data
First-Run Setup
1. Initial Admin Account
- Open the app in your browser (e.g.,
http://localhost:3000) - You'll be redirected to
/setup(only available when no users exist) - Fill in:
- Email: Admin email address
- Password: Strong password (min 8 characters)
- Name: Admin display name (optional)
- Click "Create Account"
2. TOTP Configuration
After creating the account, TOTP (Time-based One-Time Password) setup begins automatically:
- A QR code is displayed on screen
- Open your authenticator app (Google Authenticator, Authy, 1Password, etc.)
- Scan the QR code (or enter the secret key manually)
- Enter the 6-digit code from your authenticator app to verify
- Save your recovery codes in a secure location — these are your backup if you lose access to your authenticator
3. Brand Configuration
After logging in for the first time:
- Navigate to Settings (gear icon in sidebar)
- Configure your brand:
- Store Name: Your business name
- Logo: Upload your logo image
- Tagline: Short description
- Colors: Primary and secondary brand colors
- Contact Info: Phone, email, WhatsApp
- Address: Business address
- Footer Text: Custom footer for catalog pages
- Click "Save Settings"
- To publish your public catalog, check "Publish Catalog" in settings
Known Limitations
- SQLite write concurrency: SQLite handles concurrent reads well but writes are serialized. For a single-admin small business, this is not an issue. If you need multi-writer support, migrate to PostgreSQL (see migration plan above).
- No built-in email: The app does not send emails. Password recovery uses local recovery codes instead of email-based reset.
- Single admin: The app supports admin and operator roles, but the initial setup creates only one admin user. Additional users must be created manually via the database.
- No automated backups: The backup endpoint provides on-demand backups. Set up cron jobs or external tools for scheduled backups.
- Local file storage only: Images are stored on the local filesystem. For multi-instance deployments, a shared storage solution (S3, etc.) would be needed.
- No real-time updates: The app uses standard HTTP requests. Changes made in one browser tab are not reflected in other tabs until refresh.
- Catalog is read-only for customers: The public catalog at
/catalogois for browsing only. Customers cannot place orders through the app.
License
Private — All rights reserved.