create-rex-app 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/index.js +148 -0
- package/package.json +31 -0
- package/template/README.md +71 -0
- package/template/_gitignore +25 -0
- package/template/api/routes.js +9 -0
- package/template/eslint.config.js +29 -0
- package/template/index.html +13 -0
- package/template/package-lock.json +4667 -0
- package/template/package.json +33 -0
- package/template/public/rex logo.png +0 -0
- package/template/public/rex.png +0 -0
- package/template/server.js +77 -0
- package/template/src/App.css +42 -0
- package/template/src/App.jsx +326 -0
- package/template/src/assets/react.svg +1 -0
- package/template/src/hooks/useTest.js +18 -0
- package/template/src/index.css +1 -0
- package/template/src/main.jsx +14 -0
- package/template/vercel.json +8 -0
- package/template/vite.config.js +7 -0
- package/template-mongo/README.md +96 -0
- package/template-mongo/api/db.js +17 -0
- package/template-mongo/api/middleware/authMiddleware.js +31 -0
- package/template-mongo/api/models/User.js +28 -0
- package/template-mongo/api/routes.js +99 -0
- package/template-mongo/env-sample.txt +3 -0
- package/template-mongo/eslint.config.js +29 -0
- package/template-mongo/index.html +13 -0
- package/template-mongo/package-lock.json +5076 -0
- package/template-mongo/package.json +38 -0
- package/template-mongo/public/rex logo.png +0 -0
- package/template-mongo/public/rex.png +0 -0
- package/template-mongo/public/vite.svg +1 -0
- package/template-mongo/server.js +80 -0
- package/template-mongo/src/App.css +42 -0
- package/template-mongo/src/App.jsx +42 -0
- package/template-mongo/src/assets/react.svg +1 -0
- package/template-mongo/src/components/Layout.jsx +85 -0
- package/template-mongo/src/components/PublicOnly.jsx +17 -0
- package/template-mongo/src/components/RequireAuth.jsx +20 -0
- package/template-mongo/src/context/AuthContext.jsx +35 -0
- package/template-mongo/src/hooks/useAuthContext.js +11 -0
- package/template-mongo/src/hooks/useLogin.js +42 -0
- package/template-mongo/src/hooks/useSignup.js +23 -0
- package/template-mongo/src/hooks/useTest.js +18 -0
- package/template-mongo/src/index.css +1 -0
- package/template-mongo/src/main.jsx +18 -0
- package/template-mongo/src/pages/Docs.jsx +131 -0
- package/template-mongo/src/pages/Home.jsx +93 -0
- package/template-mongo/src/pages/Login.jsx +97 -0
- package/template-mongo/src/pages/Signup.jsx +112 -0
- package/template-mongo/vercel.json +13 -0
- package/template-mongo/vite.config.js +7 -0
package/index.js
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import fs from 'fs';
|
|
4
|
+
import path from 'path';
|
|
5
|
+
import { fileURLToPath } from 'url';
|
|
6
|
+
import { exec } from 'child_process';
|
|
7
|
+
import util from 'util';
|
|
8
|
+
import chalk from 'chalk';
|
|
9
|
+
import ora from 'ora';
|
|
10
|
+
import { confirm, select } from '@inquirer/prompts'; // <-- Imported select
|
|
11
|
+
|
|
12
|
+
const execPromise = util.promisify(exec);
|
|
13
|
+
|
|
14
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
15
|
+
const projectName = process.argv[2] || 'react-express-app';
|
|
16
|
+
const currentDir = process.cwd();
|
|
17
|
+
const targetPath = path.join(currentDir, projectName);
|
|
18
|
+
|
|
19
|
+
console.log();
|
|
20
|
+
console.log(`Creating a new Rex app in ${chalk.green(targetPath)}.`);
|
|
21
|
+
console.log();
|
|
22
|
+
|
|
23
|
+
// 1. The Safe Empty Check
|
|
24
|
+
if (fs.existsSync(targetPath)) {
|
|
25
|
+
const files = fs.readdirSync(targetPath);
|
|
26
|
+
if (files.length > 0) {
|
|
27
|
+
console.error(chalk.red(`The directory ${chalk.green(projectName)} contains files that could conflict.`));
|
|
28
|
+
console.error(chalk.red('Either try using a new directory name, or remove the existing files.'));
|
|
29
|
+
process.exit(1);
|
|
30
|
+
}
|
|
31
|
+
} else {
|
|
32
|
+
fs.mkdirSync(targetPath, { recursive: true });
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// 2. Template Selection Prompt
|
|
36
|
+
const templateChoice = await select({
|
|
37
|
+
message: 'Which Rex template would you like to use?',
|
|
38
|
+
choices: [
|
|
39
|
+
{
|
|
40
|
+
name: chalk.hex('#7C3AED')('Vanilla Rex (Standard)'), // Rex Purple
|
|
41
|
+
value: 'template',
|
|
42
|
+
description: 'The blazing fast, lightweight setup.',
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
name: chalk.hex('#F97316')('Mongo Rex (Standard + MongoDB + JWT Auth)'), // Rex Orange
|
|
46
|
+
value: 'template-mongo',
|
|
47
|
+
description: 'Includes Mongoose, Auth flow, and protected routes.',
|
|
48
|
+
}
|
|
49
|
+
],
|
|
50
|
+
theme: {
|
|
51
|
+
prefix: chalk.hex('#F97316')('?'), // Changes the default green question mark
|
|
52
|
+
style: {
|
|
53
|
+
highlight: (text) => chalk.hex('#7C3AED')(text), // Changes the default cyan highlight pointer
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
// Set the path dynamically based on the user's choice!
|
|
59
|
+
const templatePath = path.join(__dirname, templateChoice);
|
|
60
|
+
|
|
61
|
+
console.log();
|
|
62
|
+
|
|
63
|
+
// 3. Copy files with a clean spinner
|
|
64
|
+
const copySpinner = ora(`Initializing ${templateChoice} files...`).start();
|
|
65
|
+
const copyRecursiveSync = (src, dest) => {
|
|
66
|
+
const exists = fs.existsSync(src);
|
|
67
|
+
const stats = exists && fs.statSync(src);
|
|
68
|
+
const isDirectory = exists && stats.isDirectory();
|
|
69
|
+
|
|
70
|
+
if (isDirectory) {
|
|
71
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
72
|
+
fs.readdirSync(src).forEach((childItemName) => {
|
|
73
|
+
if (childItemName === 'node_modules') return;
|
|
74
|
+
copyRecursiveSync(path.join(src, childItemName), path.join(dest, childItemName));
|
|
75
|
+
});
|
|
76
|
+
} else {
|
|
77
|
+
fs.copyFileSync(src, dest);
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
try {
|
|
82
|
+
copyRecursiveSync(templatePath, targetPath);
|
|
83
|
+
|
|
84
|
+
const gitignorePath = path.join(targetPath, '_gitignore');
|
|
85
|
+
if (fs.existsSync(gitignorePath)) {
|
|
86
|
+
fs.renameSync(gitignorePath, path.join(targetPath, '.gitignore'));
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const envPath = path.join(targetPath, '_env');
|
|
90
|
+
if (fs.existsSync(envPath)) {
|
|
91
|
+
fs.renameSync(envPath, path.join(targetPath, '.env'));
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
copySpinner.succeed(chalk.green('Initialized project files.'));
|
|
95
|
+
} catch (error) {
|
|
96
|
+
copySpinner.fail(chalk.red('Failed to extract template.'));
|
|
97
|
+
console.error(error);
|
|
98
|
+
process.exit(1);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// 4. Interactive Prompt
|
|
102
|
+
console.log();
|
|
103
|
+
const shouldInstall = await confirm({
|
|
104
|
+
message: 'Would you like to install dependencies now?',
|
|
105
|
+
default: true
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
// 5. The Real Worker (Auto-Install)
|
|
109
|
+
console.log();
|
|
110
|
+
if (shouldInstall) {
|
|
111
|
+
const installSpinner = ora('Installing packages. This might take a couple of minutes...').start();
|
|
112
|
+
try {
|
|
113
|
+
await execPromise('npm install', { cwd: targetPath });
|
|
114
|
+
installSpinner.succeed(chalk.green('Installed dependencies.'));
|
|
115
|
+
} catch (error) {
|
|
116
|
+
installSpinner.fail(chalk.yellow('Installation failed. You can run `npm install` manually later.'));
|
|
117
|
+
}
|
|
118
|
+
} else {
|
|
119
|
+
console.log(chalk.dim('Skipped dependency installation.'));
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
console.log();
|
|
123
|
+
console.log(`${chalk.green('Success!')} Created ${chalk.cyan(projectName)} at ${chalk.cyan(targetPath)}`);
|
|
124
|
+
console.log('Inside that directory, you can run several commands:');
|
|
125
|
+
console.log();
|
|
126
|
+
console.log(chalk.cyan(` npm run dev`));
|
|
127
|
+
console.log(' Starts the development server.');
|
|
128
|
+
console.log();
|
|
129
|
+
console.log(chalk.cyan(` npm run build`));
|
|
130
|
+
console.log(' Bundles the app into static files for production.');
|
|
131
|
+
console.log();
|
|
132
|
+
console.log('We suggest that you begin by typing:');
|
|
133
|
+
console.log();
|
|
134
|
+
|
|
135
|
+
if (projectName !== '.') {
|
|
136
|
+
console.log(chalk.cyan(` cd ${projectName}`));
|
|
137
|
+
}
|
|
138
|
+
if (!shouldInstall) {
|
|
139
|
+
console.log(chalk.cyan(` npm install`));
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Add a helpful reminder for Mongo template users
|
|
143
|
+
if (templateChoice === 'template-mongo') {
|
|
144
|
+
console.log(chalk.yellow(`\n ⚠️ Don't forget to configure your .env file with your MONGO_URI and JWT_SECRET!`));
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
console.log(chalk.cyan(` npm run dev`));
|
|
148
|
+
console.log();
|
package/package.json
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "create-rex-app",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"main": "index.js",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"homepage": "https://rex-docs.vercel.app/",
|
|
7
|
+
"bin": {
|
|
8
|
+
"create-rex-app": "./index.js"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
12
|
+
},
|
|
13
|
+
"keywords": [
|
|
14
|
+
"rex",
|
|
15
|
+
"react",
|
|
16
|
+
"express",
|
|
17
|
+
"vite",
|
|
18
|
+
"boilerplate",
|
|
19
|
+
"monolith",
|
|
20
|
+
"mern",
|
|
21
|
+
"template"
|
|
22
|
+
],
|
|
23
|
+
"author": "Saumya Rawal",
|
|
24
|
+
"license": "MIT",
|
|
25
|
+
"description": "Scaffold a React + Vite + Express boilerplate ready for Vercel",
|
|
26
|
+
"dependencies": {
|
|
27
|
+
"@inquirer/prompts": "^8.2.1",
|
|
28
|
+
"chalk": "^5.6.2",
|
|
29
|
+
"ora": "^9.3.0"
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# 🚂 React Express Boilerplate
|
|
2
|
+
|
|
3
|
+
A robust, "Monolith-style" fullstack boilerplate that unifies **Vite (React)** and **Express** into a single development experience.
|
|
4
|
+
|
|
5
|
+
> **One Terminal. One Port. Fullstack Power.**
|
|
6
|
+
|
|
7
|
+
## 🚀 Features
|
|
8
|
+
|
|
9
|
+
* **Unified Server:** `server.js` acts as the single entry point. It serves the API *and* the Frontend (via Vite middleware).
|
|
10
|
+
* **Smart Watchdog:** Uses `nodemon` to restart the server **only** when backend files change, while Vite handles HMR for the frontend.
|
|
11
|
+
* **API Ready:** Organized `api/` folder structure with Express Router.
|
|
12
|
+
* **Data Fetching:** Pre-configured with **Axios** and **TanStack Query (React Query)** for professional-grade state management.
|
|
13
|
+
* **Custom Hooks:** Clean separation of concerns with a `hooks/` directory pattern.
|
|
14
|
+
|
|
15
|
+
## 🛠️ Tech Stack
|
|
16
|
+
|
|
17
|
+
* **Frontend:** React, Vite
|
|
18
|
+
* **Backend:** Express.js
|
|
19
|
+
* **Utilities:** Axios, TanStack Query, Nodemon
|
|
20
|
+
|
|
21
|
+
## 📂 Project Structure
|
|
22
|
+
|
|
23
|
+
```text
|
|
24
|
+
root/
|
|
25
|
+
├── api/ # Backend API Routes
|
|
26
|
+
│ └── routes.js # Express Router definitions
|
|
27
|
+
├── src/ # React Frontend
|
|
28
|
+
│ ├── hooks/ # Custom React Hooks (e.g., useTest.js)
|
|
29
|
+
│ ├── App.jsx # Main Component
|
|
30
|
+
│ └── main.jsx # Entry point with QueryProvider
|
|
31
|
+
├── server.js # The Engine (Express + Vite Middleware)
|
|
32
|
+
├── package.json # Scripts and Dependencies
|
|
33
|
+
└── ...
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
## 🏁 Getting Started
|
|
39
|
+
|
|
40
|
+
### 1. Clone & Install
|
|
41
|
+
```bash
|
|
42
|
+
git clone [https://github.com/saumyarawal-webdev/react-express.git](https://github.com/saumyarawal-webdev/react-express.git)
|
|
43
|
+
cd react-express-boilerplate
|
|
44
|
+
npm install
|
|
45
|
+
```
|
|
46
|
+
### 2. Start the Engine 🚂
|
|
47
|
+
Run the development environment. This starts the Express server and the Vite middleware together.
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
npm run dev
|
|
51
|
+
```
|
|
52
|
+
* **Frontend:** `http://localhost:5173`
|
|
53
|
+
* **API Endpoint:** `http://localhost:5173/api/test`
|
|
54
|
+
|
|
55
|
+
### 3. Build for Production
|
|
56
|
+
When ready to deploy, build the React app. The server is configured to serve the static files in production (requires minor `dist` folder config in server.js for final deploy).
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
npm run build
|
|
60
|
+
```
|
|
61
|
+
## 🧩 How to Add a New API
|
|
62
|
+
|
|
63
|
+
1. **Backend:** Add a new route in `api/routes.js`:
|
|
64
|
+
```javascript
|
|
65
|
+
router.get('/users', (req, res) => { ... })
|
|
66
|
+
```
|
|
67
|
+
2. **Frontend:** Create a hook in `src/hooks/useUsers.js` using Axios + useQuery.
|
|
68
|
+
3. **UI:** Import the hook in your component and display the data.
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
*Made with 💙 by Saumya*
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# Logs
|
|
2
|
+
logs
|
|
3
|
+
*.log
|
|
4
|
+
npm-debug.log*
|
|
5
|
+
yarn-debug.log*
|
|
6
|
+
yarn-error.log*
|
|
7
|
+
pnpm-debug.log*
|
|
8
|
+
lerna-debug.log*
|
|
9
|
+
|
|
10
|
+
node_modules
|
|
11
|
+
dist
|
|
12
|
+
.env
|
|
13
|
+
dist-ssr
|
|
14
|
+
*.local
|
|
15
|
+
|
|
16
|
+
# Editor directories and files
|
|
17
|
+
.vscode/*
|
|
18
|
+
!.vscode/extensions.json
|
|
19
|
+
.idea
|
|
20
|
+
.DS_Store
|
|
21
|
+
*.suo
|
|
22
|
+
*.ntvs*
|
|
23
|
+
*.njsproj
|
|
24
|
+
*.sln
|
|
25
|
+
*.sw?
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import js from '@eslint/js'
|
|
2
|
+
import globals from 'globals'
|
|
3
|
+
import reactHooks from 'eslint-plugin-react-hooks'
|
|
4
|
+
import reactRefresh from 'eslint-plugin-react-refresh'
|
|
5
|
+
import { defineConfig, globalIgnores } from 'eslint/config'
|
|
6
|
+
|
|
7
|
+
export default defineConfig([
|
|
8
|
+
globalIgnores(['dist']),
|
|
9
|
+
{
|
|
10
|
+
files: ['**/*.{js,jsx}'],
|
|
11
|
+
extends: [
|
|
12
|
+
js.configs.recommended,
|
|
13
|
+
reactHooks.configs.flat.recommended,
|
|
14
|
+
reactRefresh.configs.vite,
|
|
15
|
+
],
|
|
16
|
+
languageOptions: {
|
|
17
|
+
ecmaVersion: 2020,
|
|
18
|
+
globals: globals.browser,
|
|
19
|
+
parserOptions: {
|
|
20
|
+
ecmaVersion: 'latest',
|
|
21
|
+
ecmaFeatures: { jsx: true },
|
|
22
|
+
sourceType: 'module',
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
rules: {
|
|
26
|
+
'no-unused-vars': ['error', { varsIgnorePattern: '^[A-Z_]' }],
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
])
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<link rel="icon" type="image/svg+xml" href="/rex.png" />
|
|
6
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
7
|
+
<title>Rex - One House Partner</title>
|
|
8
|
+
</head>
|
|
9
|
+
<body>
|
|
10
|
+
<div id="root"></div>
|
|
11
|
+
<script type="module" src="/src/main.jsx"></script>
|
|
12
|
+
</body>
|
|
13
|
+
</html>
|