Full-stack MVC framework — Express backend + Vue 3 frontend, wired together with Vite.
Vulkano is a full-stack framework that gives you a convention-based Express API backend (@vulkano/core) and a Vue 3 frontend — both running from the same project, bundled by Vite.
Inspired by KumbiaPHP.
app/is the Express backendclient/is the Vue 3 SPA.
Full folder layout: see docs/ARCHITECTURE.md. Detailed conventions (routing, controller/model, auth, components, testing, SEO, ...) live as Claude Code skills under .claude/skills/ — docs/BACKEND.md and docs/FRONTEND.md are thin pointers into them.
| Layer | Technology |
|---|---|
| Backend | Node.js 22, Express 4, Mongoose, @vulkano/core |
| Frontend | Vue 3, Vue Router, Vite |
| Styling | SCSS, Foundation Sites |
| Dev tools | Nodemon, ESLint, PM2 |
- Node.js
^22 - Vite+ CLI (
vp) — installs Vite, Vitest, and the rest of the toolchain globally. See viteplus.dev for the install command, then verify withvp help. - MongoDB (optional — only needed if you use models)
- Redis (optional — Socket.io adapter or sessions)
pnpm install # or npm installNew project? Strip the demo boilerplate (example controller/model, HelloWorld component, demo view) before you start building:
pnpm run cleanupAsks for confirmation (yes) before deleting anything. Skip this if you want to keep the demo as a reference.
| Command | Description |
|---|---|
pnpm run dev |
Start Express + Vite dev server with HMR |
pnpm run cleanup |
Remove demo boilerplate (new projects) |
pnpm run build |
Build frontend assets into public/ |
pnpm run start |
Start Express in production mode |
pnpm run lint |
Lint via vp lint |
pnpm run test |
Run tests via vp test |
pnpm run dev starts Express and the Vite dev server together, but only one port matters: open http://localhost:$PORT (8000 by default, set in .env). Express serves the page and injects the Vite-bundled frontend automatically — no need to also open localhost:5173 in a second tab, that's Vite's internal dev server, not a second app.
PORT=8000
HOST=localhost
MONGO_URI=mongodb://localhost:27017/myapp
SALT_KEY=random-string
JWT_SECRET_KEY=supersecret
# COOKIES_SECRET_KEY=another-secret # optional, only for signed cookies
VITE_CHUNK_NAMES=false
# VITE_HOST=192.168.x.x # optional — forces a specific dev-server host; unset uses auto LAN detection
Be specific about the layer, the data, and the behavior you want.
Instead of: "add a contact form"
Say: "add a contact form with name, email, and message fields, a
ContactController that validates them and saves a Contact model, and a success message on the frontend"
Instead of: "add authentication"
Say: "add JWT login using the existing @vulkano/core auth conventions, with a User model and a login view under client/views/Login"
Instead of: "show a list of users"
Say: "add a /users route backed by UserController#index that returns paginated User documents, and a client/views/Users/Index.vue that renders them in a table"
Claude Code picks up the controller/model/view/auth/testing conventions
automatically from .claude/skills/ — no need to paste them into your
prompt. See AGENTS.md for the rules your agent reads
automatically (Claude Code, and any tool that honors
AGENTS.md/CLAUDE.md) — workflow, security boundaries, and handoff
checklist.
- Tell your AI agent: "add a
[table]model with fields[...]" - The agent will create/update
app/models/[table].js
- Tell your AI agent: "add a route to save
[table]to the database" — usuallydomain.com/api/[table] - The agent creates the matching controller under
app/controllers/api/and wires it to the model
Say which one you want, they're not the same:
- Frontend view (Vue SPA, client-rendered): "create a view to show
[table]records" →client/views/[Table]/Index.vue, fetches data viaclient/Api.js - Backend view (Nunjucks, server-rendered): "create a backend view to show
[table]records" →app/views/[table]/index.html, rendered by the controller viares.vsr/res.render
- Tell your AI agent: "create a route to create a form" (e.g. "add a
/posts/newroute with a form to create aPost") - The agent adds the route in
client/routes.jsand a view underclient/views/, wired to the API route that saves the model
| Task | What to tell your agent |
|---|---|
| New component | "add a PostCard component" → client/components/PostCard/ |
| Run checks | vp check (format/lint/typecheck) and vp test (tests) |
Drop any files here that you want your AI agent to work with — logos,
images, fonts, PDFs — then tell your agent to use them, e.g. "use the
logo in inbox/ for the header". Files dropped in inbox/ are not
committed by default (see inbox/.gitignore).
For images, run the following to convert .jpg/.jpeg/.png files in
inbox/ to .webp in place (smaller file size, originals are kept):
pnpm run inbox:webpThen use <picture> with the .webp as the primary source and the
original as fallback, plus explicit width/height (prevents layout
shift) and loading="lazy" for below-the-fold images:
<picture>
<source srcset="/img/hero.webp" type="image/webp" />
<img src="/img/hero.jpg" alt="..." width="1200" height="600" loading="lazy" />
</picture>The project ships with an ecosystem.config.js for PM2:
pm2 start ecosystem.config.jsBuild and run the production image directly:
docker build -t vulkano-framework .
docker run --env-file .env -p 8000:8000 vulkano-frameworkOr use Docker Compose, which reads .env for you:
docker compose up --buildBy default this only starts the app service — set MONGO_URI in .env to
point at an external MongoDB (Atlas, a managed instance, etc). If you'd
rather run MongoDB locally in a container, start the local-db profile
instead:
docker compose --profile local-db up --buildUse the Docker Compose deployment type in Coolify and point it at this
repo — it picks up docker-compose.yml and nixpacks.toml automatically.
.env is gitignored and never reaches the build, so set your environment
variables (PORT, MONGO_URI, SALT_KEY, JWT_SECRET_KEY, etc) in Coolify's
own Environment Variables panel for the app — Coolify injects them into
the running container at deploy time.
MIT © Vulkano Team
