starter-structure-cli 0.2.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/package.json ADDED
@@ -0,0 +1,34 @@
1
+ {
2
+ "name": "starter-structure-cli",
3
+ "version": "0.2.0",
4
+ "description": "Scaffold starter projects from your own stack-based template folders",
5
+ "type": "module",
6
+ "files": [
7
+ "bin",
8
+ "scripts",
9
+ "templates",
10
+ "README.md"
11
+ ],
12
+ "scripts": {
13
+ "check:templates": "node ./scripts/check-templates.js",
14
+ "prepack": "node ./scripts/check-templates.js"
15
+ },
16
+ "bin": {
17
+ "starter-structure-cli": "bin/starter-structure-cli.js"
18
+ },
19
+ "keywords": [
20
+ "cli",
21
+ "scaffold",
22
+ "starter",
23
+ "template",
24
+ "boilerplate"
25
+ ],
26
+ "engines": {
27
+ "node": ">=18"
28
+ },
29
+ "dependencies": {
30
+ "@clack/prompts": "^0.11.0",
31
+ "picocolors": "^1.1.1"
32
+ },
33
+ "license": "MIT"
34
+ }
@@ -0,0 +1,66 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+
5
+ const __filename = fileURLToPath(import.meta.url);
6
+ const __dirname = path.dirname(__filename);
7
+ const templatesRoot = path.resolve(__dirname, "..", "templates");
8
+
9
+ function hasAnyFile(dir) {
10
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
11
+
12
+ for (const entry of entries) {
13
+ const entryPath = path.join(dir, entry.name);
14
+ if (entry.isFile()) {
15
+ return true;
16
+ }
17
+
18
+ if (entry.isDirectory() && hasAnyFile(entryPath)) {
19
+ return true;
20
+ }
21
+ }
22
+
23
+ return false;
24
+ }
25
+
26
+ function getTemplateDirs(rootDir) {
27
+ if (!fs.existsSync(rootDir)) {
28
+ return [];
29
+ }
30
+
31
+ const templateDirs = [];
32
+ const categories = fs
33
+ .readdirSync(rootDir, { withFileTypes: true })
34
+ .filter((entry) => entry.isDirectory())
35
+ .map((entry) => entry.name);
36
+
37
+ for (const category of categories) {
38
+ const categoryDir = path.join(rootDir, category);
39
+ const starters = fs
40
+ .readdirSync(categoryDir, { withFileTypes: true })
41
+ .filter((entry) => entry.isDirectory())
42
+ .map((entry) => path.join(categoryDir, entry.name));
43
+
44
+ templateDirs.push(...starters);
45
+ }
46
+
47
+ return templateDirs;
48
+ }
49
+
50
+ const emptyTemplates = getTemplateDirs(templatesRoot)
51
+ .filter((templateDir) => !hasAnyFile(templateDir))
52
+ .map((templateDir) => path.relative(templatesRoot, templateDir));
53
+
54
+ if (emptyTemplates.length > 0) {
55
+ console.error("Template validation failed.");
56
+ console.error("These template directories do not contain any files:");
57
+ for (const templateDir of emptyTemplates) {
58
+ console.error(`- ${templateDir}`);
59
+ }
60
+ console.error("");
61
+ console.error("Add the real starter files before publishing.");
62
+ console.error("If a template intentionally contains only empty folders, add a .gitkeep file.");
63
+ process.exit(1);
64
+ }
65
+
66
+ console.log(`Template validation passed for ${getTemplateDirs(templatesRoot).length} templates.`);
@@ -0,0 +1,6 @@
1
+ PORT=5000
2
+ NODE_ENV=development
3
+ MONGODB_URI=mongodb://127.0.0.1:27017/__APP_NAME__
4
+ JWT_SECRET=change-this-secret
5
+ JWT_EXPIRES_IN=7d
6
+ CLIENT_URL=http://localhost:5173
@@ -0,0 +1,43 @@
1
+ # __APP_NAME__
2
+
3
+ Express + Mongoose + JWT starter API.
4
+
5
+ ## Features
6
+
7
+ - User registration and login
8
+ - Password hashing with `bcryptjs`
9
+ - JWT authentication middleware
10
+ - MongoDB connection with Mongoose
11
+ - Structured route/controller/model layout
12
+ - Standard API response helpers
13
+
14
+ ## Project structure
15
+
16
+ ```text
17
+ .
18
+ ├── config/
19
+ ├── controllers/
20
+ ├── middleware/
21
+ ├── models/
22
+ ├── routes/
23
+ ├── utils/
24
+ ├── .env.example
25
+ ├── .gitignore
26
+ ├── index.js
27
+ └── package.json
28
+ ```
29
+
30
+ ## Getting started
31
+
32
+ ```bash
33
+ npm install
34
+ cp .env.example .env
35
+ npm run dev
36
+ ```
37
+
38
+ ## API endpoints
39
+
40
+ - `GET /api/health`
41
+ - `POST /api/auth/register`
42
+ - `POST /api/auth/login`
43
+ - `GET /api/auth/me`
@@ -0,0 +1,19 @@
1
+ const mongoose = require("mongoose");
2
+
3
+ async function connectDB() {
4
+ const mongoURI = process.env.MONGODB_URI;
5
+
6
+ if (!mongoURI) {
7
+ throw new Error("MONGODB_URI is not defined in environment variables.");
8
+ }
9
+
10
+ try {
11
+ await mongoose.connect(mongoURI);
12
+ console.log("MongoDB connected");
13
+ } catch (error) {
14
+ console.error("MongoDB connection failed:", error.message);
15
+ process.exit(1);
16
+ }
17
+ }
18
+
19
+ module.exports = connectDB;
@@ -0,0 +1,91 @@
1
+ const User = require("../../models/user");
2
+ const { successResponse, errorResponse } = require("../../utils/api-response");
3
+ const generateToken = require("../../utils/generate-token");
4
+
5
+ async function register(req, res, next) {
6
+ try {
7
+ const { name, email, password } = req.body;
8
+
9
+ if (!name || !email || !password) {
10
+ return errorResponse(res, "Name, email, and password are required.", 400);
11
+ }
12
+
13
+ const existingUser = await User.findOne({ email: email.toLowerCase() });
14
+ if (existingUser) {
15
+ return errorResponse(res, "User already exists with this email.", 409);
16
+ }
17
+
18
+ const user = await User.create({
19
+ name,
20
+ email: email.toLowerCase(),
21
+ password
22
+ });
23
+
24
+ const token = generateToken(user._id);
25
+
26
+ return successResponse(
27
+ res,
28
+ {
29
+ user,
30
+ token
31
+ },
32
+ "Registration successful.",
33
+ 201
34
+ );
35
+ } catch (error) {
36
+ next(error);
37
+ }
38
+ }
39
+
40
+ async function login(req, res, next) {
41
+ try {
42
+ const { email, password } = req.body;
43
+
44
+ if (!email || !password) {
45
+ return errorResponse(res, "Email and password are required.", 400);
46
+ }
47
+
48
+ const user = await User.findOne({ email: email.toLowerCase() });
49
+ if (!user) {
50
+ return errorResponse(res, "Invalid email or password.", 401);
51
+ }
52
+
53
+ const isPasswordValid = await user.comparePassword(password);
54
+ if (!isPasswordValid) {
55
+ return errorResponse(res, "Invalid email or password.", 401);
56
+ }
57
+
58
+ const token = generateToken(user._id);
59
+
60
+ return successResponse(
61
+ res,
62
+ {
63
+ user,
64
+ token
65
+ },
66
+ "Login successful."
67
+ );
68
+ } catch (error) {
69
+ next(error);
70
+ }
71
+ }
72
+
73
+ async function me(req, res, next) {
74
+ try {
75
+ const user = await User.findById(req.user.id);
76
+
77
+ if (!user) {
78
+ return errorResponse(res, "User not found.", 404);
79
+ }
80
+
81
+ return successResponse(res, { user }, "Authenticated user fetched.");
82
+ } catch (error) {
83
+ next(error);
84
+ }
85
+ }
86
+
87
+ module.exports = {
88
+ register,
89
+ login,
90
+ me
91
+ };
@@ -0,0 +1,44 @@
1
+ const express = require("express");
2
+ const cors = require("cors");
3
+ const dotenv = require("dotenv");
4
+ const helmet = require("helmet");
5
+ const morgan = require("morgan");
6
+
7
+ const connectDB = require("./config/db");
8
+ const routes = require("./routes");
9
+ const notFound = require("./middleware/not-found");
10
+ const errorHandler = require("./middleware/error-handler");
11
+
12
+ dotenv.config();
13
+
14
+ const app = express();
15
+ const PORT = process.env.PORT || 5000;
16
+
17
+ connectDB();
18
+
19
+ app.use(
20
+ cors({
21
+ origin: process.env.CLIENT_URL || "*",
22
+ credentials: true
23
+ })
24
+ );
25
+ app.use(helmet());
26
+ app.use(morgan("dev"));
27
+ app.use(express.json());
28
+ app.use(express.urlencoded({ extended: true }));
29
+
30
+ app.get("/", (_req, res) => {
31
+ res.json({
32
+ success: true,
33
+ message: "__APP_NAME__ API is running"
34
+ });
35
+ });
36
+
37
+ app.use("/api", routes);
38
+
39
+ app.use(notFound);
40
+ app.use(errorHandler);
41
+
42
+ app.listen(PORT, () => {
43
+ console.log(`Server running on port ${PORT}`);
44
+ });
@@ -0,0 +1,35 @@
1
+ const jwt = require("jsonwebtoken");
2
+
3
+ const User = require("../models/user");
4
+ const { errorResponse } = require("../utils/api-response");
5
+
6
+ async function authenticate(req, res, next) {
7
+ try {
8
+ const authHeader = req.headers.authorization;
9
+
10
+ if (!authHeader || !authHeader.startsWith("Bearer ")) {
11
+ return errorResponse(res, "Unauthorized access.", 401);
12
+ }
13
+
14
+ const token = authHeader.split(" ")[1];
15
+ const decoded = jwt.verify(token, process.env.JWT_SECRET);
16
+
17
+ const user = await User.findById(decoded.id).select("_id name email role");
18
+ if (!user) {
19
+ return errorResponse(res, "User not found.", 401);
20
+ }
21
+
22
+ req.user = {
23
+ id: user._id.toString(),
24
+ name: user.name,
25
+ email: user.email,
26
+ role: user.role
27
+ };
28
+
29
+ next();
30
+ } catch (_error) {
31
+ return errorResponse(res, "Invalid or expired token.", 401);
32
+ }
33
+ }
34
+
35
+ module.exports = authenticate;
@@ -0,0 +1,16 @@
1
+ const { errorResponse } = require("../utils/api-response");
2
+
3
+ function errorHandler(error, _req, res, _next) {
4
+ const statusCode = error.statusCode || 500;
5
+ const message = error.message || "Internal server error.";
6
+
7
+ if (process.env.NODE_ENV !== "test") {
8
+ console.error(error);
9
+ }
10
+
11
+ return errorResponse(res, message, statusCode, {
12
+ stack: process.env.NODE_ENV === "development" ? error.stack : undefined
13
+ });
14
+ }
15
+
16
+ module.exports = errorHandler;
@@ -0,0 +1,7 @@
1
+ const { errorResponse } = require("../utils/api-response");
2
+
3
+ function notFound(req, res) {
4
+ return errorResponse(res, `Route not found: ${req.originalUrl}`, 404);
5
+ }
6
+
7
+ module.exports = notFound;
@@ -0,0 +1,54 @@
1
+ const bcrypt = require("bcryptjs");
2
+ const mongoose = require("mongoose");
3
+
4
+ const userSchema = new mongoose.Schema(
5
+ {
6
+ name: {
7
+ type: String,
8
+ required: true,
9
+ trim: true
10
+ },
11
+ email: {
12
+ type: String,
13
+ required: true,
14
+ unique: true,
15
+ lowercase: true,
16
+ trim: true
17
+ },
18
+ password: {
19
+ type: String,
20
+ required: true,
21
+ minlength: 6
22
+ },
23
+ role: {
24
+ type: String,
25
+ enum: ["admin", "user"],
26
+ default: "user"
27
+ }
28
+ },
29
+ {
30
+ timestamps: true
31
+ }
32
+ );
33
+
34
+ userSchema.pre("save", async function preSave(next) {
35
+ if (!this.isModified("password")) {
36
+ return next();
37
+ }
38
+
39
+ const salt = await bcrypt.genSalt(10);
40
+ this.password = await bcrypt.hash(this.password, salt);
41
+ next();
42
+ });
43
+
44
+ userSchema.methods.comparePassword = function comparePassword(candidatePassword) {
45
+ return bcrypt.compare(candidatePassword, this.password);
46
+ };
47
+
48
+ userSchema.methods.toJSON = function toJSON() {
49
+ const userObject = this.toObject();
50
+ delete userObject.password;
51
+ return userObject;
52
+ };
53
+
54
+ module.exports = mongoose.model("User", userSchema);
@@ -0,0 +1,30 @@
1
+ {
2
+ "name": "__APP_NAME__",
3
+ "version": "1.0.0",
4
+ "description": "Express + Mongoose + JWT starter API",
5
+ "main": "index.js",
6
+ "scripts": {
7
+ "dev": "nodemon index.js",
8
+ "start": "node index.js"
9
+ },
10
+ "keywords": [
11
+ "express",
12
+ "mongoose",
13
+ "jwt",
14
+ "api"
15
+ ],
16
+ "license": "MIT",
17
+ "dependencies": {
18
+ "bcryptjs": "^2.4.3",
19
+ "cors": "^2.8.5",
20
+ "dotenv": "^16.4.7",
21
+ "express": "^4.21.2",
22
+ "helmet": "^8.0.0",
23
+ "jsonwebtoken": "^9.0.2",
24
+ "mongoose": "^8.9.5",
25
+ "morgan": "^1.10.0"
26
+ },
27
+ "devDependencies": {
28
+ "nodemon": "^3.1.9"
29
+ }
30
+ }
@@ -0,0 +1,12 @@
1
+ const express = require("express");
2
+
3
+ const { login, me, register } = require("../../controllers/auth");
4
+ const authenticate = require("../../middleware/authenticate");
5
+
6
+ const router = express.Router();
7
+
8
+ router.post("/register", register);
9
+ router.post("/login", login);
10
+ router.get("/me", authenticate, me);
11
+
12
+ module.exports = router;
@@ -0,0 +1,16 @@
1
+ const express = require("express");
2
+
3
+ const authRoutes = require("./auth");
4
+
5
+ const router = express.Router();
6
+
7
+ router.get("/health", (_req, res) => {
8
+ res.json({
9
+ success: true,
10
+ message: "Server is healthy"
11
+ });
12
+ });
13
+
14
+ router.use("/auth", authRoutes);
15
+
16
+ module.exports = router;
@@ -0,0 +1,20 @@
1
+ function successResponse(res, data = null, message = "Success", statusCode = 200) {
2
+ return res.status(statusCode).json({
3
+ success: true,
4
+ message,
5
+ data
6
+ });
7
+ }
8
+
9
+ function errorResponse(res, message = "Something went wrong", statusCode = 500, errors = null) {
10
+ return res.status(statusCode).json({
11
+ success: false,
12
+ message,
13
+ errors
14
+ });
15
+ }
16
+
17
+ module.exports = {
18
+ successResponse,
19
+ errorResponse
20
+ };
@@ -0,0 +1,9 @@
1
+ const jwt = require("jsonwebtoken");
2
+
3
+ function generateToken(userId) {
4
+ return jwt.sign({ id: userId }, process.env.JWT_SECRET, {
5
+ expiresIn: process.env.JWT_EXPIRES_IN || "7d"
6
+ });
7
+ }
8
+
9
+ module.exports = generateToken;