SvelteKit + Skeleton v4 + Fastify + MongoDB, containerized with Docker.
The Web/ directory is the scaffold.
See Web/CLAUDE.md for the full project reference.
| Layer | Package | Version |
|---|---|---|
| Frontend | SvelteKit + Svelte | 2 / 5 |
| Components | Skeleton | v4 |
| CSS | Tailwind | v4 |
| API | Fastify | v5 |
| Database | MongoDB | 7 |
| Auth | @fastify/session + bcryptjs | — |
| Nodemailer (Ethereal dev / SMTP prod) | — | |
| Container | Docker Compose | — |
cd Web
cp .env.Web .env
# Edit .env — set SESSION_SECRET at minimum
cd frontend && npm install && cd ..
cd api && npm install && cd ..
docker compose -f docker-compose.yml -f docker-compose.dev.yml up --build
Windows: Bind mounts don't propagate FS events reliably. API changes always require a rebuild; Svelte changes often do too. Never rely on hot-reload for server-side files.
docker compose -f docker-compose.yml -f docker-compose.dev.yml build api
docker compose -f docker-compose.yml -f docker-compose.dev.yml up -d api
docker compose up --build -d
docker compose down # stop, keep volumes
docker compose down -v # stop, delete volumes
GET /settings, PATCH /settings/:key; typed inputs (string/boolean/number/select)app.name setting updates the header and browser title in real time; admins can upload a custom logo image to replace the default SVG iconlogAudit() helper; events across auth/users/roles/messages/settings; GET /audit with paginationOLLAMA_URL=http://host.docker.internal:11434); can be disabled via chat.enabled settingexample/ # The scaffold — clone this for new projects
├── docker-compose.yml
├── docker-compose.dev.yml
├── .env.example
├── CLAUDE.md # Full project reference for AI-assisted dev
├── frontend/ # SvelteKit app — port 3000
│ └── src/
│ ├── routes/
│ │ ├── (marketing)/ # Public marketing pages
│ │ ├── api/ # SvelteKit → Fastify proxy routes
│ │ ├── dashboard/
│ │ ├── messages/
│ │ ├── settings/ # Admin: app configuration
│ │ ├── users/ # Admin: Manage Users
│ │ ├── roles/ # Admin: Manage Roles
│ │ ├── login/
│ │ ├── forgot-password/
│ │ └── reset-password/
│ └── lib/
│ ├── components/ # Shared UI components
│ ├── config/
│ │ ├── logo.ts # Brand name / logo
│ │ └── nav.ts # Sidebar nav items (module-extensible)
│ └── permissions.ts # hasPermission(user, resource, action)
└── api/ # Fastify app — port 4000
└── src/
├── server.ts # Entry point; routes auto-loaded from routes/
├── plugins/ # session, MongoDB, CORS, seed
├── routes/ # One subdirectory per resource (autoloaded)
├── data/
│ └── permissions.json # Default role permissions (module-extensible)
└── lib/ # checkDuplicateUser, email, audit helpers
modules/ # Build-time feature modules
├── commerce/ # E-commerce: products, categories, orders, inventory
│ ├── module.json
│ ├── api/src/routes/commerce/
│ └── frontend/src/routes/commerce/
└── notifications/ # Example stub module
├── module.json
├── api/src/routes/notifications/
└── frontend/src/routes/notifications/
arch.js # Project scaffold CLI (see below)
New projects are created from the scaffold with arch.js:
node arch.js create my-app # scaffold only
node arch.js create my-app --modules notifications # with modules
node arch.js create my-app --modules a,b,c # multiple modules
node arch.js list # available modules
node arch.js info notifications # module manifest
arch.js create copies example/ to projects/<name>/, updates the MongoDB database name, and for each module:
frontend/src/lib/config/nav.tsapi/src/data/permissions.jsondependencies into both package.json files.env and .env.exampleA module is a directory under modules/ containing module.json and optional api/ / frontend/ subtrees that mirror the scaffold layout exactly.
{
"name": "my-feature",
"description": "Short description",
"nav": [{ "label": "My Feature", "href": "/my-feature", "icon": "Star", "permission": "my_feature.read" }],
"permissions": [{ "resource": "my_feature", "actions": ["create", "read", "update", "delete"] }],
"dependencies": { "frontend": {}, "api": {} },
"env": [{ "key": "MY_API_KEY", "default": "", "description": "Required for My Feature" }]
}
Icons must be valid lucide-svelte component names. The permission field is "resource.action" (dot-separated).
| Variable | Default | Notes |
|---|---|---|
SESSION_SECRET |
— | Required. 64-char hex. |
MONGO_DB |
appdb |
|
WEB_PORT |
3000 |
|
API_PORT |
4000 |
|
SMTP_HOST |
(blank) | Blank → Ethereal auto-provisioned in dev |
SMTP_PORT |
587 |
|
SMTP_USER |
— | |
SMTP_PASS |
— | |
SMTP_FROM |
Architectonic <[email protected]> |
|
APP_URL |
http://localhost:3000 |
Used in password reset links |
OLLAMA_URL |
http://host.docker.internal:11434 |
Dev overlay only |
Generate SESSION_SECRET:
node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"
git config --global core.autocrlf input before cloning.| Package | URL |
|---|---|
| SvelteKit | https://svelte.dev/docs/kit |
| Svelte 5 | https://svelte.dev/docs/svelte |
| Skeleton v4 | https://skeleton.dev |
| Tailwind v4 | https://tailwindcss.com/docs |
| Fastify v5 | https://fastify.dev/docs |
| Nodemailer | https://nodemailer.com |
Build a custom theme: https://themes.skeleton.dev/themes/create — place the CSS file in frontend/ and import it in app.css.