neatnode 1.0.1 ā 3.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 +107 -0
- package/bin/index.js +1 -1
- package/package.json +9 -1
- package/src/actions/createProject.js +26 -9
- package/src/actions/removeCRUD.js +49 -0
- package/src/cli.js +48 -8
- package/src/utils/copyTemplate.js +19 -10
- package/templates/express-basic/.env.example +3 -0
- package/templates/express-basic/package-lock.json +1505 -0
- package/templates/express-basic/package.json +21 -0
- package/templates/express-basic/server.js +11 -0
- package/templates/express-basic/src/app.js +45 -0
- package/templates/express-basic/src/config/db.config.js +12 -0
- package/templates/express-basic/src/config/env.config.js +11 -0
- package/templates/express-basic/src/controllers/todo.controller.js +90 -0
- package/templates/express-basic/src/middleware/notFound.middleware.js +5 -0
- package/templates/express-basic/src/models/todo.model.js +21 -0
- package/templates/express-basic/src/routes/index.route.js +10 -0
- package/templates/express-basic/src/routes/todo.route.js +15 -0
- package/templates/express-basic/src/utils/responseHandler.js +7 -0
- package/templates/express-rest-api/.env.example +7 -0
- package/templates/express-rest-api/package-lock.json +1977 -0
- package/templates/express-rest-api/package.json +30 -0
- package/templates/express-rest-api/server.js +14 -0
- package/templates/express-rest-api/src/app.js +55 -0
- package/templates/express-rest-api/src/config/db.config.js +14 -0
- package/templates/express-rest-api/src/config/env.config.js +13 -0
- package/templates/express-rest-api/src/config/logger.config.js +20 -0
- package/templates/express-rest-api/src/controllers/user.controller.js +35 -0
- package/templates/express-rest-api/src/middleware/auth.middleware.js +36 -0
- package/templates/express-rest-api/src/middleware/error.middleware.js +32 -0
- package/templates/express-rest-api/src/middleware/rateLimiter.js +13 -0
- package/templates/express-rest-api/src/middleware/validateRequest.middleware.js +15 -0
- package/templates/express-rest-api/src/models/user.model.js +31 -0
- package/templates/express-rest-api/src/routes/user.route.js +14 -0
- package/templates/express-rest-api/src/schemas/user.schema.js +43 -0
- package/templates/express-rest-api/src/services/user.service.js +54 -0
- package/templates/express-rest-api/src/utils/ApiError.js +17 -0
- package/templates/express-rest-api/src/utils/ApiResponse.js +9 -0
- package/templates/express-rest-api/src/utils/CatchAsync.js +5 -0
- package/templates/express-rest-api/src/utils/Token.js +16 -0
- package/templates/express-socket/.env.example +5 -0
- package/templates/express-socket/package-lock.json +2262 -0
- package/templates/express-socket/package.json +31 -0
- package/templates/express-socket/server.js +19 -0
- package/templates/express-socket/src/app.js +33 -0
- package/templates/express-socket/src/config/db.config.js +14 -0
- package/templates/express-socket/src/config/env.config.js +10 -0
- package/templates/express-socket/src/config/logger.js +20 -0
- package/templates/express-socket/src/config/socket.js +23 -0
- package/templates/express-socket/src/middleware/auth.middleware.js +36 -0
- package/templates/express-socket/src/middleware/error.middleware.js +31 -0
- package/templates/express-socket/src/middleware/rateLimiter.js +14 -0
- package/templates/express-socket/src/middleware/validateRequest.middleware.js +15 -0
- package/templates/express-socket/src/utils/ApiError.js +17 -0
- package/templates/express-socket/src/utils/ApiResponse.js +9 -0
- package/templates/express-socket/src/utils/CatchAsync.js +5 -0
- package/templates/express-socket/src/utils/Token.js +16 -0
- package/templates/express-basic/.env +0 -0
- package/templates/express-basic/app.js +0 -5
- package/templates/express-rest-api/index.js +0 -5
- package/templates/express-socket/index.js +0 -5
package/README.md
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# NeatNode
|
|
2
|
+
|
|
3
|
+
**NeatNode** is a plug-and-play CLI that scaffolds clean, production-ready Node.js backends in seconds.
|
|
4
|
+
It comes with pre-built templates, optional CRUD modules, and a modern developer workflow.
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## š Features
|
|
9
|
+
|
|
10
|
+
* Multiple templates: **Basic API**, **REST API**, and **Socket API**
|
|
11
|
+
* Clean MVC folder structure
|
|
12
|
+
* Optional CRUD scaffolding (User or Todo)
|
|
13
|
+
* Integrated logging (Winston + Morgan)
|
|
14
|
+
* Security middleware (Helmet, Rate Limiter)
|
|
15
|
+
* Dynamic CLI with file removal & template customization
|
|
16
|
+
* Ready for ESM, Redis, and future TypeScript support
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## š§ Installation
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
npm install -g neatnode
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
or run directly:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
npx neatnode
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## āļø Usage
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
npx neatnode
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
* Enter your **project name**
|
|
41
|
+
* Choose a **template** (Basic / REST / Socket)
|
|
42
|
+
* Select whether to include **CRUD** examples
|
|
43
|
+
* Install dependencies and start your project:
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
cd my-app
|
|
47
|
+
npm install
|
|
48
|
+
npm run dev
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## š Templates
|
|
54
|
+
|
|
55
|
+
### **Basic API**
|
|
56
|
+
|
|
57
|
+
Minimal Express setup with optional Todo CRUD.
|
|
58
|
+
Perfect for small projects or quick prototypes.
|
|
59
|
+
|
|
60
|
+
### **REST API**
|
|
61
|
+
|
|
62
|
+
Full architecture with controllers, services, models, routes, error handling, validation, and logging.
|
|
63
|
+
Ideal for scalable, production-grade APIs.
|
|
64
|
+
|
|
65
|
+
### **Socket API**
|
|
66
|
+
|
|
67
|
+
Express + Socket.io integration with logging and CORS setup.
|
|
68
|
+
Best for chat apps or real-time systems.
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
## š§© Example Structure
|
|
73
|
+
|
|
74
|
+
```
|
|
75
|
+
src/
|
|
76
|
+
āāā controllers/
|
|
77
|
+
āāā models/
|
|
78
|
+
āāā routes/
|
|
79
|
+
āāā services/
|
|
80
|
+
āāā utils/
|
|
81
|
+
āāā app.js
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
## š ļø Built With
|
|
87
|
+
|
|
88
|
+
* Node.js & Express
|
|
89
|
+
* Inquirer (CLI)
|
|
90
|
+
* fs-extra & path (File system automation)
|
|
91
|
+
* Morgan & Winston (Logging)
|
|
92
|
+
* Joi (Validation)
|
|
93
|
+
* Helmet, Rate Limiter (Security)
|
|
94
|
+
|
|
95
|
+
---
|
|
96
|
+
|
|
97
|
+
## š§āš» Author
|
|
98
|
+
|
|
99
|
+
**Aakash Gupta**
|
|
100
|
+
[GitHub](https://github.com/aakash-gupta02) Ā· [Email](mailto:aakashgupta052004@gmail.com)
|
|
101
|
+
|
|
102
|
+
---
|
|
103
|
+
|
|
104
|
+
## šŖŖ License
|
|
105
|
+
|
|
106
|
+
MIT Ā© Aakash Gupta
|
|
107
|
+
|
package/bin/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
1
|
+
#!/usr/bin/env node
|
|
2
2
|
import("../src/cli.js");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "neatnode",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.1",
|
|
4
4
|
"description": "Plug & Play Node.js backend starter templates ā build REST APIs, socket servers, and more in seconds.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"neatnode": "./bin/index.js"
|
|
@@ -15,6 +15,14 @@
|
|
|
15
15
|
"template",
|
|
16
16
|
"neatnode"
|
|
17
17
|
],
|
|
18
|
+
"files": [
|
|
19
|
+
"bin/",
|
|
20
|
+
"src/",
|
|
21
|
+
"templates/",
|
|
22
|
+
"utils/",
|
|
23
|
+
"package.json",
|
|
24
|
+
"README.md"
|
|
25
|
+
],
|
|
18
26
|
"author": {
|
|
19
27
|
"name": "Aakash Gupta",
|
|
20
28
|
"email": "aakashgupta052004@gmail.com",
|
|
@@ -1,32 +1,49 @@
|
|
|
1
1
|
import fs from "fs";
|
|
2
2
|
import path from "path";
|
|
3
|
+
import os from "os";
|
|
3
4
|
import { fileURLToPath } from "url";
|
|
4
5
|
import { copyTemplate } from "../utils/copyTemplate.js";
|
|
6
|
+
import { removeCrud, removeCrudReferences } from "./removeCRUD.js";
|
|
5
7
|
|
|
6
8
|
const __filename = fileURLToPath(import.meta.url);
|
|
7
9
|
const __dirname = path.dirname(__filename);
|
|
8
10
|
|
|
9
|
-
export async function createProject({ projectName, templatePath }) {
|
|
11
|
+
export async function createProject({ projectName, templatePath, includeCrud, crudName }) {
|
|
10
12
|
try {
|
|
11
|
-
|
|
13
|
+
// If user wants to create in current dir (.)
|
|
14
|
+
const targetPath = projectName === "." ? process.cwd() : path.join(process.cwd(), projectName);
|
|
12
15
|
|
|
13
|
-
|
|
14
|
-
if (fs.existsSync(targetPath)) {
|
|
16
|
+
if (fs.existsSync(targetPath) && projectName !== ".") {
|
|
15
17
|
console.error(`ā Folder "${projectName}" already exists.`);
|
|
16
18
|
process.exit(1);
|
|
17
19
|
}
|
|
18
20
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
if (projectName !== ".") {
|
|
22
|
+
console.log("Creating project folder...");
|
|
23
|
+
fs.mkdirSync(targetPath);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
await copyTemplate(templatePath, targetPath, {
|
|
27
|
+
"project-name": projectName === "." ? path.basename(process.cwd()) : projectName,
|
|
28
|
+
"author": os.userInfo().username || "author",
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
if (!includeCrud) {
|
|
32
|
+
console.log("šļø Removing CRUD files...");
|
|
33
|
+
removeCrud(targetPath, crudName);
|
|
34
|
+
removeCrudReferences(path.join(targetPath, "src", "app.js"));
|
|
35
|
+
} else {
|
|
36
|
+
console.log("ā
Including CRUD functionality...");
|
|
37
|
+
}
|
|
23
38
|
|
|
24
39
|
console.log(`ā
Project "${projectName}" created successfully!`);
|
|
25
|
-
console.log(`\ncd ${projectName}`);
|
|
40
|
+
console.log(`\ncd ${projectName === "." ? "" : projectName}`);
|
|
26
41
|
console.log("npm install");
|
|
27
42
|
console.log("npm run dev (or npm start)\n");
|
|
43
|
+
|
|
28
44
|
} catch (err) {
|
|
29
45
|
console.error("Failed to create project:", err);
|
|
30
46
|
process.exit(1);
|
|
31
47
|
}
|
|
32
48
|
}
|
|
49
|
+
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
export function removeCrud(targetPath, name) {
|
|
6
|
+
try {
|
|
7
|
+
const crudPaths = [
|
|
8
|
+
`src/models/${name}.model.js`,
|
|
9
|
+
`src/controllers/${name}.controller.js`,
|
|
10
|
+
`src/routes/${name}.route.js`,
|
|
11
|
+
`src/services/${name}.service.js`,
|
|
12
|
+
`src/validations/${name}.validation.js`,
|
|
13
|
+
"src/middlewares/auth.middleware.js",
|
|
14
|
+
`src/schemas/${name}.schema.js`
|
|
15
|
+
];
|
|
16
|
+
|
|
17
|
+
crudPaths.forEach(relPath => {
|
|
18
|
+
const absPath = path.join(targetPath, relPath);
|
|
19
|
+
|
|
20
|
+
if (fs.existsSync(absPath)) {
|
|
21
|
+
fs.rmSync(absPath, { force: true });
|
|
22
|
+
console.log(`ā Removed: ${relPath}`);
|
|
23
|
+
} // else {
|
|
24
|
+
// console.log(`Skipped (not found): ${relPath}`);
|
|
25
|
+
// }
|
|
26
|
+
});
|
|
27
|
+
} catch (err) {
|
|
28
|
+
console.error("ā Error while removing CRUD files:", err.message);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
export function removeCrudReferences(appJsPath) {
|
|
34
|
+
let content = fs.readFileSync(appJsPath, "utf8");
|
|
35
|
+
|
|
36
|
+
// Remove imports block
|
|
37
|
+
content = content.replace(
|
|
38
|
+
/\/\/ ROUTE_IMPORTS_START[\s\S]*?\/\/ ROUTE_IMPORTS_END/,
|
|
39
|
+
""
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
// Remove route usage block
|
|
43
|
+
content = content.replace(
|
|
44
|
+
/\/\/ ROUTE_USES_START[\s\S]*?\/\/ ROUTE_USES_END/,
|
|
45
|
+
""
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
fs.writeFileSync(appJsPath, content, "utf8");
|
|
49
|
+
}
|
package/src/cli.js
CHANGED
|
@@ -1,34 +1,74 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
1
2
|
import inquirer from "inquirer";
|
|
2
3
|
import { createProject } from "./actions/createProject.js";
|
|
3
4
|
import templates from "./config/templates.js";
|
|
4
5
|
|
|
5
6
|
async function main() {
|
|
6
|
-
console.log("Welcome to NodeNeat CLI
|
|
7
|
+
console.log("\nš Welcome to NodeNeat CLI!\n");
|
|
7
8
|
|
|
8
|
-
|
|
9
|
+
// Step 1: Ask for project name
|
|
10
|
+
const { projectName } = await inquirer.prompt([
|
|
9
11
|
{
|
|
10
12
|
name: "projectName",
|
|
11
13
|
type: "input",
|
|
12
14
|
message: "Enter your project folder name:",
|
|
13
|
-
default: "my-app"
|
|
15
|
+
default: "my-app",
|
|
16
|
+
validate: (input) => input.trim() !== "" || "Project name cannot be empty."
|
|
14
17
|
}
|
|
15
18
|
]);
|
|
16
19
|
|
|
17
|
-
|
|
20
|
+
// Step 2: Choose template
|
|
18
21
|
const { template } = await inquirer.prompt([
|
|
19
22
|
{
|
|
20
23
|
name: "template",
|
|
21
24
|
type: "list",
|
|
22
25
|
message: "Choose a template:",
|
|
23
|
-
choices: templates.map(t => t.name)
|
|
26
|
+
choices: templates.map((t) => t.name)
|
|
24
27
|
}
|
|
25
28
|
]);
|
|
26
29
|
|
|
27
|
-
const chosen = templates.find(t => t.name === template);
|
|
30
|
+
const chosen = templates.find((t) => t.name === template);
|
|
31
|
+
|
|
32
|
+
// Step 3: Optional prompt if REST API
|
|
33
|
+
let includeCrud = false;
|
|
34
|
+
let crudName = "";
|
|
35
|
+
if (chosen.name === "Basic Express") {
|
|
36
|
+
const answer = await inquirer.prompt([
|
|
37
|
+
{
|
|
38
|
+
name: "includeCrud",
|
|
39
|
+
type: "confirm",
|
|
40
|
+
message: "Include example Todo CRUD setup?",
|
|
41
|
+
default: true
|
|
42
|
+
}
|
|
43
|
+
]);
|
|
44
|
+
includeCrud = answer.includeCrud;
|
|
45
|
+
crudName = "todo";
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (chosen.name === "REST API") {
|
|
49
|
+
const answer = await inquirer.prompt([
|
|
50
|
+
{
|
|
51
|
+
name: "includeCrud",
|
|
52
|
+
type: "confirm",
|
|
53
|
+
message: "Include example User CRUD setup?",
|
|
54
|
+
default: true
|
|
55
|
+
}
|
|
56
|
+
]);
|
|
57
|
+
includeCrud = answer.includeCrud;
|
|
58
|
+
crudName = "user";
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Step 4: Create the project
|
|
28
62
|
await createProject({
|
|
29
63
|
projectName,
|
|
30
|
-
templatePath: chosen.path
|
|
64
|
+
templatePath: chosen.path,
|
|
65
|
+
includeCrud,
|
|
66
|
+
crudName
|
|
31
67
|
});
|
|
68
|
+
|
|
69
|
+
console.log(`\nā
Project "${projectName}" created successfully using "${chosen.name}" template.\n`);
|
|
32
70
|
}
|
|
33
71
|
|
|
34
|
-
main()
|
|
72
|
+
main().catch((err) => {
|
|
73
|
+
console.error("ā Error:", err.message);
|
|
74
|
+
});
|
|
@@ -1,31 +1,40 @@
|
|
|
1
1
|
import fs from "fs";
|
|
2
2
|
import path from "path";
|
|
3
3
|
|
|
4
|
-
export async function copyTemplate(srcDir, destDir) {
|
|
5
|
-
|
|
6
|
-
const ignoreList = ["node_modules", ".git", ".env"];
|
|
4
|
+
export async function copyTemplate(srcDir, destDir, replacements = {}, ) {
|
|
5
|
+
const ignoreList = ["node_modules", ".git", ".env", "package-lock.json"];
|
|
7
6
|
|
|
8
|
-
// make sure destination exists
|
|
9
7
|
if (!fs.existsSync(destDir)) {
|
|
10
8
|
fs.mkdirSync(destDir, { recursive: true });
|
|
11
9
|
}
|
|
12
10
|
|
|
13
|
-
// read all files/folders in template
|
|
14
11
|
const items = fs.readdirSync(srcDir, { withFileTypes: true });
|
|
15
12
|
|
|
16
13
|
for (const item of items) {
|
|
17
|
-
// skip ignored ones
|
|
18
14
|
if (ignoreList.includes(item.name)) continue;
|
|
19
15
|
|
|
20
16
|
const srcPath = path.join(srcDir, item.name);
|
|
21
17
|
const destPath = path.join(destDir, item.name);
|
|
22
18
|
|
|
23
19
|
if (item.isDirectory()) {
|
|
24
|
-
//
|
|
25
|
-
await copyTemplate(srcPath, destPath);
|
|
20
|
+
// recursively copy folders
|
|
21
|
+
await copyTemplate(srcPath, destPath, replacements);
|
|
26
22
|
} else {
|
|
27
|
-
//
|
|
28
|
-
|
|
23
|
+
// for certain files, replace placeholders
|
|
24
|
+
if (["package.json"].includes(item.name)) {
|
|
25
|
+
let content = fs.readFileSync(srcPath, "utf-8");
|
|
26
|
+
|
|
27
|
+
// replace all {{key}} with provided replacements
|
|
28
|
+
for (const [key, value] of Object.entries(replacements)) {
|
|
29
|
+
const regex = new RegExp(`project-name`, "g");
|
|
30
|
+
content = content.replace(regex, value);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
fs.writeFileSync(destPath, content, "utf-8");
|
|
34
|
+
} else {
|
|
35
|
+
// just copy normal files
|
|
36
|
+
fs.copyFileSync(srcPath, destPath);
|
|
37
|
+
}
|
|
29
38
|
}
|
|
30
39
|
}
|
|
31
40
|
}
|