create-butterfly-app-v2 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +152 -0
- package/cli.js +49 -0
- package/package.json +13 -0
- package/templates/fullstack/backend/config/db.js +13 -0
- package/templates/fullstack/backend/package.json +16 -0
- package/templates/fullstack/backend/routes/api.js +26 -0
- package/templates/fullstack/backend/server.js +26 -0
- package/templates/fullstack/frontend/index.html +12 -0
- package/templates/fullstack/frontend/package.json +21 -0
- package/templates/fullstack/frontend/postcss.config.js +6 -0
- package/templates/fullstack/frontend/src/App.jsx +86 -0
- package/templates/fullstack/frontend/src/index.css +3 -0
- package/templates/fullstack/frontend/src/main.jsx +10 -0
- package/templates/fullstack/frontend/tailwind.config.js +8 -0
- package/templates/fullstack/frontend/vite.config.js +12 -0
- package/templates/fullstack/package.json +12 -0
package/README.md
ADDED
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
# create-butterfly-app
|
|
2
|
+
|
|
3
|
+
Scaffold a full-stack **Node.js + React + Tailwind + MySQL** project in seconds.
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
npx create-butterfly-app my-project
|
|
7
|
+
cd my-project
|
|
8
|
+
npm run dev
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Why this exists
|
|
14
|
+
|
|
15
|
+
If you have an **exam or assignment** that requires a working full-stack web app — with a backend API, a database, and a modern frontend — but you **don't know how to set it all up from scratch**, this boilerplate is for you.
|
|
16
|
+
|
|
17
|
+
Instead of spending hours wiring up Express, configuring Vite, installing Tailwind, connecting MySQL — writing boilerplate config files you don't understand — you get a **ready-to-run project** with one command.
|
|
18
|
+
|
|
19
|
+
You only need to know:
|
|
20
|
+
1. **That XAMPP must be running** (start Apache & MySQL from the XAMPP Control Panel)
|
|
21
|
+
2. **How to create a database** (open phpMyAdmin, click "New", type your project name, click "Create")
|
|
22
|
+
3. **How to run `npm run dev`** in the terminal
|
|
23
|
+
|
|
24
|
+
That's it.
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## What you get
|
|
29
|
+
|
|
30
|
+
```
|
|
31
|
+
my-project/
|
|
32
|
+
├── backend/
|
|
33
|
+
│ ├── server.js # Express server, starts on port 5000
|
|
34
|
+
│ ├── config/db.js # MySQL connection via XAMPP
|
|
35
|
+
│ └── routes/api.js # /api/users GET + POST
|
|
36
|
+
├── frontend/
|
|
37
|
+
│ ├── src/App.jsx # React UI with Tailwind styling
|
|
38
|
+
│ ├── src/main.jsx # React entry point
|
|
39
|
+
│ ├── vite.config.js # Dev server + proxy to backend
|
|
40
|
+
│ ├── tailwind.config.js
|
|
41
|
+
│ └── postcss.config.js
|
|
42
|
+
├── .env # DB credentials (user=root, password=blank)
|
|
43
|
+
└── package.json # Runs both backend & frontend together
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
## How to use it in an exam / project
|
|
49
|
+
|
|
50
|
+
### Step 1 — Start XAMPP
|
|
51
|
+
|
|
52
|
+
Open **XAMPP Control Panel** → click **Start** next to **Apache** and **MySQL**.
|
|
53
|
+
|
|
54
|
+
### Step 2 — Create the database
|
|
55
|
+
|
|
56
|
+
Open your browser, go to `http://localhost/phpmyadmin`:
|
|
57
|
+
- Click **New** (left sidebar)
|
|
58
|
+
- Type your project name as the database name
|
|
59
|
+
- Click **Create** (no tables needed — the API will create them if you want, or you can add a table manually)
|
|
60
|
+
|
|
61
|
+
### Step 3 — Create the project
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
npx create-butterfly-app my-exam-project
|
|
65
|
+
cd my-exam-project
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### Step 4 — Start the app
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
npm run dev
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
This starts:
|
|
75
|
+
- **Backend** on `http://localhost:5000`
|
|
76
|
+
- **Frontend** on `http://localhost:5173` (opens in browser automatically)
|
|
77
|
+
|
|
78
|
+
### Step 5 — Add a users table (required for the demo)
|
|
79
|
+
|
|
80
|
+
Go to `http://localhost/phpmyadmin`, select your database, and run this SQL:
|
|
81
|
+
|
|
82
|
+
```sql
|
|
83
|
+
CREATE TABLE users (
|
|
84
|
+
id INT AUTO_INCREMENT PRIMARY KEY,
|
|
85
|
+
name VARCHAR(100) NOT NULL,
|
|
86
|
+
email VARCHAR(100) NOT NULL,
|
|
87
|
+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
88
|
+
);
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Now the app works — add users from the browser UI, see them stored in MySQL.
|
|
92
|
+
|
|
93
|
+
---
|
|
94
|
+
|
|
95
|
+
## Why this is effective when you don't know code
|
|
96
|
+
|
|
97
|
+
| Problem | Solution |
|
|
98
|
+
|--------|----------|
|
|
99
|
+
| "I don't know how to set up a Node.js server" | `server.js` is pre-written with Express |
|
|
100
|
+
| "I don't know how to connect to MySQL" | `config/db.js` connects automatically using `.env` |
|
|
101
|
+
| "I don't know React or Tailwind" | `App.jsx` gives you a styled working UI |
|
|
102
|
+
| "I can't configure Vite" | `vite.config.js` is ready with API proxy |
|
|
103
|
+
| "I don't understand npm" | One command installs everything |
|
|
104
|
+
| "I don't know how to run two servers at once" | `concurrently` runs both with `npm run dev` |
|
|
105
|
+
|
|
106
|
+
The boilerplate handles **every configuration detail**. You focus only on:
|
|
107
|
+
- **Modifying the HTML/JSX** if you want to change the page layout
|
|
108
|
+
- **Adding SQL queries** in `routes/api.js` if you need more features
|
|
109
|
+
- **Changing the CSS** with Tailwind utility classes if you want different colors
|
|
110
|
+
|
|
111
|
+
Everything else works out of the box.
|
|
112
|
+
|
|
113
|
+
---
|
|
114
|
+
|
|
115
|
+
## Testing locally without publishing
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
# From the create-butterfly-app folder
|
|
119
|
+
npm link
|
|
120
|
+
create-butterfly-app my-test
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
---
|
|
124
|
+
|
|
125
|
+
## Publishing your own version
|
|
126
|
+
|
|
127
|
+
```bash
|
|
128
|
+
cd create-butterfly-app
|
|
129
|
+
npm login
|
|
130
|
+
npm publish
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
Then anyone can run:
|
|
134
|
+
|
|
135
|
+
```bash
|
|
136
|
+
npx create-butterfly-app any-project-name
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
---
|
|
140
|
+
|
|
141
|
+
## Important notes for exams
|
|
142
|
+
|
|
143
|
+
- **XAMPP must be running** before you start the app
|
|
144
|
+
- The `.env` file assumes default XAMPP credentials (`root` / blank password / port 3306) — change if yours is different
|
|
145
|
+
- The app runs on **port 5000** (backend) and **port 5173** (frontend) — make sure these are free
|
|
146
|
+
- If the frontend can't reach the backend, check that the proxy in `vite.config.js` matches the backend port
|
|
147
|
+
|
|
148
|
+
---
|
|
149
|
+
|
|
150
|
+
## License
|
|
151
|
+
|
|
152
|
+
MIT — use it for anything, including exams.
|
package/cli.js
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
const fs = require("fs");
|
|
3
|
+
const path = require("path");
|
|
4
|
+
const { execSync } = require("child_process");
|
|
5
|
+
|
|
6
|
+
const projectName = process.argv[2] || "my-fullstack-app";
|
|
7
|
+
const targetDir = path.resolve(process.cwd(), projectName);
|
|
8
|
+
const templateDir = path.join(__dirname, "templates", "fullstack");
|
|
9
|
+
|
|
10
|
+
if (fs.existsSync(targetDir)) {
|
|
11
|
+
console.error(`Error: Directory "${projectName}" already exists.`);
|
|
12
|
+
process.exit(1);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
console.log(`\n Creating ${projectName}...\n`);
|
|
16
|
+
|
|
17
|
+
function copyRecursive(src, dest) {
|
|
18
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
19
|
+
const entries = fs.readdirSync(src, { withFileTypes: true });
|
|
20
|
+
for (const entry of entries) {
|
|
21
|
+
const srcPath = path.join(src, entry.name);
|
|
22
|
+
const destPath = path.join(dest, entry.name);
|
|
23
|
+
if (entry.isDirectory()) {
|
|
24
|
+
copyRecursive(srcPath, destPath);
|
|
25
|
+
} else {
|
|
26
|
+
let content = fs.readFileSync(srcPath, "utf-8");
|
|
27
|
+
content = content.replace(/__PROJECT_NAME__/g, projectName);
|
|
28
|
+
fs.writeFileSync(destPath, content);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
copyRecursive(templateDir, targetDir);
|
|
34
|
+
console.log(` Created project at: ${targetDir}\n`);
|
|
35
|
+
|
|
36
|
+
try {
|
|
37
|
+
console.log(" Installing backend dependencies...");
|
|
38
|
+
execSync("npm install", { cwd: path.join(targetDir, "backend"), stdio: "inherit" });
|
|
39
|
+
console.log(" Installing frontend dependencies...");
|
|
40
|
+
execSync("npm install", { cwd: path.join(targetDir, "frontend"), stdio: "inherit" });
|
|
41
|
+
console.log(" Installing root dependencies...");
|
|
42
|
+
execSync("npm install", { cwd: targetDir, stdio: "inherit" });
|
|
43
|
+
} catch {
|
|
44
|
+
console.log("\n Run 'npm install' in each folder manually if install failed.");
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
console.log(`\n Done! Run these commands:\n`);
|
|
48
|
+
console.log(` cd ${projectName}`);
|
|
49
|
+
console.log(` npm run dev # starts both backend & frontend\n`);
|
package/package.json
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "create-butterfly-app-v2",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Scaffold a full-stack Node.js + React + Tailwind + MySQL project",
|
|
5
|
+
"bin": {
|
|
6
|
+
"create-butterfly-app": "cli.js"
|
|
7
|
+
},
|
|
8
|
+
"files": [
|
|
9
|
+
"cli.js",
|
|
10
|
+
"templates/"
|
|
11
|
+
],
|
|
12
|
+
"license": "MIT"
|
|
13
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
const mysql = require("mysql2/promise");
|
|
2
|
+
|
|
3
|
+
const pool = mysql.createPool({
|
|
4
|
+
host: process.env.DB_HOST || "localhost",
|
|
5
|
+
user: process.env.DB_USER || "root",
|
|
6
|
+
password: process.env.DB_PASSWORD || "",
|
|
7
|
+
database: process.env.DB_NAME || "__PROJECT_NAME__",
|
|
8
|
+
port: process.env.DB_PORT || 3306,
|
|
9
|
+
waitForConnections: true,
|
|
10
|
+
connectionLimit: 10,
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
module.exports = pool;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "__PROJECT_NAME__-backend",
|
|
3
|
+
"private": true,
|
|
4
|
+
"scripts": {
|
|
5
|
+
"dev": "nodemon server.js"
|
|
6
|
+
},
|
|
7
|
+
"dependencies": {
|
|
8
|
+
"cors": "^2.8.5",
|
|
9
|
+
"dotenv": "^16.4.7",
|
|
10
|
+
"express": "^4.21.1",
|
|
11
|
+
"mysql2": "^3.11.5"
|
|
12
|
+
},
|
|
13
|
+
"devDependencies": {
|
|
14
|
+
"nodemon": "^3.1.9"
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
const router = require("express").Router();
|
|
2
|
+
const db = require("../config/db");
|
|
3
|
+
|
|
4
|
+
router.get("/users", async (req, res) => {
|
|
5
|
+
try {
|
|
6
|
+
const [rows] = await db.query("SELECT * FROM users");
|
|
7
|
+
res.json(rows);
|
|
8
|
+
} catch (err) {
|
|
9
|
+
res.status(500).json({ error: err.message });
|
|
10
|
+
}
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
router.post("/users", async (req, res) => {
|
|
14
|
+
const { name, email } = req.body;
|
|
15
|
+
try {
|
|
16
|
+
const [result] = await db.query(
|
|
17
|
+
"INSERT INTO users (name, email) VALUES (?, ?)",
|
|
18
|
+
[name, email]
|
|
19
|
+
);
|
|
20
|
+
res.status(201).json({ id: result.insertId, name, email });
|
|
21
|
+
} catch (err) {
|
|
22
|
+
res.status(500).json({ error: err.message });
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
module.exports = router;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
require("dotenv").config({ path: "../.env" });
|
|
2
|
+
const express = require("express");
|
|
3
|
+
const cors = require("cors");
|
|
4
|
+
const db = require("./config/db");
|
|
5
|
+
|
|
6
|
+
const app = express();
|
|
7
|
+
const PORT = process.env.PORT || 5000;
|
|
8
|
+
|
|
9
|
+
app.use(cors());
|
|
10
|
+
app.use(express.json());
|
|
11
|
+
|
|
12
|
+
app.get("/api/health", (req, res) => {
|
|
13
|
+
res.json({ status: "ok", project: "__PROJECT_NAME__" });
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
app.use("/api", require("./routes/api"));
|
|
17
|
+
|
|
18
|
+
db.query("SELECT 1")
|
|
19
|
+
.then(() => {
|
|
20
|
+
console.log("MySQL connected");
|
|
21
|
+
app.listen(PORT, () => console.log(`Backend on http://localhost:${PORT}`));
|
|
22
|
+
})
|
|
23
|
+
.catch((err) => {
|
|
24
|
+
console.error("MySQL connection failed:", err.message);
|
|
25
|
+
process.exit(1);
|
|
26
|
+
});
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<title>__PROJECT_NAME__</title>
|
|
7
|
+
</head>
|
|
8
|
+
<body>
|
|
9
|
+
<div id="root"></div>
|
|
10
|
+
<script type="module" src="/src/main.jsx"></script>
|
|
11
|
+
</body>
|
|
12
|
+
</html>
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "__PROJECT_NAME__-frontend",
|
|
3
|
+
"private": true,
|
|
4
|
+
"type": "module",
|
|
5
|
+
"scripts": {
|
|
6
|
+
"dev": "vite",
|
|
7
|
+
"build": "vite build",
|
|
8
|
+
"preview": "vite preview"
|
|
9
|
+
},
|
|
10
|
+
"dependencies": {
|
|
11
|
+
"react": "^19.0.0",
|
|
12
|
+
"react-dom": "^19.0.0"
|
|
13
|
+
},
|
|
14
|
+
"devDependencies": {
|
|
15
|
+
"@vitejs/plugin-react": "^4.3.4",
|
|
16
|
+
"autoprefixer": "^10.4.20",
|
|
17
|
+
"postcss": "^8.4.49",
|
|
18
|
+
"tailwindcss": "^3.4.17",
|
|
19
|
+
"vite": "^6.0.6"
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { useState, useEffect } from "react";
|
|
2
|
+
|
|
3
|
+
function App() {
|
|
4
|
+
const [users, setUsers] = useState([]);
|
|
5
|
+
const [name, setName] = useState("");
|
|
6
|
+
const [email, setEmail] = useState("");
|
|
7
|
+
const [status, setStatus] = useState("");
|
|
8
|
+
|
|
9
|
+
useEffect(() => {
|
|
10
|
+
fetch("/api/health")
|
|
11
|
+
.then((r) => r.json())
|
|
12
|
+
.then((d) => setStatus(d.status))
|
|
13
|
+
.catch(() => setStatus("no connection"));
|
|
14
|
+
}, []);
|
|
15
|
+
|
|
16
|
+
useEffect(() => {
|
|
17
|
+
fetch("/api/users")
|
|
18
|
+
.then((r) => r.json())
|
|
19
|
+
.then(setUsers)
|
|
20
|
+
.catch(console.error);
|
|
21
|
+
}, []);
|
|
22
|
+
|
|
23
|
+
const addUser = async (e) => {
|
|
24
|
+
e.preventDefault();
|
|
25
|
+
const res = await fetch("/api/users", {
|
|
26
|
+
method: "POST",
|
|
27
|
+
headers: { "Content-Type": "application/json" },
|
|
28
|
+
body: JSON.stringify({ name, email }),
|
|
29
|
+
});
|
|
30
|
+
if (res.ok) {
|
|
31
|
+
const user = await res.json();
|
|
32
|
+
setUsers((prev) => [...prev, user]);
|
|
33
|
+
setName("");
|
|
34
|
+
setEmail("");
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
return (
|
|
39
|
+
<div className="min-h-screen bg-gray-100 flex flex-col items-center p-6">
|
|
40
|
+
<h1 className="text-4xl font-bold text-blue-600 mb-2">__PROJECT_NAME__</h1>
|
|
41
|
+
<p className="text-gray-500 mb-6">Backend: {status}</p>
|
|
42
|
+
|
|
43
|
+
<form onSubmit={addUser} className="flex gap-2 mb-6">
|
|
44
|
+
<input
|
|
45
|
+
className="border rounded px-3 py-2"
|
|
46
|
+
placeholder="Name"
|
|
47
|
+
value={name}
|
|
48
|
+
onChange={(e) => setName(e.target.value)}
|
|
49
|
+
required
|
|
50
|
+
/>
|
|
51
|
+
<input
|
|
52
|
+
className="border rounded px-3 py-2"
|
|
53
|
+
type="email"
|
|
54
|
+
placeholder="Email"
|
|
55
|
+
value={email}
|
|
56
|
+
onChange={(e) => setEmail(e.target.value)}
|
|
57
|
+
required
|
|
58
|
+
/>
|
|
59
|
+
<button className="bg-blue-600 text-white rounded px-4 py-2 hover:bg-blue-700">
|
|
60
|
+
Add User
|
|
61
|
+
</button>
|
|
62
|
+
</form>
|
|
63
|
+
|
|
64
|
+
<table className="w-full max-w-lg bg-white rounded shadow">
|
|
65
|
+
<thead>
|
|
66
|
+
<tr className="border-b bg-gray-50">
|
|
67
|
+
<th className="p-2 text-left">ID</th>
|
|
68
|
+
<th className="p-2 text-left">Name</th>
|
|
69
|
+
<th className="p-2 text-left">Email</th>
|
|
70
|
+
</tr>
|
|
71
|
+
</thead>
|
|
72
|
+
<tbody>
|
|
73
|
+
{users.map((u) => (
|
|
74
|
+
<tr key={u.id} className="border-b">
|
|
75
|
+
<td className="p-2">{u.id}</td>
|
|
76
|
+
<td className="p-2">{u.name}</td>
|
|
77
|
+
<td className="p-2">{u.email}</td>
|
|
78
|
+
</tr>
|
|
79
|
+
))}
|
|
80
|
+
</tbody>
|
|
81
|
+
</table>
|
|
82
|
+
</div>
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export default App;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "__PROJECT_NAME__",
|
|
3
|
+
"private": true,
|
|
4
|
+
"scripts": {
|
|
5
|
+
"dev": "concurrently \"npm run dev --prefix backend\" \"npm run dev --prefix frontend\"",
|
|
6
|
+
"backend": "npm run dev --prefix backend",
|
|
7
|
+
"frontend": "npm run dev --prefix frontend"
|
|
8
|
+
},
|
|
9
|
+
"devDependencies": {
|
|
10
|
+
"concurrently": "^9.1.0"
|
|
11
|
+
}
|
|
12
|
+
}
|