cleancode-e 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 ADDED
@@ -0,0 +1,181 @@
1
+ <div align="center">
2
+
3
+ # ⚡ cleancode-e
4
+
5
+ **CLI Generator untuk project backend Fastify — siap pakai dalam hitungan detik.**
6
+
7
+ [![npm version](https://img.shields.io/npm/v/cleancode-e?color=cyan&style=flat-square)](https://www.npmjs.com/package/cleancode-e)
8
+ [![license](https://img.shields.io/badge/license-MIT-green?style=flat-square)](LICENSE)
9
+ [![node](https://img.shields.io/badge/node-%3E%3D18-brightgreen?style=flat-square)](https://nodejs.org)
10
+
11
+ </div>
12
+
13
+ ---
14
+
15
+ ## Apa itu cleancode-e?
16
+
17
+ `cleancode-e` adalah CLI generator yang men-scaffold project backend [Fastify](https://fastify.dev) secara interaktif — lengkap dengan struktur folder siap produksi, pilihan database (MongoDB / MySQL / tanpa database), dan auto-install dependency.
18
+
19
+ Terinspirasi dari Artisan (Laravel) dan Create-React-App, tapi untuk ekosistem **Node.js + Fastify**.
20
+
21
+ ---
22
+
23
+ ## Quickstart
24
+
25
+ ```bash
26
+ npx cleancode-e
27
+ ```
28
+
29
+ > Tidak perlu install dulu. Jalankan langsung, ikuti promptnya.
30
+
31
+ ---
32
+
33
+ ## Cara Pakai
34
+
35
+ ```bash
36
+ npx cleancode-e
37
+
38
+ # atau jika install global:
39
+ npm install -g cleancode-e
40
+ cleancode-e
41
+ ```
42
+
43
+ Ikuti prompt interaktifnya:
44
+
45
+ ```
46
+ ┌ cleancode-e CLI Generator
47
+
48
+ ◇ Nama project kamu:
49
+ │ my-api
50
+
51
+ ◇ Pilih database:
52
+ │ ● Tidak pakai database (in-memory)
53
+ │ ○ MongoDB (Mongoose)
54
+ │ ○ MySQL (Sequelize + mysql2)
55
+
56
+ ◇ Pakai nodemon untuk auto-reload saat development?
57
+ │ ● Ya ○ Tidak
58
+
59
+ └ folder berhasil dibuat, Selamat berkarya! masbro 🚀
60
+ ```
61
+
62
+ ---
63
+
64
+ ## Struktur Project yang Dihasilkan
65
+
66
+ ```
67
+ my-api/
68
+ ├── app.js # Entry point server Fastify
69
+ ├── package.json # Scripts & dependencies
70
+ ├── .env.example # Template environment variables
71
+
72
+ ├── routes/
73
+ │ ├── example.userRoute.js # Contoh route GET & POST /users
74
+ │ └── readme.txt # Panduan penggunaan folder routes
75
+
76
+ ├── controllers/
77
+ │ ├── example.userController.js # Contoh logic controller
78
+ │ └── readme.txt
79
+
80
+ ├── models/
81
+ │ ├── example.userModel.js # Skema User (Mongoose / Sequelize)
82
+ │ └── readme.txt
83
+
84
+ ├── config/
85
+ │ ├── db.js # Koneksi database (jika dipilih)
86
+ │ └── readme.txt
87
+
88
+ ├── plugins/
89
+ │ ├── example.corsPlugin.js # Contoh plugin CORS
90
+ │ └── readme.txt
91
+
92
+ └── middleware/
93
+ ├── example.authMiddleware.js # Contoh middleware autentikasi
94
+ └── readme.txt
95
+ ```
96
+
97
+ ---
98
+
99
+ ## Dependency yang Diinstall Otomatis
100
+
101
+ | Pilihan | Package yang diinstall |
102
+ |---|---|
103
+ | Semua project | `fastify` |
104
+ | MongoDB | + `mongoose` |
105
+ | MySQL | + `sequelize`, `mysql2` |
106
+ | Nodemon (jika Ya) | `nodemon` (devDependency) |
107
+
108
+ ---
109
+
110
+ ## Setup Setelah Generate
111
+
112
+ ### 1. Tanpa Database
113
+ Langsung jalankan:
114
+ ```bash
115
+ cd my-api
116
+ npm start
117
+ # → http://localhost:3000
118
+ ```
119
+
120
+ ### 2. MongoDB
121
+ ```bash
122
+ cd my-api
123
+
124
+ # Rename dan isi .env
125
+ cp .env.example .env
126
+ # Edit MONGO_URI di .env:
127
+ # MONGO_URI=mongodb://localhost:27017/my-api
128
+ # atau MongoDB Atlas:
129
+ # MONGO_URI=mongodb+srv://user:pass@cluster.mongodb.net/my-api
130
+
131
+ npm start
132
+ ```
133
+
134
+ ### 3. MySQL
135
+ ```bash
136
+ cd my-api
137
+
138
+ # Buat database dulu di MySQL:
139
+ # CREATE DATABASE my_api;
140
+
141
+ cp .env.example .env
142
+ # Edit .env:
143
+ # DB_HOST=localhost
144
+ # DB_NAME=my_api
145
+ # DB_USER=root
146
+ # DB_PASS=yourpassword
147
+
148
+ npm start
149
+ # Sequelize akan auto-sync tabel saat pertama kali jalan
150
+ ```
151
+
152
+ ---
153
+
154
+ ## Scripts
155
+
156
+ | Command | Keterangan |
157
+ |---|---|
158
+ | `npm start` | Jalankan server (production) |
159
+ | `npm run dev` | Jalankan dengan nodemon (development) |
160
+
161
+ ---
162
+
163
+ ## Syarat
164
+
165
+ - Node.js `>= 18`
166
+ - npm `>= 8`
167
+
168
+ ---
169
+
170
+ ## Tech Stack
171
+
172
+ - **[Fastify](https://fastify.dev)** — Web framework Node.js ultra-cepat
173
+ - **[@clack/prompts](https://github.com/natemoo-re/clack)** — Prompt CLI yang elegan
174
+ - **[execa](https://github.com/sindresorhus/execa)** — Shell command runner
175
+ - **[picocolors](https://github.com/alexeyraspopov/picocolors)** — Terminal color output
176
+
177
+ ---
178
+
179
+ ## Lisensi
180
+
181
+ MIT © cleancode-e contributors
package/bin/index.js ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ import "../src/cli.js";
package/package.json ADDED
@@ -0,0 +1,15 @@
1
+ {
2
+ "name": "cleancode-e",
3
+ "version": "1.0.0",
4
+ "description": "CLI generator untuk project backend Fastify siap pakai",
5
+ "keywords": ["fastify", "cli", "generator", "scaffold", "backend", "nodejs"],
6
+ "type": "module",
7
+ "bin": {
8
+ "cleancode-e": "./bin/index.js"
9
+ },
10
+ "dependencies": {
11
+ "@clack/prompts": "^0.7.0",
12
+ "execa": "^8.0.1",
13
+ "picocolors": "^1.0.0"
14
+ }
15
+ }
package/src/cli.js ADDED
@@ -0,0 +1,159 @@
1
+ import { intro, outro, text, select, confirm, spinner, note } from "@clack/prompts";
2
+ import { execa } from "execa";
3
+ import fs from "fs";
4
+ import path from "path";
5
+ import { fileURLToPath } from "node:url";
6
+ import colors from "picocolors";
7
+
8
+ const __filename = fileURLToPath(import.meta.url);
9
+ const __dirname = path.dirname(__filename);
10
+
11
+ async function main() {
12
+ console.clear();
13
+ intro(colors.bgCyan(colors.black(" cleancode-e CLI Generator ")));
14
+
15
+ const projectName = await text({
16
+ message: "Nama project kamu:",
17
+ placeholder: "contoh: my-api",
18
+ validate: (value) => {
19
+ if (!value) return "Nama project tidak boleh kosong";
20
+ if (value.includes(" ")) return "Nama project tidak boleh mengandung spasi";
21
+ if (value.length < 3) return "Nama project minimal 3 karakter";
22
+ if (/\d/.test(value)) return "Nama project tidak boleh mengandung angka";
23
+ if (fs.existsSync(value)) return "Folder dengan nama itu sudah ada";
24
+ },
25
+ });
26
+
27
+ if (typeof projectName === "symbol") {
28
+ outro(colors.red("Dibatalkan."));
29
+ process.exit(0);
30
+ }
31
+
32
+ const database = await select({
33
+ message: "Pilih database:",
34
+ options: [
35
+ { value: "none", label: "Tidak pakai database (in-memory)" },
36
+ { value: "mongo", label: "MongoDB (Mongoose)" },
37
+ { value: "mysql", label: "MySQL (Sequelize + mysql2)" },
38
+ ],
39
+ });
40
+
41
+ if (typeof database === "symbol") {
42
+ outro(colors.red("Dibatalkan."));
43
+ process.exit(0);
44
+ }
45
+
46
+ const useNodemon = await confirm({
47
+ message: "Pakai nodemon untuk auto-reload saat development?",
48
+ initialValue: true,
49
+ });
50
+
51
+ if (typeof useNodemon === "symbol") {
52
+ outro(colors.red("Dibatalkan."));
53
+ process.exit(0);
54
+ }
55
+
56
+ const targetDir = path.resolve(projectName);
57
+ const templateDir = path.join(__dirname, "templates");
58
+
59
+ const s = spinner();
60
+ s.start("Menyiapkan struktur project...");
61
+
62
+ try {
63
+ const dirs = ["routes", "controllers", "models", "config", "plugins", "middleware"];
64
+ for (const dir of dirs) {
65
+ fs.mkdirSync(path.join(targetDir, dir), { recursive: true });
66
+ }
67
+
68
+ const from = (src) => path.join(templateDir, src);
69
+ const to = (dest) => path.join(targetDir, dest);
70
+ const cp = (src, dest) => fs.copyFileSync(from(src), to(dest));
71
+
72
+ cp(`app.${database}.js`, "app.js");
73
+ cp(".env.example", ".env.example");
74
+
75
+ cp("routes/example.userRoute.js", "routes/example.userRoute.js");
76
+ cp("routes/readme.txt", "routes/readme.txt");
77
+ cp("controllers/readme.txt", "controllers/readme.txt");
78
+ cp("models/readme.txt", "models/readme.txt");
79
+ cp("config/readme.txt", "config/readme.txt");
80
+ cp("plugins/readme.txt", "plugins/readme.txt");
81
+ cp("plugins/example.corsPlugin.js", "plugins/example.corsPlugin.js");
82
+ cp("middleware/readme.txt", "middleware/readme.txt");
83
+ cp("middleware/example.authMiddleware.js", "middleware/example.authMiddleware.js");
84
+
85
+ if (database === "mongo") {
86
+ cp("config/db.mongo.js", "config/db.js");
87
+ cp("models/example.userModel.mongo.js", "models/example.userModel.js");
88
+ cp("controllers/example.userController.mongo.js", "controllers/example.userController.js");
89
+ } else if (database === "mysql") {
90
+ cp("config/db.mysql.js", "config/db.js");
91
+ cp("models/example.userModel.mysql.js", "models/example.userModel.js");
92
+ cp("controllers/example.userController.mysql.js", "controllers/example.userController.js");
93
+ } else {
94
+ cp("controllers/example.userController.none.js", "controllers/example.userController.js");
95
+ }
96
+
97
+ const deps = { fastify: "latest" };
98
+ if (database === "mongo") deps.mongoose = "latest";
99
+ if (database === "mysql") {
100
+ deps.sequelize = "latest";
101
+ deps.mysql2 = "latest";
102
+ }
103
+
104
+ fs.writeFileSync(
105
+ to("package.json"),
106
+ JSON.stringify({
107
+ name: projectName,
108
+ version: "1.0.0",
109
+ type: "module",
110
+ main: "app.js",
111
+ scripts: {
112
+ start: "node app.js",
113
+ dev: useNodemon ? "nodemon app.js" : "node --watch app.js",
114
+ },
115
+ dependencies: deps,
116
+ }, null, 2)
117
+ );
118
+
119
+ s.stop("Struktur project berhasil dibuat!");
120
+
121
+ const installSpinner = spinner();
122
+ installSpinner.start("Menginstall dependencies (ini mungkin memakan waktu >_<)...");
123
+
124
+ const installList = ["fastify"];
125
+ if (database === "mongo") installList.push("mongoose");
126
+ if (database === "mysql") installList.push("sequelize", "mysql2");
127
+
128
+ await execa("npm", ["install", ...installList], { cwd: targetDir });
129
+
130
+ if (useNodemon) {
131
+ await execa("npm", ["install", "--save-dev", "nodemon"], { cwd: targetDir });
132
+ }
133
+
134
+ installSpinner.stop("Dependencies berhasil diinstall!");
135
+
136
+ note(
137
+ [
138
+ `Project berhasil dibuat di: ${colors.cyan(targetDir)}`,
139
+ "",
140
+ ` cd ${projectName}`,
141
+ ` npm start`,
142
+ "",
143
+ database !== "none"
144
+ ? `Isi ${colors.yellow(".env.example")} lalu rename jadi ${colors.yellow(".env")}`
145
+ : "",
146
+ ].filter(Boolean).join("\n"),
147
+ "Selesai!"
148
+ );
149
+
150
+ outro(colors.green("folder berhasil dibuat, Selamat berkarya! masbro 🚀"));
151
+ } catch (err) {
152
+ s.stop(colors.red("Terjadi kesalahan!"));
153
+ console.error(err);
154
+ outro(colors.red("Proses pembuatan project gagal."));
155
+ process.exit(1);
156
+ }
157
+ }
158
+
159
+ main();
@@ -0,0 +1,10 @@
1
+ PORT=3000
2
+
3
+ # MongoDB
4
+ MONGO_URI=mongodb://localhost:27017/myapp
5
+
6
+ # MySQL
7
+ DB_HOST=localhost
8
+ DB_NAME=myapp
9
+ DB_USER=root
10
+ DB_PASS=
@@ -0,0 +1,18 @@
1
+ import Fastify from 'fastify';
2
+ import { connectDB } from './config/db.js';
3
+ import userRoutes from './routes/userRoutes.js';
4
+
5
+ const fastify = Fastify({ logger: true });
6
+
7
+ fastify.register(userRoutes);
8
+
9
+ const start = async () => {
10
+ try {
11
+ await connectDB();
12
+ await fastify.listen({ port: process.env.PORT || 3000, host: '0.0.0.0' });
13
+ } catch (err) {
14
+ fastify.log.error(err);
15
+ process.exit(1);
16
+ }
17
+ };
18
+ start();
@@ -0,0 +1,21 @@
1
+ import Fastify from 'fastify';
2
+ import sequelize from './config/db.js';
3
+ import userRoutes from './routes/userRoutes.js';
4
+ import './models/User.js';
5
+
6
+ const fastify = Fastify({ logger: true });
7
+
8
+ fastify.register(userRoutes);
9
+
10
+ const start = async () => {
11
+ try {
12
+ await sequelize.authenticate();
13
+ await sequelize.sync();
14
+ console.log('MySQL connected & synced');
15
+ await fastify.listen({ port: process.env.PORT || 3000, host: '0.0.0.0' });
16
+ } catch (err) {
17
+ fastify.log.error(err);
18
+ process.exit(1);
19
+ }
20
+ };
21
+ start();
@@ -0,0 +1,16 @@
1
+ import Fastify from 'fastify';
2
+ import userRoutes from './routes/userRoutes.js';
3
+
4
+ const fastify = Fastify({ logger: true });
5
+
6
+ fastify.register(userRoutes);
7
+
8
+ const start = async () => {
9
+ try {
10
+ await fastify.listen({ port: process.env.PORT || 3000, host: '0.0.0.0' });
11
+ } catch (err) {
12
+ fastify.log.error(err);
13
+ process.exit(1);
14
+ }
15
+ };
16
+ start();
@@ -0,0 +1,6 @@
1
+ import mongoose from 'mongoose';
2
+
3
+ export async function connectDB() {
4
+ await mongoose.connect(process.env.MONGO_URI || 'mongodb://localhost:27017/myapp');
5
+ console.log('MongoDB connected');
6
+ }
@@ -0,0 +1,14 @@
1
+ import { Sequelize } from 'sequelize';
2
+
3
+ const sequelize = new Sequelize(
4
+ process.env.DB_NAME || 'myapp',
5
+ process.env.DB_USER || 'root',
6
+ process.env.DB_PASS || '',
7
+ {
8
+ host: process.env.DB_HOST || 'localhost',
9
+ dialect: 'mysql',
10
+ logging: false,
11
+ }
12
+ );
13
+
14
+ export default sequelize;
@@ -0,0 +1,4 @@
1
+ config/
2
+ Folder ini berisi konfigurasi koneksi database dan environment.
3
+ File db.js mengekspor koneksi yang siap dipakai di seluruh aplikasi.
4
+ Simpan kredensial sensitif di file .env dan jangan di-commit ke git.
@@ -0,0 +1,11 @@
1
+ import User from '../models/example.userModel.js';
2
+
3
+ export async function getUsers(request, reply) {
4
+ const users = await User.find();
5
+ return reply.send({ status: 'success', data: users });
6
+ }
7
+
8
+ export async function createUser(request, reply) {
9
+ const user = await User.create(request.body);
10
+ return reply.code(201).send({ status: 'success', data: user });
11
+ }
@@ -0,0 +1,11 @@
1
+ import User from '../models/example.userModel.js';
2
+
3
+ export async function getUsers(request, reply) {
4
+ const users = await User.findAll();
5
+ return reply.send({ status: 'success', data: users });
6
+ }
7
+
8
+ export async function createUser(request, reply) {
9
+ const user = await User.create(request.body);
10
+ return reply.code(201).send({ status: 'success', data: user });
11
+ }
@@ -0,0 +1,12 @@
1
+ const users = [];
2
+
3
+ export async function getUsers(request, reply) {
4
+ return reply.send({ status: 'success', data: users });
5
+ }
6
+
7
+ export async function createUser(request, reply) {
8
+ const { name, email } = request.body;
9
+ const user = { id: users.length + 1, name, email };
10
+ users.push(user);
11
+ return reply.code(201).send({ status: 'success', data: user });
12
+ }
@@ -0,0 +1,4 @@
1
+ controllers/
2
+ Folder ini berisi logika bisnis untuk setiap resource.
3
+ Controller menerima request dari route dan berinteraksi dengan model.
4
+ Kembalikan response yang konsisten: { status, data } atau { status, message }.
@@ -0,0 +1,9 @@
1
+ export async function authMiddleware(request, reply) {
2
+ const token = request.headers['authorization'];
3
+ if (!token) {
4
+ return reply.code(401).send({ status: 'error', message: 'Unauthorized' });
5
+ }
6
+ // TODO: verifikasi token JWT di sini
7
+ // const decoded = jwt.verify(token.replace('Bearer ', ''), process.env.JWT_SECRET);
8
+ // request.user = decoded;
9
+ }
@@ -0,0 +1,5 @@
1
+ middleware/
2
+ Folder ini berisi fungsi middleware (hook) Fastify.
3
+ Middleware biasanya digunakan untuk autentikasi, logging, dan validasi request.
4
+ Daftarkan sebagai hook di app.js: fastify.addHook('preHandler', myMiddleware)
5
+ Penamaan file: namaMiddleware.middleware.js → contoh: auth.middleware.js
@@ -0,0 +1,9 @@
1
+ import mongoose from 'mongoose';
2
+
3
+ const UserSchema = new mongoose.Schema({
4
+ name: { type: String, required: true },
5
+ email: { type: String, required: true, unique: true },
6
+ createdAt: { type: Date, default: Date.now },
7
+ });
8
+
9
+ export default mongoose.model('User', UserSchema);
@@ -0,0 +1,12 @@
1
+ import { DataTypes } from 'sequelize';
2
+ import sequelize from '../config/db.js';
3
+
4
+ const User = sequelize.define('User', {
5
+ name: { type: DataTypes.STRING, allowNull: false },
6
+ email: { type: DataTypes.STRING, allowNull: false, unique: true },
7
+ }, {
8
+ tableName: 'users',
9
+ timestamps: true,
10
+ });
11
+
12
+ export default User;
@@ -0,0 +1,4 @@
1
+ models/
2
+ Folder ini berisi definisi skema dan model data.
3
+ Setiap file merepresentasikan satu entitas (misal: User, Product).
4
+ Untuk MongoDB gunakan Mongoose Schema, untuk MySQL gunakan Sequelize define().
@@ -0,0 +1,8 @@
1
+ import cors from '@fastify/cors';
2
+
3
+ export default async function corsPlugin(fastify) {
4
+ await fastify.register(cors, {
5
+ origin: process.env.CORS_ORIGIN || '*',
6
+ methods: ['GET', 'POST', 'PUT', 'DELETE'],
7
+ });
8
+ }
@@ -0,0 +1,5 @@
1
+ plugins/
2
+ Folder ini berisi plugin Fastify yang didaftarkan ke instance global.
3
+ Gunakan fastify.register() di app.js untuk mendaftarkan plugin.
4
+ Contoh: CORS, rate limiting, JWT auth, Swagger.
5
+ Penamaan file: namaPlugin.plugin.js → contoh: cors.plugin.js
@@ -0,0 +1,6 @@
1
+ import { getUsers, createUser } from '../controllers/example.userController.js';
2
+
3
+ export default async function userRoutes(fastify) {
4
+ fastify.get('/users', getUsers);
5
+ fastify.post('/users', createUser);
6
+ }
@@ -0,0 +1,4 @@
1
+ routes/
2
+ Folder ini berisi definisi route Fastify.
3
+ Setiap file mendaftarkan satu grup endpoint (misal: userRoutes.js untuk /users).
4
+ Daftarkan semua route di app.js menggunakan fastify.register().