boilerbase 1.0.1

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 ADDED
@@ -0,0 +1,55 @@
1
+ # BoilerBase
2
+
3
+ A modern beginner-friendly backend scaffolding CLI for Node.js developers.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install
9
+ npm link
10
+ ```
11
+
12
+ ## Usage
13
+
14
+ ```bash
15
+ npx boilerbase
16
+ # or
17
+ boilerbase
18
+ ```
19
+
20
+ ## What it does
21
+
22
+ - Prompts for project settings in the terminal
23
+ - Generates folders and starter files dynamically
24
+ - Installs dependencies automatically
25
+ - Opens the generated project in VS Code
26
+
27
+ ## Stack
28
+
29
+ - Node.js
30
+ - inquirer
31
+ - chalk
32
+ - ora
33
+ - fs-extra
34
+
35
+ ## Generated project
36
+
37
+ The generated backend starter includes:
38
+
39
+ - Express server scaffold
40
+ - Optional MongoDB config
41
+ - JWT auth routes and middleware
42
+ - EJS or HTML views
43
+ - CSS starter file and public assets
44
+
45
+ ## Publish
46
+
47
+ This package is ready for `npm link`, `npm publish`, and `npx boilerbase`.
48
+
49
+ If your npm account has 2FA enabled, publish with an OTP:
50
+
51
+ ```bash
52
+ npm publish --otp=123456
53
+ ```
54
+
55
+ Or create a granular access token with `bypass 2fa` enabled and use that token for publishing.
package/bin/index.js ADDED
@@ -0,0 +1,111 @@
1
+ #!/usr/bin/env node
2
+
3
+ const path = require("path");
4
+ const inquirer = require("inquirer");
5
+ const chalk = require("chalk");
6
+ const ora = require("ora");
7
+
8
+ const createProject = require("../generators/createProject");
9
+ const installDependencies = require("../generators/installDependencies");
10
+ const openVSCode = require("../generators/openVSCode");
11
+ const { sanitizeProjectName } = require("../utils/pathUtils");
12
+
13
+ const prompts = [
14
+ {
15
+ type: "input",
16
+ name: "projectName",
17
+ message: "Project name",
18
+ default: "my-backend-app",
19
+ validate: (value) => (value.trim() ? true : "Project name is required."),
20
+ },
21
+ {
22
+ type: "list",
23
+ name: "databaseChoice",
24
+ message: "Select database",
25
+ choices: ["MongoDB", "None"],
26
+ default: "MongoDB",
27
+ },
28
+ {
29
+ type: "list",
30
+ name: "authenticationType",
31
+ message: "Select authentication",
32
+ choices: ["JWT", "None"],
33
+ default: "JWT",
34
+ },
35
+ {
36
+ type: "list",
37
+ name: "templateEngine",
38
+ message: "Select template engine",
39
+ choices: ["EJS", "HTML"],
40
+ default: "EJS",
41
+ },
42
+ {
43
+ type: "list",
44
+ name: "cssFramework",
45
+ message: "Select CSS framework",
46
+ choices: ["Vanilla CSS", "Bootstrap"],
47
+ default: "Vanilla CSS",
48
+ },
49
+ {
50
+ type: "confirm",
51
+ name: "openInVSCode",
52
+ message: "Open the generated folder in another VS Code Tab?",
53
+ default: true,
54
+ },
55
+ ];
56
+
57
+ const run = async () => {
58
+ console.log(chalk.green("\n✔ Welcome to BoilerBase\n"));
59
+
60
+ const answers = await inquirer.prompt(prompts);
61
+ const projectName = sanitizeProjectName(answers.projectName);
62
+ const projectPath = path.join(process.cwd(), projectName);
63
+
64
+ const generationSpinner = ora("Generating folders and files...").start();
65
+
66
+ try {
67
+ await createProject({
68
+ projectName: answers.projectName,
69
+ displayProjectName: answers.projectName.trim(),
70
+ databaseChoice: answers.databaseChoice,
71
+ authenticationType: answers.authenticationType,
72
+ templateEngine: answers.templateEngine,
73
+ cssFramework: answers.cssFramework,
74
+ includeMVCStructure: true,
75
+ includeAuthSystem: answers.authenticationType === "JWT",
76
+ includeREADME: true,
77
+ targetDirectory: process.cwd(),
78
+ });
79
+
80
+ generationSpinner.succeed("Generating folders and files...");
81
+
82
+ const installSpinner = ora("Installing dependencies...").start();
83
+ await installDependencies(projectPath);
84
+ installSpinner.succeed("Installing dependencies...");
85
+
86
+ if (answers.openInVSCode) {
87
+ const openSpinner = ora("Opening VS Code...").start();
88
+
89
+ try {
90
+ await openVSCode(projectPath);
91
+ openSpinner.succeed("Opening VS Code...");
92
+ } catch (error) {
93
+ openSpinner.warn("Opening VS Code skipped");
94
+ console.log(chalk.yellow(`\n! ${error.message}`));
95
+ }
96
+ } else {
97
+ console.log(chalk.yellow("\n! Skipped opening VS Code."));
98
+ }
99
+
100
+ console.log(chalk.green("\n✔ Project ready successfully"));
101
+ console.log(chalk.cyan("\nNext steps:"));
102
+ console.log(chalk.white(`cd ${projectName}`));
103
+ console.log(chalk.white("npm run dev\n"));
104
+ } catch (error) {
105
+ generationSpinner.fail("Generation failed");
106
+ console.error(chalk.red(`\n✖ ${error.message}`));
107
+ process.exit(1);
108
+ }
109
+ };
110
+
111
+ run();
@@ -0,0 +1,13 @@
1
+ const {
2
+ buildAuthMiddlewareTemplate,
3
+ buildAuthControllerTemplate,
4
+ buildAuthRoutesTemplate,
5
+ buildUserModelTemplate,
6
+ } = require("./templates");
7
+
8
+ module.exports = {
9
+ buildAuthMiddlewareTemplate,
10
+ buildAuthControllerTemplate,
11
+ buildAuthRoutesTemplate,
12
+ buildUserModelTemplate,
13
+ };
@@ -0,0 +1,3 @@
1
+ const { buildEnvTemplate } = require("./templates");
2
+
3
+ module.exports = buildEnvTemplate;
@@ -0,0 +1,71 @@
1
+ const fs = require("fs-extra");
2
+ const path = require("path");
3
+
4
+ const {
5
+ buildPackageTemplate,
6
+ buildEnvTemplate,
7
+ buildDbTemplate,
8
+ buildHomeControllerTemplate,
9
+ buildIndexRouteTemplate,
10
+ buildServerTemplate,
11
+ buildAuthMiddlewareTemplate,
12
+ buildUserModelTemplate,
13
+ buildAuthControllerTemplate,
14
+ buildAuthRoutesTemplate,
15
+ buildIndexViewTemplate,
16
+ buildLoginViewTemplate,
17
+ buildRegisterViewTemplate,
18
+ buildReadmeTemplate,
19
+ buildPublicCssTemplate,
20
+ buildPublicJsTemplate,
21
+ getViewExtension,
22
+ isAuthEnabled,
23
+ isMongoDB,
24
+ isMVC,
25
+ } = require("./templates");
26
+
27
+ const writeFile = async (filePath, contents) => {
28
+ await fs.ensureDir(path.dirname(filePath));
29
+ await fs.writeFile(filePath, contents, "utf8");
30
+ };
31
+
32
+ const createFiles = async (projectPath, options) => {
33
+ const viewExtension = getViewExtension(options);
34
+
35
+ await writeFile(path.join(projectPath, "server.js"), buildServerTemplate(options));
36
+ await writeFile(path.join(projectPath, "package.json"), buildPackageTemplate(options));
37
+ await writeFile(path.join(projectPath, ".env"), buildEnvTemplate(options));
38
+ await writeFile(path.join(projectPath, "config", "db.js"), buildDbTemplate(options));
39
+ await writeFile(path.join(projectPath, "public", "css", "style.css"), buildPublicCssTemplate(options));
40
+ await writeFile(path.join(projectPath, "public", "js", "main.js"), buildPublicJsTemplate(options));
41
+
42
+ if (isMVC(options)) {
43
+ await writeFile(path.join(projectPath, "controllers", "homeController.js"), buildHomeControllerTemplate(options));
44
+ await writeFile(path.join(projectPath, "routes", "index.js"), buildIndexRouteTemplate(options));
45
+ }
46
+
47
+ if (isAuthEnabled(options)) {
48
+ await writeFile(path.join(projectPath, "middleware", "authMiddleware.js"), buildAuthMiddlewareTemplate(options));
49
+ await writeFile(path.join(projectPath, "controllers", "authController.js"), buildAuthControllerTemplate(options));
50
+ await writeFile(path.join(projectPath, "routes", "authRoutes.js"), buildAuthRoutesTemplate(options));
51
+
52
+ if (isMongoDB(options)) {
53
+ await writeFile(path.join(projectPath, "models", "User.js"), buildUserModelTemplate(options));
54
+ }
55
+ }
56
+
57
+ await writeFile(path.join(projectPath, "views", `index.${viewExtension}`), buildIndexViewTemplate(options));
58
+
59
+ if (isAuthEnabled(options)) {
60
+ await writeFile(path.join(projectPath, "views", `login.${viewExtension}`), buildLoginViewTemplate(options));
61
+ await writeFile(path.join(projectPath, "views", `register.${viewExtension}`), buildRegisterViewTemplate(options));
62
+ }
63
+
64
+ if (options.includeREADME) {
65
+ await writeFile(path.join(projectPath, "README.md"), buildReadmeTemplate(options));
66
+ }
67
+
68
+ return projectPath;
69
+ };
70
+
71
+ module.exports = createFiles;
@@ -0,0 +1,20 @@
1
+ const fs = require("fs-extra");
2
+ const path = require("path");
3
+
4
+ const { PROJECT_ROOT_FOLDERS } = require("../utils/constants");
5
+
6
+ const createFolders = async (projectPath, options = {}) => {
7
+ const folders = [...PROJECT_ROOT_FOLDERS];
8
+
9
+ for (const folder of folders) {
10
+ await fs.ensureDir(path.join(projectPath, folder));
11
+ }
12
+
13
+ if (options.includeMVCStructure) {
14
+ await fs.ensureDir(path.join(projectPath, "config"));
15
+ }
16
+
17
+ return projectPath;
18
+ };
19
+
20
+ module.exports = createFolders;
@@ -0,0 +1,3 @@
1
+ const { buildDbTemplate } = require("./templates");
2
+
3
+ module.exports = buildDbTemplate;
@@ -0,0 +1,3 @@
1
+ const { buildPackageTemplate } = require("./templates");
2
+
3
+ module.exports = buildPackageTemplate;
@@ -0,0 +1,35 @@
1
+ const fs = require("fs-extra");
2
+
3
+ const createFolders = require("./createFolders");
4
+ const createFiles = require("./createFiles");
5
+ const { getProjectPath, sanitizeProjectName } = require("../utils/pathUtils");
6
+
7
+ const createProject = async (options) => {
8
+ const displayProjectName = options.projectName.trim();
9
+ const projectName = sanitizeProjectName(displayProjectName);
10
+ const targetDirectory = options.targetDirectory || process.cwd();
11
+ const projectPath = getProjectPath(targetDirectory, projectName);
12
+
13
+ if (await fs.pathExists(projectPath)) {
14
+ const contents = await fs.readdir(projectPath);
15
+
16
+ if (contents.length > 0) {
17
+ throw new Error(
18
+ `The folder "${projectName}" already exists and is not empty.`,
19
+ );
20
+ }
21
+ }
22
+
23
+ await fs.ensureDir(projectPath);
24
+
25
+ await createFolders(projectPath, options);
26
+ await createFiles(projectPath, {
27
+ ...options,
28
+ projectName,
29
+ displayProjectName,
30
+ });
31
+
32
+ return projectPath;
33
+ };
34
+
35
+ module.exports = createProject;
@@ -0,0 +1,9 @@
1
+ const {
2
+ buildPublicCssTemplate,
3
+ buildPublicJsTemplate,
4
+ } = require("./templates");
5
+
6
+ module.exports = {
7
+ buildPublicCssTemplate,
8
+ buildPublicJsTemplate,
9
+ };
@@ -0,0 +1,3 @@
1
+ const { buildReadmeTemplate } = require("./templates");
2
+
3
+ module.exports = buildReadmeTemplate;
@@ -0,0 +1,3 @@
1
+ const { buildServerTemplate } = require("./templates");
2
+
3
+ module.exports = buildServerTemplate;
@@ -0,0 +1,11 @@
1
+ const {
2
+ buildIndexViewTemplate,
3
+ buildLoginViewTemplate,
4
+ buildRegisterViewTemplate,
5
+ } = require("./templates");
6
+
7
+ module.exports = {
8
+ buildIndexViewTemplate,
9
+ buildLoginViewTemplate,
10
+ buildRegisterViewTemplate,
11
+ };
@@ -0,0 +1,22 @@
1
+ const { spawn } = require("child_process");
2
+
3
+ const installDependencies = (projectPath) =>
4
+ new Promise((resolve, reject) => {
5
+ const child = spawn("npm", ["install"], {
6
+ cwd: projectPath,
7
+ stdio: "inherit",
8
+ shell: true,
9
+ });
10
+
11
+ child.on("error", reject);
12
+ child.on("close", (code) => {
13
+ if (code === 0) {
14
+ resolve();
15
+ return;
16
+ }
17
+
18
+ reject(new Error(`npm install failed with exit code ${code}`));
19
+ });
20
+ });
21
+
22
+ module.exports = installDependencies;
@@ -0,0 +1,18 @@
1
+ const { spawn } = require("child_process");
2
+
3
+ const openVSCode = (projectPath) =>
4
+ new Promise((resolve, reject) => {
5
+ const child = spawn("code", [projectPath], {
6
+ stdio: "ignore",
7
+ shell: true,
8
+ detached: true,
9
+ });
10
+
11
+ child.once("error", reject);
12
+ child.once("spawn", () => {
13
+ child.unref();
14
+ resolve();
15
+ });
16
+ });
17
+
18
+ module.exports = openVSCode;
@@ -0,0 +1,260 @@
1
+ const { sanitizeProjectName, toPackageName } = require("../utils/pathUtils");
2
+
3
+ const isMongoDB = (options) => options.databaseChoice === "MongoDB";
4
+ const isAuthEnabled = (options) =>
5
+ options.includeAuthSystem && options.authenticationType === "JWT";
6
+ const isEJS = (options) => options.templateEngine === "EJS";
7
+ const isBootstrap = (options) => options.cssFramework === "Bootstrap";
8
+ const isMVC = (options) => options.includeMVCStructure;
9
+
10
+ const getViewExtension = (options) => (isEJS(options) ? "ejs" : "html");
11
+
12
+ const buildPackageTemplate = (options) => {
13
+ const dependencies = {
14
+ express: "^5.1.0",
15
+ dotenv: "^16.5.0",
16
+ };
17
+
18
+ if (isEJS(options)) dependencies.ejs = "^3.1.10";
19
+ if (isMongoDB(options)) dependencies.mongoose = "^8.15.0";
20
+ if (isAuthEnabled(options)) {
21
+ dependencies.jsonwebtoken = "^9.0.2";
22
+ dependencies.bcryptjs = "^3.0.2";
23
+ }
24
+
25
+ return JSON.stringify(
26
+ {
27
+ name: toPackageName(options.projectName),
28
+ version: "1.0.0",
29
+ description: `Starter project generated for ${options.displayProjectName || options.projectName}`,
30
+ main: "server.js",
31
+ scripts: {
32
+ start: "node server.js",
33
+ dev: "nodemon server.js",
34
+ },
35
+ dependencies,
36
+ devDependencies: {
37
+ nodemon: "^3.1.10",
38
+ },
39
+ },
40
+ null,
41
+ 2,
42
+ );
43
+ };
44
+
45
+ const buildEnvTemplate = (options) => {
46
+ const lines = ["PORT=3000", "NODE_ENV=development"];
47
+
48
+ if (isMongoDB(options)) {
49
+ lines.push(
50
+ `MONGO_URI=mongodb://127.0.0.1:27017/${toPackageName(options.projectName)}`,
51
+ );
52
+ }
53
+
54
+ if (isAuthEnabled(options)) {
55
+ lines.push("JWT_SECRET=replace-with-a-strong-secret");
56
+ lines.push("JWT_EXPIRES_IN=7d");
57
+ }
58
+
59
+ return `${lines.join("\n")}\n`;
60
+ };
61
+
62
+ const buildDbTemplate = (options) => {
63
+ if (!isMongoDB(options)) {
64
+ return `module.exports = async function connectDB() {\n console.log('Database not configured. Set up your own data layer when ready.');\n};\n`;
65
+ }
66
+
67
+ return `const mongoose = require('mongoose');\n\nconst connectDB = async () => {\n try {\n await mongoose.connect(process.env.MONGO_URI);\n console.log('MongoDB connected successfully');\n } catch (error) {\n console.error('MongoDB connection error:', error.message);\n process.exit(1);\n }\n};\n\nmodule.exports = connectDB;\n`;
68
+ };
69
+
70
+ const buildHomeControllerTemplate = (options) => {
71
+ if (isEJS(options)) {
72
+ return `const renderHome = (req, res) => {\n res.render('index', {\n pageTitle: 'BoilerBase',\n featureLabel: 'Developer-ready starter project',\n });\n};\n\nmodule.exports = {\n renderHome,\n};\n`;
73
+ }
74
+
75
+ return `const path = require('path');\n\nconst renderHome = (req, res) => {\n res.sendFile(path.join(__dirname, '../views/index.html'));\n};\n\nmodule.exports = {\n renderHome,\n};\n`;
76
+ };
77
+
78
+ const buildIndexRouteTemplate = () => {
79
+ return `const express = require('express');\nconst { renderHome } = require('../controllers/homeController');\n\nconst router = express.Router();\n\nrouter.get('/', renderHome);\n\nmodule.exports = router;\n`;
80
+ };
81
+
82
+ const buildServerTemplate = (options) => {
83
+ const lines = [
84
+ "require('dotenv').config();",
85
+ "const path = require('path');",
86
+ "const express = require('express');",
87
+ ];
88
+
89
+ if (isMongoDB(options)) {
90
+ lines.push("const connectDB = require('./config/db');");
91
+ }
92
+
93
+ if (isMVC(options)) {
94
+ lines.push("const homeRoutes = require('./routes/index');");
95
+ }
96
+
97
+ if (isAuthEnabled(options)) {
98
+ lines.push("const authRoutes = require('./routes/authRoutes');");
99
+ }
100
+
101
+ lines.push(
102
+ "",
103
+ "const app = express();",
104
+ "const PORT = process.env.PORT || 3000;",
105
+ "",
106
+ "app.use(express.urlencoded({ extended: true }));",
107
+ "app.use(express.json());",
108
+ "app.use(express.static(path.join(__dirname, 'public')));",
109
+ );
110
+
111
+ if (isEJS(options)) {
112
+ lines.push(
113
+ "app.set('view engine', 'ejs');",
114
+ "app.set('views', path.join(__dirname, 'views'));",
115
+ );
116
+ }
117
+
118
+ if (isMongoDB(options)) {
119
+ lines.push("", "connectDB();");
120
+ }
121
+
122
+ if (isMVC(options)) {
123
+ lines.push("", "app.use('/', homeRoutes);");
124
+ } else if (isEJS(options)) {
125
+ lines.push(
126
+ "",
127
+ "app.get('/', (req, res) => {",
128
+ " res.render('index', {",
129
+ " pageTitle: 'BoilerBase',",
130
+ " featureLabel: 'Developer-ready starter project',",
131
+ " });",
132
+ "});",
133
+ );
134
+ } else {
135
+ lines.push(
136
+ "",
137
+ "app.get('/', (req, res) => {",
138
+ " res.sendFile(path.join(__dirname, 'views', 'index.html'));",
139
+ "});",
140
+ );
141
+ }
142
+
143
+ if (isAuthEnabled(options)) {
144
+ lines.push("app.use('/auth', authRoutes);");
145
+ }
146
+
147
+ lines.push(
148
+ "",
149
+ "app.get('/health', (req, res) => {",
150
+ " res.json({",
151
+ " status: 'ok',",
152
+ ` project: '${sanitizeProjectName(options.projectName)}',`,
153
+ " });",
154
+ "});",
155
+ "",
156
+ "app.listen(PORT, () => {",
157
+ " console.log('Server running on port ' + PORT);",
158
+ "});",
159
+ );
160
+
161
+ return `${lines.join("\n")}\n`;
162
+ };
163
+
164
+ const buildAuthMiddlewareTemplate = () => {
165
+ return `const jwt = require('jsonwebtoken');\n\nconst protectRoute = (req, res, next) => {\n const authHeader = req.headers.authorization || '';\n const token = authHeader.startsWith('Bearer ') ? authHeader.split(' ')[1] : null;\n\n if (!token) {\n return res.status(401).json({ message: 'Authentication required.' });\n }\n\n try {\n req.user = jwt.verify(token, process.env.JWT_SECRET);\n return next();\n } catch (error) {\n return res.status(401).json({ message: 'Invalid or expired token.' });\n }\n};\n\nmodule.exports = {\n protectRoute,\n};\n`;
166
+ };
167
+
168
+ const buildUserModelTemplate = () => {
169
+ return `const mongoose = require('mongoose');\n\nconst userSchema = new mongoose.Schema(\n {\n name: { type: String, required: true },\n email: { type: String, required: true, unique: true },\n password: { type: String, required: true },\n },\n { timestamps: true }\n);\n\nmodule.exports = mongoose.model('User', userSchema);\n`;
170
+ };
171
+
172
+ const buildAuthControllerTemplate = (options) => {
173
+ if (isMongoDB(options)) {
174
+ return `const bcrypt = require('bcryptjs');\nconst jwt = require('jsonwebtoken');\nconst User = require('../models/User');\n\nconst registerUser = async (req, res) => {\n try {\n const { name, email, password } = req.body;\n const existingUser = await User.findOne({ email });\n\n if (existingUser) {\n return res.status(400).json({ message: 'User already exists.' });\n }\n\n const hashedPassword = await bcrypt.hash(password, 10);\n const user = await User.create({ name, email, password: hashedPassword });\n\n return res.status(201).json({\n message: 'User registered successfully.',\n user: {\n id: user._id,\n name: user.name,\n email: user.email,\n },\n });\n } catch (error) {\n return res.status(500).json({ message: error.message });\n }\n};\n\nconst loginUser = async (req, res) => {\n try {\n const { email, password } = req.body;\n const user = await User.findOne({ email });\n\n if (!user) {\n return res.status(404).json({ message: 'User not found.' });\n }\n\n const passwordMatches = await bcrypt.compare(password, user.password);\n\n if (!passwordMatches) {\n return res.status(401).json({ message: 'Invalid credentials.' });\n }\n\n const token = jwt.sign(\n { id: user._id, email: user.email },\n process.env.JWT_SECRET,\n { expiresIn: process.env.JWT_EXPIRES_IN || '7d' }\n );\n\n return res.json({ token });\n } catch (error) {\n return res.status(500).json({ message: error.message });\n }\n};\n\nmodule.exports = {\n registerUser,\n loginUser,\n};\n`;
175
+ }
176
+
177
+ return `const bcrypt = require('bcryptjs');\nconst jwt = require('jsonwebtoken');\n\nconst demoUsers = [];\n\nconst registerUser = async (req, res) => {\n try {\n const { name, email, password } = req.body;\n const existingUser = demoUsers.find((user) => user.email === email);\n\n if (existingUser) {\n return res.status(400).json({ message: 'User already exists.' });\n }\n\n const hashedPassword = await bcrypt.hash(password, 10);\n const user = {\n id: Date.now().toString(),\n name,\n email,\n password: hashedPassword,\n };\n\n demoUsers.push(user);\n\n return res.status(201).json({\n message: 'User registered successfully.',\n user: {\n id: user.id,\n name: user.name,\n email: user.email,\n },\n });\n } catch (error) {\n return res.status(500).json({ message: error.message });\n }\n};\n\nconst loginUser = async (req, res) => {\n try {\n const { email, password } = req.body;\n const user = demoUsers.find((entry) => entry.email === email);\n\n if (!user) {\n return res.status(404).json({ message: 'User not found.' });\n }\n\n const passwordMatches = await bcrypt.compare(password, user.password);\n\n if (!passwordMatches) {\n return res.status(401).json({ message: 'Invalid credentials.' });\n }\n\n const token = jwt.sign(\n { id: user.id, email: user.email },\n process.env.JWT_SECRET,\n { expiresIn: process.env.JWT_EXPIRES_IN || '7d' }\n );\n\n return res.json({ token });\n } catch (error) {\n return res.status(500).json({ message: error.message });\n }\n};\n\nmodule.exports = {\n registerUser,\n loginUser,\n};\n`;
178
+ };
179
+
180
+ const buildAuthRoutesTemplate = () => {
181
+ return `const express = require('express');\nconst { registerUser, loginUser } = require('../controllers/authController');\nconst { protectRoute } = require('../middleware/authMiddleware');\n\nconst router = express.Router();\n\nrouter.post('/register', registerUser);\nrouter.post('/login', loginUser);\nrouter.get('/profile', protectRoute, (req, res) => {\n res.json({\n message: 'Protected route reached successfully.',\n user: req.user,\n });\n});\n\nmodule.exports = router;\n`;
182
+ };
183
+
184
+ const buildIndexViewTemplate = (options) => {
185
+ const bootstrapLink = isBootstrap(options)
186
+ ? ` <link href='https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css' rel='stylesheet'>\n`
187
+ : "";
188
+ const actionCopy = isEJS(options)
189
+ ? `<span><%= featureLabel %></span>`
190
+ : `<span>Developer-ready starter project</span>`;
191
+
192
+ if (isEJS(options)) {
193
+ return `<!DOCTYPE html>\n<html lang='en'>\n<head>\n <meta charset='UTF-8'>\n <meta name='viewport' content='width=device-width, initial-scale=1.0'>\n <title><%= pageTitle %></title>\n${bootstrapLink} <link rel='stylesheet' href='/css/style.css'>\n</head>\n<body>\n <main class='page-shell'>\n <section class='hero-card'>\n <div class='hero-badge'>BoilerBase Starter</div>\n <h1>Project scaffold ready in seconds.</h1>\n <p>${actionCopy}</p>\n <div class='hero-grid'>\n <article>\n <strong>MongoDB-ready</strong>\n <span>Config, models, and environment setup included.</span>\n </article>\n <article>\n <strong>Auth-friendly</strong>\n <span>JWT routes and middleware are scaffolded when enabled.</span>\n </article>\n <article>\n <strong>Responsive UI</strong>\n <span>Built with a polished, mobile-ready starter layout.</span>\n </article>\n </div>\n </section>\n </main>\n <script src='/js/main.js'></script>\n</body>\n</html>\n`;
194
+ }
195
+
196
+ return `<!DOCTYPE html>\n<html lang='en'>\n<head>\n <meta charset='UTF-8'>\n <meta name='viewport' content='width=device-width, initial-scale=1.0'>\n <title>BoilerBase</title>\n${bootstrapLink} <link rel='stylesheet' href='/css/style.css'>\n</head>\n<body>\n <main class='page-shell'>\n <section class='hero-card'>\n <div class='hero-badge'>BoilerBase Starter</div>\n <h1>Project scaffold ready in seconds.</h1>\n <p>Developer-ready starter project</p>\n <div class='hero-grid'>\n <article>\n <strong>MongoDB-ready</strong>\n <span>Config, models, and environment setup included.</span>\n </article>\n <article>\n <strong>Auth-friendly</strong>\n <span>JWT routes and middleware are scaffolded when enabled.</span>\n </article>\n <article>\n <strong>Responsive UI</strong>\n <span>Built with a polished, mobile-ready starter layout.</span>\n </article>\n </div>\n </section>\n </main>\n <script src='/js/main.js'></script>\n</body>\n</html>\n`;
197
+ };
198
+
199
+ const buildLoginViewTemplate = (options) => {
200
+ if (!isEJS(options)) {
201
+ return `<!DOCTYPE html>\n<html lang='en'>\n<head>\n <meta charset='UTF-8'>\n <meta name='viewport' content='width=device-width, initial-scale=1.0'>\n <title>Login</title>\n <link rel='stylesheet' href='/css/style.css'>\n</head>\n<body>\n <section class='auth-card'>\n <h1>Login</h1>\n <p>Connect this form to your auth API.</p>\n </section>\n</body>\n</html>\n`;
202
+ }
203
+
204
+ return `<section class='auth-card'>\n <h1>Login</h1>\n <p>Connect this form to your auth API.</p>\n</section>\n`;
205
+ };
206
+
207
+ const buildRegisterViewTemplate = (options) => {
208
+ if (!isEJS(options)) {
209
+ return `<!DOCTYPE html>\n<html lang='en'>\n<head>\n <meta charset='UTF-8'>\n <meta name='viewport' content='width=device-width, initial-scale=1.0'>\n <title>Register</title>\n <link rel='stylesheet' href='/css/style.css'>\n</head>\n<body>\n <section class='auth-card'>\n <h1>Register</h1>\n <p>Connect this form to your auth API.</p>\n </section>\n</body>\n</html>\n`;
210
+ }
211
+
212
+ return `<section class='auth-card'>\n <h1>Register</h1>\n <p>Connect this form to your auth API.</p>\n</section>\n`;
213
+ };
214
+
215
+ const buildReadmeTemplate = (options) => {
216
+ const features = [
217
+ "- Express server scaffold",
218
+ `- ${options.databaseChoice} data layer`,
219
+ `- ${options.authenticationType} authentication scaffold`,
220
+ `- ${options.templateEngine} templating`,
221
+ `- ${options.cssFramework} styling`,
222
+ ];
223
+
224
+ return `# ${options.displayProjectName || options.projectName}\n\nGenerated by BoilerBase CLI.\n\n## Included\n${features.join("\n")}\n\n## Run\n\`\`\`bash\nnpm install\nnpm run dev\n\`\`\`\n`;
225
+ };
226
+
227
+ const buildPublicCssTemplate = (options) => {
228
+ const frameworkAccent = isBootstrap(options) ? "#7dd3fc" : "#8b5cf6";
229
+
230
+ return `:root {\n color-scheme: dark;\n --bg: #07111f;\n --panel: rgba(12, 18, 33, 0.88);\n --panel-border: rgba(148, 163, 184, 0.18);\n --text: #e2e8f0;\n --muted: #94a3b8;\n --accent: ${frameworkAccent};\n --accent-2: #22d3ee;\n --shadow: 0 30px 80px rgba(0, 0, 0, 0.35);\n}\n\n* {\n box-sizing: border-box;\n}\n\nbody {\n margin: 0;\n min-height: 100vh;\n font-family: Inter, Segoe UI, Arial, sans-serif;\n color: var(--text);\n background:\n radial-gradient(circle at top left, rgba(34, 211, 238, 0.16), transparent 28%),\n radial-gradient(circle at top right, rgba(139, 92, 246, 0.18), transparent 24%),\n linear-gradient(180deg, #040812 0%, #07111f 100%);\n}\n\nbody::before {\n content: '';\n position: fixed;\n inset: 0;\n background-image: linear-gradient(rgba(255,255,255,0.02) 1px, transparent 1px), linear-gradient(90deg, rgba(255,255,255,0.02) 1px, transparent 1px);\n background-size: 36px 36px;\n pointer-events: none;\n opacity: 0.35;\n}\n\n.page-shell {\n position: relative;\n z-index: 1;\n min-height: 100vh;\n display: grid;\n place-items: center;\n padding: 40px 20px;\n}\n\n.hero-card,\n.auth-card {\n width: min(1100px, 100%);\n background: var(--panel);\n border: 1px solid var(--panel-border);\n box-shadow: var(--shadow);\n backdrop-filter: blur(20px);\n border-radius: 28px;\n padding: clamp(28px, 5vw, 56px);\n}\n\n.hero-badge {\n display: inline-flex;\n align-items: center;\n gap: 8px;\n padding: 10px 14px;\n border-radius: 999px;\n background: rgba(34, 211, 238, 0.12);\n color: #cffafe;\n border: 1px solid rgba(34, 211, 238, 0.22);\n font-size: 0.85rem;\n letter-spacing: 0.08em;\n text-transform: uppercase;\n}\n\nh1 {\n margin: 20px 0 12px;\n font-size: clamp(2.4rem, 5vw, 4.8rem);\n line-height: 0.95;\n}\n\np {\n margin: 0;\n max-width: 60ch;\n color: var(--muted);\n font-size: 1.05rem;\n}\n\n.hero-grid {\n display: grid;\n grid-template-columns: repeat(3, minmax(0, 1fr));\n gap: 18px;\n margin-top: 32px;\n}\n\n.hero-grid article {\n padding: 20px;\n border-radius: 20px;\n background: rgba(15, 23, 42, 0.66);\n border: 1px solid rgba(148, 163, 184, 0.14);\n}\n\n.hero-grid strong {\n display: block;\n margin-bottom: 8px;\n color: white;\n}\n\n.hero-grid span {\n color: var(--muted);\n line-height: 1.6;\n}\n\n@media (max-width: 900px) {\n .hero-grid {\n grid-template-columns: 1fr;\n }\n}\n`;
231
+ };
232
+
233
+ const buildPublicJsTemplate = () => {
234
+ return `document.addEventListener('DOMContentLoaded', () => {\n document.body.classList.add('is-ready');\n\n const cards = document.querySelectorAll('.hero-grid article, .form-card, .stats-card');\n cards.forEach((card, index) => {\n card.style.animationDelay = String(index * 90) + 'ms';\n card.classList.add('reveal');\n });\n});\n`;
235
+ };
236
+
237
+ module.exports = {
238
+ buildPackageTemplate,
239
+ buildEnvTemplate,
240
+ buildDbTemplate,
241
+ buildHomeControllerTemplate,
242
+ buildIndexRouteTemplate,
243
+ buildServerTemplate,
244
+ buildAuthMiddlewareTemplate,
245
+ buildUserModelTemplate,
246
+ buildAuthControllerTemplate,
247
+ buildAuthRoutesTemplate,
248
+ buildIndexViewTemplate,
249
+ buildLoginViewTemplate,
250
+ buildRegisterViewTemplate,
251
+ buildReadmeTemplate,
252
+ buildPublicCssTemplate,
253
+ buildPublicJsTemplate,
254
+ getViewExtension,
255
+ isMongoDB,
256
+ isAuthEnabled,
257
+ isEJS,
258
+ isBootstrap,
259
+ isMVC,
260
+ };
package/package.json ADDED
@@ -0,0 +1,28 @@
1
+ {
2
+ "name": "boilerbase",
3
+ "version": "1.0.1",
4
+ "description": "A minimal developer CLI for generating backend starter projects",
5
+ "main": "./bin/index.js",
6
+ "bin": {
7
+ "boilerbase": "./bin/index.js"
8
+ },
9
+ "publishConfig": {
10
+ "access": "public"
11
+ },
12
+ "files": [
13
+ "bin",
14
+ "generators",
15
+ "templates",
16
+ "utils",
17
+ "README.md"
18
+ ],
19
+ "scripts": {
20
+ "start": "node ./bin/index.js"
21
+ },
22
+ "dependencies": {
23
+ "chalk": "^4.1.2",
24
+ "fs-extra": "^11.3.0",
25
+ "inquirer": "^8.2.7",
26
+ "ora": "^5.4.1"
27
+ }
28
+ }
@@ -0,0 +1,11 @@
1
+ const {
2
+ buildAuthMiddlewareTemplate,
3
+ buildAuthControllerTemplate,
4
+ buildAuthRoutesTemplate,
5
+ } = require("../generators/templates");
6
+
7
+ module.exports = {
8
+ buildAuthMiddlewareTemplate,
9
+ buildAuthControllerTemplate,
10
+ buildAuthRoutesTemplate,
11
+ };
@@ -0,0 +1,5 @@
1
+ const {
2
+ buildPublicCssTemplate,
3
+ } = require("../generators/templates");
4
+
5
+ module.exports = buildPublicCssTemplate;
@@ -0,0 +1,3 @@
1
+ const { buildDbTemplate } = require("../generators/templates");
2
+
3
+ module.exports = buildDbTemplate;
@@ -0,0 +1,3 @@
1
+ const { buildEnvTemplate } = require("../generators/templates");
2
+
3
+ module.exports = buildEnvTemplate;
@@ -0,0 +1,3 @@
1
+ const { buildUserModelTemplate } = require("../generators/templates");
2
+
3
+ module.exports = buildUserModelTemplate;
@@ -0,0 +1,3 @@
1
+ const { buildPackageTemplate } = require("../generators/templates");
2
+
3
+ module.exports = buildPackageTemplate;
@@ -0,0 +1,3 @@
1
+ const { buildReadmeTemplate } = require("../generators/templates");
2
+
3
+ module.exports = buildReadmeTemplate;
@@ -0,0 +1,9 @@
1
+ const {
2
+ buildIndexRouteTemplate,
3
+ buildAuthRoutesTemplate,
4
+ } = require("../generators/templates");
5
+
6
+ module.exports = {
7
+ buildIndexRouteTemplate,
8
+ buildAuthRoutesTemplate,
9
+ };
@@ -0,0 +1,3 @@
1
+ const { buildServerTemplate } = require("../generators/templates");
2
+
3
+ module.exports = buildServerTemplate;
@@ -0,0 +1,11 @@
1
+ const {
2
+ buildIndexViewTemplate,
3
+ buildLoginViewTemplate,
4
+ buildRegisterViewTemplate,
5
+ } = require("../generators/templates");
6
+
7
+ module.exports = {
8
+ buildIndexViewTemplate,
9
+ buildLoginViewTemplate,
10
+ buildRegisterViewTemplate,
11
+ };
@@ -0,0 +1,23 @@
1
+ const PROJECT_ROOT_FOLDERS = [
2
+ "config",
3
+ "controllers",
4
+ "middleware",
5
+ "models",
6
+ "routes",
7
+ "views",
8
+ "public",
9
+ "public/css",
10
+ "public/js",
11
+ ];
12
+
13
+ const FORM_DEFAULTS = {
14
+ databaseChoice: "MongoDB",
15
+ authenticationType: "JWT",
16
+ templateEngine: "EJS",
17
+ cssFramework: "Vanilla CSS",
18
+ };
19
+
20
+ module.exports = {
21
+ PROJECT_ROOT_FOLDERS,
22
+ FORM_DEFAULTS,
23
+ };
@@ -0,0 +1,29 @@
1
+ const { FORM_DEFAULTS } = require("./constants");
2
+
3
+ const normalizeOptions = (body = {}) => {
4
+ const projectName = (body.projectName || "").trim();
5
+
6
+ return {
7
+ projectName,
8
+ databaseChoice: body.databaseChoice || FORM_DEFAULTS.databaseChoice,
9
+ authenticationType: body.authenticationType || FORM_DEFAULTS.authenticationType,
10
+ templateEngine: body.templateEngine || FORM_DEFAULTS.templateEngine,
11
+ cssFramework: body.cssFramework || FORM_DEFAULTS.cssFramework,
12
+ includeMVCStructure: body.includeMVCStructure === "on" || body.includeMVCStructure === "true",
13
+ includeAuthSystem: body.includeAuthSystem === "on" || body.includeAuthSystem === "true",
14
+ includeREADME: body.includeREADME === "on" || body.includeREADME === "true",
15
+ };
16
+ };
17
+
18
+ const validateOptions = (options) => {
19
+ if (!options.projectName) {
20
+ return "Project name is required.";
21
+ }
22
+
23
+ return null;
24
+ };
25
+
26
+ module.exports = {
27
+ normalizeOptions,
28
+ validateOptions,
29
+ };
@@ -0,0 +1,23 @@
1
+ const path = require("path");
2
+
3
+ const sanitizeProjectName = (value = "") => {
4
+ const cleaned = value
5
+ .trim()
6
+ .replace(/[^a-zA-Z0-9\s-_]/g, "")
7
+ .replace(/\s+/g, "-")
8
+ .replace(/-+/g, "-")
9
+ .replace(/^-|-$/g, "");
10
+
11
+ return cleaned || "my-app";
12
+ };
13
+
14
+ const toPackageName = (value = "") => sanitizeProjectName(value).toLowerCase();
15
+
16
+ const getProjectPath = (baseDir, projectName) =>
17
+ path.join(baseDir, sanitizeProjectName(projectName));
18
+
19
+ module.exports = {
20
+ sanitizeProjectName,
21
+ toPackageName,
22
+ getProjectPath,
23
+ };