create-mvc-stack
v1.1.0
Published
Scaffold a full-stack project: Vite frontend with Tailwind + Express backend in MVC pattern.
Maintainers
Readme
create-mvc-stack
Scaffold a full-stack project in one command: a Vite frontend with Tailwind CSS v4 and an Express backend in MVC layout, wired together with an axios API client.
npx create-mvc-stack my-appYou get asked which Vite template to use, then everything installs.
What it generates
my-app/
├── frontend/ # npm create vite@latest + Tailwind v4
│ ├── vite.config.js # @tailwindcss/vite plugin
│ ├── .env # VITE_API_URL=http://localhost:5001/api
│ └── src/
│ ├── index.css # @import "tailwindcss";
│ └── utils/api.js # axios instance (api.ts for -ts templates)
├── backend/
│ ├── server.js # express + cors + routes + listen, reads .env
│ ├── .env / .env.example
│ └── src/
│ ├── routes/ # index.js with GET /api/health
│ ├── controllers/ # empty, add yours here
│ ├── models/ # empty, add yours here
│ └── middlewares/ # error.middleware.js
└── package.json # dev:frontend / dev:backend scriptsNo example resource is generated — the MVC folders are yours to fill. Nothing is
installed until both folders exist, then each gets a single npm install.
Every dependency is installed as @latest, so a generated project always starts on
current versions of Vite, Tailwind, Express and friends — not on whatever was current
when this generator was published.
Options
npx create-mvc-stack [project-name] [options]
-t, --template <name> Vite template, skips the prompt
react, react-ts, vue, vue-ts, svelte, svelte-ts,
solid, solid-ts, preact, preact-ts, vanilla, vanilla-ts
(any other create-vite template name also works)
--no-install Write files and record dependencies, skip downloading
-y, --yes Accept all defaults, never prompt
-h, --help Show helpRunning the generated app
cd my-app
npm run dev:backend # http://localhost:5001
npm run dev:frontend # http://localhost:5173The backend defaults to port 5001 because macOS AirPlay Receiver occupies 5000.
Calling the API
frontend/src/utils/api.js (or api.ts) exports a configured axios instance.
The base URL comes from frontend/.env:
VITE_API_URL=http://localhost:5001/apiimport api from './utils/api';
const health = await api.get('/health'); // GET /api/health
await api.post('/things', { name: 'Hello' }); // any route you addA response interceptor returns response.data directly and rejects with the
backend's own error message, so a failed request throws that message instead of
Request failed with status code 400.
Requests are plain cross-origin calls — the backend allows the origin set in
backend/.env (CLIENT_URL), so keep the two ports in sync if you change them.
Backend API
| Method | Route | Description |
| ------ | -------------- | --------------- |
| GET | / | Health banner |
| GET | /api/health | Status + uptime |
That's the whole surface. To add a resource:
src/models/— data access (in-memory, Mongoose, Prisma, SQL, whatever)src/controllers/— request handling,next(err)for failuressrc/routes/— anexpress.Router(), mounted insrc/routes/index.js
src/middlewares/error.middleware.js already handles unknown routes and turns
err.status into the response code.
License
MIT
