create-node-prodkit 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,1540 @@
1
+ # Node.js Production-Grade Skeleton
2
+
3
+ [![npm](https://img.shields.io/npm/v/create-node-prodkit)](https://www.npmjs.com/package/create-node-prodkit)
4
+ [![Node.js](https://img.shields.io/badge/node-%3E%3D18-brightgreen)](https://nodejs.org)
5
+
6
+ A clean, opinionated Node.js REST API skeleton using **ES Modules**, **Express 5**, and a layered architecture designed for production. Available as an **NPM CLI** — scaffold a fully wired project in one command.
7
+
8
+ ```bash
9
+ npx create-node-prodkit project-name
10
+ ```
11
+
12
+ ---
13
+
14
+ ## Table of Contents
15
+
16
+ 1. [Prerequisites](#1-prerequisites)
17
+ 2. [Getting Started](#2-getting-started)
18
+ 3. [Project Structure](#3-project-structure)
19
+ 4. [Request Lifecycle (The Full Flow)](#4-request-lifecycle-the-full-flow)
20
+ - [Architecture Diagram](#the-layered-architecture)
21
+ - [Error Path](#the-error-path)
22
+ - [Concrete Traced Example](#concrete-traced-example--get-apiv1users42)
23
+ 5. [Configuration](#5-configuration)
24
+ 6. [How to Write a Feature — Step by Step](#6-how-to-write-a-feature--step-by-step)
25
+ - [Route](#step-1-create-the-route)
26
+ - [Controller](#step-2-create-the-controller)
27
+ - [Validation Middleware](#step-3-create-validation-middleware)
28
+ - [Service](#step-4-create-the-service)
29
+ - [Repository](#step-5-create-the-repository)
30
+ - [Model](#step-6-create-the-model-optional)
31
+ - [Register the Route](#step-7-register-the-route)
32
+ 7. [Utilities — How to Use Each One](#7-utilities--how-to-use-each-one)
33
+ - [asyncHandler](#asynchandler)
34
+ - [ResponseUtil](#responseutil)
35
+ - [logService](#logservice)
36
+ - [apiCall (axios.util)](#apicall-axiosutil)
37
+ - [logSanitizer / sensitiveKeys](#logsanitizer--sensitivekeys)
38
+ 8. [Middlewares](#8-middlewares)
39
+ - [rateLimitMiddleware](#ratelimitmiddleware)
40
+ - [ipMiddleware](#ipmiddleware)
41
+ - [errorMiddleware](#errormiddleware)
42
+ - [encryptMiddleware](#encryptmiddleware)
43
+ 9. [Database Connections](#9-database-connections)
44
+ 10. [Logging System Deep Dive](#10-logging-system-deep-dive)
45
+ 11. [Environment Variables Reference](#11-environment-variables-reference)
46
+ 12. [Code Standards & Conventions](#12-code-standards--conventions)
47
+ - [Rule 1 — Single Responsibility](#rule-1--single-responsibility-per-layer)
48
+ - [Rule 2 — File & Folder Naming](#rule-2--file--folder-naming)
49
+ - [Rule 3 — Naming Conventions](#rule-3--naming-conventions-in-code)
50
+ - [Rule 4 — Import Order](#rule-4--import-order)
51
+ - [Rule 5 — Exports](#rule-5--exports)
52
+ - [Rule 6 — Async / Await](#rule-6--async--await)
53
+ - [Rule 7 — Error Handling](#rule-7--error-handling)
54
+ - [Rule 8 — Responses](#rule-8--responses)
55
+ - [Rule 9 — Logging](#rule-9--logging)
56
+ - [Rule 10 — Configuration](#rule-10--configuration)
57
+ - [Rule 11 — Small & Flat Functions](#rule-11--keep-functions-small-and-flat)
58
+ - [Rule 12 — Comments](#rule-12--comments)
59
+ - [Rule 13 — Repository Shape](#rule-13--repository-shape)
60
+ - [Rule 14 — Never Pass req/res to Services](#rule-14--never-pass-req-or-res-to-a-service)
61
+ 13. [What NOT to Do](#13-what-not-to-do)
62
+ 14. [CLI Package — How It Works](#14-cli-package--how-it-works)
63
+ 15. [Maintaining & Publishing Updates](#15-maintaining--publishing-updates)
64
+
65
+ ---
66
+
67
+ ## 1. Prerequisites
68
+
69
+ | Tool | Minimum Version |
70
+ |------|----------------|
71
+ | Node.js | 18+ |
72
+ | npm | 9+ |
73
+
74
+ ---
75
+
76
+ ## 2. Getting Started
77
+
78
+ ```bash
79
+ # 1. Clone or copy the skeleton
80
+ cd project-name
81
+
82
+ # 2. Install dependencies
83
+ npm install
84
+
85
+ # 3. Create your environment file
86
+ cp .env.example .env # or create .env manually (see section 11)
87
+
88
+ # 4. Start the dev server (with hot reload)
89
+ npm run dev
90
+ ```
91
+
92
+ The server starts at `http://localhost:3000` by default.
93
+
94
+ > **How hot reload works:** `nodemon` watches the `src/` directory. Any `.js` or `.json` file change triggers an automatic restart. This is configured in `package.json` under `nodemonConfig`.
95
+
96
+ ---
97
+
98
+ ## 3. Project Structure
99
+
100
+ ```
101
+ src/
102
+ ├── server.js → Entry point: starts the HTTP server
103
+ ├── app.js → Express app setup, middleware stack, routes
104
+
105
+ ├── config/
106
+ │ └── app.config.js → All config values read from .env in one place
107
+
108
+ ├── routes/
109
+ │ ├── index.route.js → Root router — mounts all feature routers
110
+ │ └── sample1.route.js → Feature-specific routes
111
+
112
+ ├── controllers/
113
+ │ └── sample.controller.js → Handles req/res, calls services
114
+
115
+ ├── services/
116
+ │ └── sample.service.js → Business logic layer
117
+
118
+ ├── repositories/
119
+ │ └── sample.repository.js → Data access layer (DB or external API)
120
+
121
+ ├── models/
122
+ │ └── sample.model.js → DB model definitions (Mongoose, Sequelize, etc.)
123
+
124
+ ├── middlewares/
125
+ │ ├── error.middleware.js → Global error handler
126
+ │ ├── ip.middleware.js → IP allowlist guard
127
+ │ ├── rateLimit.middleware.js → Request throttling
128
+ │ └── encrypt.middleware.js → Placeholder for payload encryption
129
+
130
+ ├── validations/
131
+ │ └── sample.validation.js → Request body/param/query validators
132
+
133
+ ├── db/
134
+ │ ├── mongo.db.js → MongoDB connection setup
135
+ │ ├── mysql.db.js → MySQL connection setup
136
+ │ └── postgres.db.js → PostgreSQL connection setup
137
+
138
+ └── utils/
139
+ ├── asyncHandler.js → Wraps async route handlers to catch errors
140
+ ├── response.util.js → Standardised JSON response helpers
141
+ ├── log.util.js → Logging service (file or external)
142
+ ├── logSanitizer.util.js → Masks/removes sensitive fields before logging
143
+ ├── sensitiveKeys.util.js → List of field names treated as sensitive
144
+ └── axios.util.js → Centralised HTTP client wrapper
145
+ ```
146
+
147
+ ---
148
+
149
+ ## 4. Request Lifecycle (The Full Flow)
150
+
151
+ ### The Layered Architecture
152
+
153
+ This project enforces a **strict one-way data flow**. Each layer has exactly one job. No layer skips another or reaches backwards.
154
+
155
+ ```
156
+ ┌─────────────────────────────────────────────────────────────┐
157
+ │ CLIENT │
158
+ │ HTTP Request (method + headers + body) │
159
+ └─────────────────────────┬───────────────────────────────────┘
160
+
161
+
162
+ ┌─────────────────────────────────────────────────────────────┐
163
+ │ GLOBAL MIDDLEWARES │
164
+ │ (app.js — in order) │
165
+ │ │
166
+ │ 1. rateLimitMiddleware │
167
+ │ → Counts requests per IP per window │
168
+ │ → 429 if exceeded, otherwise continues │
169
+ │ │
170
+ │ 2. ipMiddleware │
171
+ │ → Checks client IP against allowlist │
172
+ │ → 403 if not allowed, otherwise continues │
173
+ │ │
174
+ │ 3. express.json() │
175
+ │ → Parses raw request body into req.body │
176
+ │ → Unreadable body = 400 automatically │
177
+ └─────────────────────────┬───────────────────────────────────┘
178
+
179
+
180
+ ┌─────────────────────────────────────────────────────────────┐
181
+ │ ROUTER │
182
+ │ routes/index.route.js │
183
+ │ │
184
+ │ → Matches URL prefix (e.g. /api/v1/users) │
185
+ │ → Delegates to the correct feature router │
186
+ │ → Feature router matches the rest (e.g. /:id) │
187
+ │ → Calls the middleware chain for that route │
188
+ └─────────────────────────┬───────────────────────────────────┘
189
+
190
+
191
+ ┌─────────────────────────────────────────────────────────────┐
192
+ │ VALIDATION MIDDLEWARE │
193
+ │ validations/<feature>.validation.js │
194
+ │ │
195
+ │ → Reads req.params / req.body / req.query │
196
+ │ → Checks required fields, types, formats │
197
+ │ → 422 + error list if invalid │
198
+ │ → Calls next() if all good — does NOT touch DB │
199
+ └─────────────────────────┬───────────────────────────────────┘
200
+
201
+
202
+ ┌─────────────────────────────────────────────────────────────┐
203
+ │ CONTROLLER │
204
+ │ controllers/<feature>.controller.js │
205
+ │ │
206
+ │ → Extracts clean inputs from req (params, body, query) │
207
+ │ → Calls the service with plain values (no req/res passed) │
208
+ │ → Receives result from service │
209
+ │ → Calls ResponseUtil.success() to send response │
210
+ │ → Wrapped in asyncHandler — errors auto-forwarded to next()│
211
+ └─────────────────────────┬───────────────────────────────────┘
212
+
213
+
214
+ ┌─────────────────────────────────────────────────────────────┐
215
+ │ SERVICE │
216
+ │ services/<feature>.service.js │
217
+ │ │
218
+ │ → Receives plain data (id, name, body object, etc.) │
219
+ │ → Contains ALL business logic and decisions │
220
+ │ → Calls repository to get/write data │
221
+ │ → Calls logService() to record meaningful events │
222
+ │ → Throws error with error.status set for HTTP errors │
223
+ │ → Returns clean result data to controller │
224
+ └─────────────────────────┬───────────────────────────────────┘
225
+
226
+
227
+ ┌─────────────────────────────────────────────────────────────┐
228
+ │ REPOSITORY │
229
+ │ repositories/<feature>.repository.js │
230
+ │ │
231
+ │ → ONLY layer that talks to external data sources │
232
+ │ → Uses apiCall() for external APIs │
233
+ │ → Uses DB model for database operations │
234
+ │ → Returns raw data — no logic, no transforms │
235
+ └─────────────────────────┬───────────────────────────────────┘
236
+
237
+ ┌────────┴────────┐
238
+ ▼ ▼
239
+ ┌──────────┐ ┌──────────────┐
240
+ │ Database │ │ External API │
241
+ │ (DB layer│ │ (axios.util) │
242
+ │ /db/) │ └──────────────┘
243
+ └──────────┘
244
+ ```
245
+
246
+ ### The Error Path
247
+
248
+ Any layer can signal a failure. Here is exactly what happens:
249
+
250
+ ```
251
+ Service throws: errorMiddleware receives it:
252
+ ───────────────── ──────────────────────────
253
+ const err = new Error("Not found") err.status === 404
254
+ err.status = 404 → ResponseUtil.notFound(res, err.message)
255
+ throw err logService("error", err.message, { stack })
256
+ ```
257
+
258
+ | Who throws | How | Where it lands |
259
+ |---|---|---|
260
+ | Service | `throw error` with `.status` | `errorMiddleware` via `asyncHandler → next(err)` |
261
+ | Validation | `ResponseUtil.validation()` directly | Stops at validation, never reaches controller |
262
+ | Repository throws (DB/API error) | Bubble up via `async/await` | `errorMiddleware` via `asyncHandler → next(err)` |
263
+ | Unhandled crash | Any uncaught throw | `errorMiddleware` default → 500 |
264
+
265
+ ---
266
+
267
+ ### Concrete Traced Example — `GET /api/v1/users/42`
268
+
269
+ ```
270
+ 1. Request arrives: GET /api/v1/users/42
271
+
272
+ 2. rateLimitMiddleware
273
+ → IP 127.0.0.1 has made 3 requests this window. Limit is 100. ✓ Pass.
274
+
275
+ 3. ipMiddleware
276
+ → clientIp = "127.0.0.1". In allowedIps. ✓ Pass.
277
+
278
+ 4. express.json()
279
+ → No body on GET. req.body = {}. ✓ Pass.
280
+
281
+ 5. Router: /api/v1 → index.route.js
282
+ → Prefix "/users" matches userRoutes.
283
+ → Path "/:id" matches. req.params.id = "42".
284
+
285
+ 6. validateGetUser middleware
286
+ → id = "42". Not empty. Not NaN. ✓ Pass. next().
287
+
288
+ 7. getUserById controller
289
+ → const { id } = req.params → id = "42"
290
+ → calls getUserByIdService("42")
291
+
292
+ 8. getUserByIdService
293
+ → calls userRepository.findById("42")
294
+
295
+ 9. userRepository.findById
296
+ → apiCall({ method: "GET", url: "https://api.example.com/users/42" })
297
+ → Returns: { id: 42, name: "Vinay", email: "vinay@example.com" }
298
+
299
+ 10. Back in service
300
+ → data is not null. ✓
301
+ → logService("info", "User fetched", { userId: 42 })
302
+ └─ sanitizeLogData masks "userId" if LOG_TYPE=2, removes if LOG_TYPE=3
303
+ └─ Writes JSON line to logs/app-2026-02-21.log
304
+ → return { id: 42, name: "Vinay", email: "vinay@example.com" }
305
+
306
+ 11. Back in controller
307
+ → user = { id: 42, name: "Vinay", ... }
308
+ → ResponseUtil.success(res, "User fetched successfully", user)
309
+
310
+ 12. Response sent to client:
311
+ HTTP 200
312
+ {
313
+ "success": true,
314
+ "statusCode": 200,
315
+ "message": "User fetched successfully",
316
+ "data": { "id": 42, "name": "Vinay", "email": "vinay@example.com" },
317
+ "errors": null
318
+ }
319
+ ```
320
+
321
+ ---
322
+
323
+ ### What if user ID doesn't exist — the error path traced
324
+
325
+ ```
326
+ 8. userRepository.findById("999")
327
+ → API returns null / DB returns null
328
+
329
+ 9. Back in service:
330
+ → data is null
331
+ → const err = new Error("User not found")
332
+ → err.status = 404
333
+ → throw err
334
+
335
+ 10. asyncHandler catches the throw
336
+ → calls next(err)
337
+
338
+ 11. errorMiddleware receives err
339
+ → logService("error", "User not found", { stack: "..." })
340
+ → err.status === 404 → ResponseUtil.notFound(res, "User not found")
341
+
342
+ 12. Response sent to client:
343
+ HTTP 404
344
+ {
345
+ "success": false,
346
+ "statusCode": 404,
347
+ "message": "User not found",
348
+ "data": null,
349
+ "errors": null
350
+ }
351
+ ```
352
+
353
+ ---
354
+
355
+ ## 5. Configuration
356
+
357
+ All configuration lives in `src/config/app.config.js`. **Never read `process.env` directly anywhere else in the codebase.** Always import from config.
358
+
359
+ ```js
360
+ // src/config/app.config.js
361
+ import dotenv from "dotenv";
362
+ dotenv.config();
363
+
364
+ export default {
365
+ port: process.env.PORT || 3000,
366
+
367
+ api: {
368
+ timeout: Number(process.env.API_TIMEOUT) || 5000,
369
+ },
370
+
371
+ rateLimit: {
372
+ windowMs: 15 * 60 * 1000, // 15 minutes
373
+ max: 100, // max requests per window per IP
374
+ },
375
+
376
+ logging: {
377
+ mode: process.env.LOG_MODE || "internal", // "internal" | "external"
378
+ externalUrl: process.env.LOG_SERVICE_URL || "",
379
+ directory: "logs",
380
+ fileName: "app",
381
+ maxSize: 5 * 1024 * 1024, // 5 MB per log file
382
+ dailyRotate: true,
383
+ logType: Number(process.env.LOG_TYPE) || 1 // 1=show | 2=mask | 3=remove sensitive
384
+ },
385
+ };
386
+ ```
387
+
388
+ **To add a new config value:**
389
+ ```js
390
+ // .env
391
+ DB_URI=mongodb://localhost:27017/mydb
392
+
393
+ // app.config.js — add inside the export
394
+ db: {
395
+ uri: process.env.DB_URI || "mongodb://localhost:27017/mydb",
396
+ },
397
+
398
+ // Usage anywhere
399
+ import config from "../config/app.config.js";
400
+ const uri = config.db.uri;
401
+ ```
402
+
403
+ ---
404
+
405
+ ## 6. How to Write a Feature — Step by Step
406
+
407
+ Example feature: **Users — Get user by ID**
408
+
409
+ ---
410
+
411
+ ### Step 1: Create the Route
412
+
413
+ ```js
414
+ // src/routes/user.route.js
415
+ import express from "express";
416
+ import { getUserById } from "../controllers/user.controller.js";
417
+ import { validateGetUser } from "../validations/user.validation.js";
418
+
419
+ const router = express.Router();
420
+
421
+ router.get("/:id", validateGetUser, getUserById);
422
+
423
+ export default router;
424
+ ```
425
+
426
+ **Rules:**
427
+ - One file per feature/resource.
428
+ - Route file only defines HTTP method + path + middleware chain + controller.
429
+ - No business logic here.
430
+
431
+ ---
432
+
433
+ ### Step 2: Create the Controller
434
+
435
+ ```js
436
+ // src/controllers/user.controller.js
437
+ import { asyncHandler } from "../utils/asyncHandler.js";
438
+ import { ResponseUtil } from "../utils/response.util.js";
439
+ import { getUserByIdService } from "../services/user.service.js";
440
+
441
+ export const getUserById = asyncHandler(async (req, res) => {
442
+ const { id } = req.params;
443
+
444
+ const user = await getUserByIdService(id);
445
+
446
+ return ResponseUtil.success(res, "User fetched successfully", user);
447
+ });
448
+ ```
449
+
450
+ **Rules:**
451
+ - Always wrap with `asyncHandler` — it catches all thrown errors automatically.
452
+ - Only extract data from `req` (params, body, query, headers) here.
453
+ - Never write business logic or DB queries in a controller.
454
+ - Always use `ResponseUtil` for the response — never call `res.json()` directly.
455
+ - Return the `ResponseUtil` call so the function exits cleanly.
456
+
457
+ ---
458
+
459
+ ### Step 3: Create Validation Middleware
460
+
461
+ ```js
462
+ // src/validations/user.validation.js
463
+ import { ResponseUtil } from "../utils/response.util.js";
464
+
465
+ export const validateGetUser = (req, res, next) => {
466
+ const { id } = req.params;
467
+
468
+ const errors = [];
469
+
470
+ if (!id) errors.push("id is required");
471
+ if (isNaN(Number(id))) errors.push("id must be a number");
472
+
473
+ if (errors.length > 0) {
474
+ return ResponseUtil.validation(res, "Validation failed", errors);
475
+ }
476
+
477
+ next();
478
+ };
479
+ ```
480
+
481
+ **Rules:**
482
+ - Validation middleware runs before the controller.
483
+ - Use `ResponseUtil.validation()` (422) to return errors.
484
+ - Never throw from validation — call `ResponseUtil` directly and return.
485
+ - Keep validation pure: only checks shape/format of input, no DB calls.
486
+
487
+ ---
488
+
489
+ ### Step 4: Create the Service
490
+
491
+ ```js
492
+ // src/services/user.service.js
493
+ import { userRepository } from "../repositories/user.repository.js";
494
+ import { logService } from "../utils/log.util.js";
495
+
496
+ export const getUserByIdService = async (id) => {
497
+ const user = await userRepository.findById(id);
498
+
499
+ await logService("info", "User fetched", { userId: id });
500
+
501
+ if (!user) {
502
+ const error = new Error("User not found");
503
+ error.status = 404;
504
+ throw error;
505
+ }
506
+
507
+ return user;
508
+ };
509
+ ```
510
+
511
+ **Rules:**
512
+ - Services contain all business logic.
513
+ - Services call repositories, never call DB drivers directly.
514
+ - To signal an error to the client, create an `Error`, set `error.status` to the HTTP code, and `throw` it. The `errorMiddleware` will catch it.
515
+ - Log meaningful events here with `logService`.
516
+ - Services do not touch `req` or `res`. They receive plain data and return plain data.
517
+
518
+ **Supported error status codes:**
519
+
520
+ | `error.status` | Response sent |
521
+ |----------------|--------------|
522
+ | 400 | Bad Request |
523
+ | 401 | Unauthorized |
524
+ | 403 | Forbidden |
525
+ | 404 | Not Found |
526
+ | 422 | Validation Error |
527
+ | anything else | 500 Internal Server Error |
528
+
529
+ ---
530
+
531
+ ### Step 5: Create the Repository
532
+
533
+ ```js
534
+ // src/repositories/user.repository.js
535
+ import { apiCall } from "../utils/axios.util.js";
536
+ // OR import your DB model here
537
+
538
+ export const userRepository = {
539
+ findById: async (id) => {
540
+ // External API example:
541
+ return apiCall({
542
+ method: "GET",
543
+ url: `https://jsonplaceholder.typicode.com/users/${id}`,
544
+ });
545
+
546
+ // MongoDB example (once model is connected):
547
+ // return UserModel.findById(id).lean();
548
+
549
+ // MySQL/Postgres example:
550
+ // return db.query("SELECT * FROM users WHERE id = ?", [id]);
551
+ },
552
+ };
553
+ ```
554
+
555
+ **Rules:**
556
+ - Repositories are the only layer allowed to talk to a DB or external API.
557
+ - Export a plain object with named methods — this makes it easy to mock in tests.
558
+ - Never put business logic here. Just raw data operations.
559
+ - Return raw data. Let the service decide what to do with it.
560
+
561
+ ---
562
+
563
+ ### Step 6: Create the Model (optional)
564
+
565
+ Models are used only when you have a connected database. Leave empty if using only external API calls.
566
+
567
+ ```js
568
+ // src/models/user.model.js — MongoDB example
569
+ import mongoose from "mongoose";
570
+
571
+ const userSchema = new mongoose.Schema({
572
+ name: { type: String, required: true },
573
+ email: { type: String, required: true, unique: true },
574
+ createdAt: { type: Date, default: Date.now },
575
+ });
576
+
577
+ export const UserModel = mongoose.model("User", userSchema);
578
+ ```
579
+
580
+ ---
581
+
582
+ ### Step 7: Register the Route
583
+
584
+ ```js
585
+ // src/routes/index.route.js
586
+ import express from "express";
587
+ import sampleRoutes from "./sample1.route.js";
588
+ import userRoutes from "./user.route.js"; // ← add this
589
+
590
+ const router = express.Router();
591
+
592
+ router.use("/sample", sampleRoutes);
593
+ router.use("/users", userRoutes); // ← and this
594
+
595
+ export default router;
596
+ ```
597
+
598
+ Final endpoint: `GET /api/v1/users/:id`
599
+
600
+ ---
601
+
602
+ ## 7. Utilities — How to Use Each One
603
+
604
+ ---
605
+
606
+ ### asyncHandler
607
+
608
+ **File:** `src/utils/asyncHandler.js`
609
+
610
+ **Purpose:** Eliminates the need for `try/catch` in every controller. If the async function throws, it automatically calls `next(error)` which triggers `errorMiddleware`.
611
+
612
+ ```js
613
+ // Without asyncHandler — verbose and error-prone
614
+ router.get("/", async (req, res, next) => {
615
+ try {
616
+ const data = await someService();
617
+ res.json(data);
618
+ } catch (err) {
619
+ next(err);
620
+ }
621
+ });
622
+
623
+ // With asyncHandler — clean
624
+ import { asyncHandler } from "../utils/asyncHandler.js";
625
+
626
+ router.get("/", asyncHandler(async (req, res) => {
627
+ const data = await someService();
628
+ ResponseUtil.success(res, "OK", data);
629
+ }));
630
+ ```
631
+
632
+ **Rule:** Every controller function must be wrapped in `asyncHandler`. No exceptions.
633
+
634
+ ---
635
+
636
+ ### ResponseUtil
637
+
638
+ **File:** `src/utils/response.util.js`
639
+
640
+ **Purpose:** Enforces a consistent JSON response shape across the entire API.
641
+
642
+ **Response shape:**
643
+ ```json
644
+ {
645
+ "success": true,
646
+ "statusCode": 200,
647
+ "message": "User fetched successfully",
648
+ "data": { ... },
649
+ "errors": null
650
+ }
651
+ ```
652
+
653
+ **Available methods:**
654
+
655
+ ```js
656
+ import { ResponseUtil } from "../utils/response.util.js";
657
+
658
+ ResponseUtil.success(res, "Message", data); // 200
659
+ ResponseUtil.created(res, "Created", data); // 201
660
+ ResponseUtil.badRequest(res, "Bad input", errors); // 400
661
+ ResponseUtil.unauthorized(res, "Login required"); // 401
662
+ ResponseUtil.forbidden(res, "No access"); // 403
663
+ ResponseUtil.notFound(res, "Not found"); // 404
664
+ ResponseUtil.validation(res, "Failed", errorsArray); // 422
665
+ ResponseUtil.exception(res, "Server error"); // 500
666
+
667
+ // Custom status code:
668
+ ResponseUtil.send(res, 429, "Too many requests");
669
+ ```
670
+
671
+ **Rule:** Never use `res.json()`, `res.send()`, or `res.status()` directly. Always go through `ResponseUtil`.
672
+
673
+ ---
674
+
675
+ ### logService
676
+
677
+ **File:** `src/utils/log.util.js`
678
+
679
+ **Purpose:** Writes structured log entries to a rotating daily log file, or posts them to an external log service.
680
+
681
+ ```js
682
+ import { logService } from "../utils/log.util.js";
683
+
684
+ // Levels: "info" | "warn" | "error" | "debug"
685
+ await logService("info", "User logged in", { userId: 42, role: "admin" });
686
+ await logService("error", "DB connection failed", { host: "localhost" });
687
+ await logService("warn", "Rate limit approaching", { ip: "192.168.1.1" });
688
+ ```
689
+
690
+ **Log output (written to `logs/app-YYYY-MM-DD.log`):**
691
+ ```json
692
+ {
693
+ "level": "info",
694
+ "message": "User logged in",
695
+ "timestamp": "21/02/2026, 10:30:00 am",
696
+ "meta": { "userId": 42, "role": "admin" }
697
+ }
698
+ ```
699
+
700
+ **Rules:**
701
+ - Always `await` the log call.
702
+ - Log at the service layer, not in controllers or repositories.
703
+ - Sensitive fields in `meta` are automatically masked based on `LOG_TYPE` (see section 10).
704
+
705
+ ---
706
+
707
+ ### apiCall (axios.util)
708
+
709
+ **File:** `src/utils/axios.util.js`
710
+
711
+ **Purpose:** A centralised HTTP client wrapper around axios with a global timeout from config.
712
+
713
+ ```js
714
+ import { apiCall } from "../utils/axios.util.js";
715
+
716
+ // GET request
717
+ const posts = await apiCall({
718
+ method: "GET",
719
+ url: "https://api.example.com/posts",
720
+ });
721
+
722
+ // POST request with body
723
+ const result = await apiCall({
724
+ method: "POST",
725
+ url: "https://api.example.com/users",
726
+ data: { name: "Vinay", email: "vinay@example.com" },
727
+ });
728
+
729
+ // With custom headers
730
+ const secured = await apiCall({
731
+ method: "GET",
732
+ url: "https://api.example.com/secure",
733
+ headers: { Authorization: "Bearer TOKEN" },
734
+ });
735
+ ```
736
+
737
+ **Rules:**
738
+ - Only use `apiCall` from repositories when calling external APIs.
739
+ - Never call `axios` directly anywhere in the codebase.
740
+ - Timeout is set globally via `config.api.timeout` (default 5000ms).
741
+
742
+ ---
743
+
744
+ ### logSanitizer / sensitiveKeys
745
+
746
+ **Files:** `src/utils/logSanitizer.util.js`, `src/utils/sensitiveKeys.util.js`
747
+
748
+ **Purpose:** Automatically strips or masks sensitive field values from log metadata so passwords, tokens, and IDs are never written to log files in plain text.
749
+
750
+ **To add a sensitive field:**
751
+ ```js
752
+ // src/utils/sensitiveKeys.util.js
753
+ export const sensitiveKeys = [
754
+ "userId", // existing
755
+ "id", // existing
756
+ "password", // ← add yours here
757
+ "token",
758
+ "email",
759
+ "creditCard",
760
+ ];
761
+ ```
762
+
763
+ **Behaviour controlled by `LOG_TYPE` in `.env`:**
764
+
765
+ | `LOG_TYPE` | Behaviour |
766
+ |-----------|-----------|
767
+ | `1` | Sensitive values shown as-is (development) |
768
+ | `2` | Sensitive values masked: `vi****ay` |
769
+ | `3` | Sensitive keys removed entirely from logs |
770
+
771
+ **You never need to call `sanitizeLogData` manually** — `logService` calls it automatically.
772
+
773
+ ---
774
+
775
+ ## 8. Middlewares
776
+
777
+ ---
778
+
779
+ ### rateLimitMiddleware
780
+
781
+ **File:** `src/middlewares/rateLimit.middleware.js`
782
+
783
+ Limits each IP to **100 requests per 15-minute window** (configurable in `app.config.js`).
784
+
785
+ Returns `429 Too Many Requests` via `ResponseUtil.send` when exceeded.
786
+
787
+ **To change limits:**
788
+ ```js
789
+ // app.config.js
790
+ rateLimit: {
791
+ windowMs: 10 * 60 * 1000, // 10 minutes
792
+ max: 50, // 50 requests
793
+ },
794
+ ```
795
+
796
+ ---
797
+
798
+ ### ipMiddleware
799
+
800
+ **File:** `src/middlewares/ip.middleware.js`
801
+
802
+ Only allows requests from IPs listed in the `allowedIps` array. All others receive `403 Forbidden`.
803
+
804
+ **To allow more IPs:**
805
+ ```js
806
+ const allowedIps = [
807
+ "127.0.0.1", // localhost IPv4
808
+ "::1", // localhost IPv6
809
+ "192.168.1.10", // existing
810
+ "203.0.113.50", // ← add your server/office IP
811
+ ];
812
+ ```
813
+
814
+ > To disable IP filtering in development, move `ipMiddleware` out of `app.js` or wrap it with an env check.
815
+
816
+ ---
817
+
818
+ ### errorMiddleware
819
+
820
+ **File:** `src/middlewares/error.middleware.js`
821
+
822
+ The **last middleware** in `app.js`. Catches every error that reaches `next(err)` (including those thrown inside `asyncHandler`).
823
+
824
+ - Logs the error with `logService("error", ...)`.
825
+ - Maps `error.status` to the correct `ResponseUtil` method.
826
+ - You never need to call this manually — just throw errors in services.
827
+
828
+ **How to trigger it from a service:**
829
+ ```js
830
+ const error = new Error("Item not found");
831
+ error.status = 404;
832
+ throw error; // ← asyncHandler sends this to errorMiddleware
833
+ ```
834
+
835
+ ---
836
+
837
+ ### encryptMiddleware
838
+
839
+ **File:** `src/middlewares/encrypt.middleware.js`
840
+
841
+ A placeholder for encrypting/decrypting request or response payloads. Currently a no-op (`next()` only).
842
+
843
+ **To use it on specific routes:**
844
+ ```js
845
+ import { encryptMiddleware } from "../middlewares/encrypt.middleware.js";
846
+
847
+ router.post("/sensitive-data", encryptMiddleware, myController);
848
+ ```
849
+
850
+ **To add to all routes**, add it in `app.js`:
851
+ ```js
852
+ app.use(encryptMiddleware);
853
+ ```
854
+
855
+ ---
856
+
857
+ ## 9. Database Connections
858
+
859
+ The `src/db/` folder has three empty setup files. Choose the one matching your database, connect in `server.js` before starting the listener.
860
+
861
+ ### MongoDB (Mongoose)
862
+
863
+ ```js
864
+ // src/db/mongo.db.js
865
+ import mongoose from "mongoose";
866
+ import config from "../config/app.config.js";
867
+
868
+ export const connectMongo = async () => {
869
+ await mongoose.connect(config.db.uri);
870
+ console.log("MongoDB connected");
871
+ };
872
+ ```
873
+
874
+ ```js
875
+ // src/server.js
876
+ import { connectMongo } from "./db/mongo.db.js";
877
+ import app from "./app.js";
878
+ import config from "./config/app.config.js";
879
+
880
+ connectMongo().then(() => {
881
+ app.listen(config.port, () => {
882
+ console.log(`Server running on port: ${config.port}`);
883
+ });
884
+ });
885
+ ```
886
+
887
+ ### MySQL / PostgreSQL
888
+
889
+ Follow the same pattern using `mysql2` or `pg` pool, exporting a `connectDB` function and calling it in `server.js`.
890
+
891
+ ---
892
+
893
+ ## 10. Logging System Deep Dive
894
+
895
+ | Setting | Controlled by | Default |
896
+ |---------|--------------|---------|
897
+ | Log destination | `LOG_MODE` env | `internal` (file) |
898
+ | External log URL | `LOG_SERVICE_URL` env | none |
899
+ | Log file name | `app.config.js` | `app-YYYY-MM-DD.log` |
900
+ | Max file size | `app.config.js` | 5 MB (then `.backup`) |
901
+ | Daily rotation | `app.config.js` | enabled |
902
+ | Sensitive data | `LOG_TYPE` env | `1` (show all) |
903
+
904
+ **In development**, set `LOG_TYPE=1` to see all data.
905
+ **In production**, set `LOG_TYPE=3` to remove all sensitive fields from logs.
906
+
907
+ Log files are written to the `logs/` directory at the project root.
908
+
909
+ ---
910
+
911
+ ## 11. Environment Variables Reference
912
+
913
+ Create a `.env` file in the project root:
914
+
915
+ ```env
916
+ # Server
917
+ PORT=3000
918
+
919
+ # API
920
+ API_TIMEOUT=5000
921
+
922
+ # Logging
923
+ LOG_MODE=internal # internal | external
924
+ LOG_SERVICE_URL= # only needed if LOG_MODE=external
925
+ LOG_TYPE=1 # 1=show | 2=mask | 3=remove sensitive fields
926
+
927
+ # Database (add as needed)
928
+ DB_URI=mongodb://localhost:27017/mydb
929
+ ```
930
+
931
+ ---
932
+
933
+ ## 12. Code Standards & Conventions
934
+
935
+ > These are not suggestions. They are the rules that keep the codebase consistent, predictable, and easy to navigate for anyone who picks it up.
936
+
937
+ ---
938
+
939
+ ### Rule 1 — Single Responsibility Per Layer
940
+
941
+ Every file has exactly one job. If you find yourself doing two things in one file, split it.
942
+
943
+ | Layer | Its ONE job | What it must NOT do |
944
+ |-------|------------|---------------------|
945
+ | Route | Define URL shape + middleware chain | No logic, no DB calls |
946
+ | Validation | Check input shape/format | No DB calls, no business rules |
947
+ | Controller | Bridge req → service → res | No business logic, no DB calls |
948
+ | Service | Business logic + decisions | No res.json(), no DB calls directly |
949
+ | Repository | Read/write data | No business rules, no response building |
950
+ | Util | A single reusable operation | No app-specific logic |
951
+ | Middleware | One cross-cutting concern | No feature-specific business logic |
952
+
953
+ ---
954
+
955
+ ### Rule 2 — File & Folder Naming
956
+
957
+ ```
958
+ ✅ user.route.js → <feature>.route.js
959
+ ✅ user.controller.js → <feature>.controller.js
960
+ ✅ user.service.js → <feature>.service.js
961
+ ✅ user.repository.js → <feature>.repository.js
962
+ ✅ user.model.js → <feature>.model.js
963
+ ✅ user.validation.js → <feature>.validation.js
964
+ ✅ auth.middleware.js → <name>.middleware.js
965
+ ✅ response.util.js → <name>.util.js
966
+
967
+ ❌ userController.js (no camelCase file names)
968
+ ❌ UserRoute.js (no PascalCase file names)
969
+ ❌ get-user.js (no verb-based names for modules)
970
+ ```
971
+
972
+ - Use `kebab-case` for all file names.
973
+ - Never abbreviate (`usr`, `ctrl`, `svc`) — spell it out.
974
+ - Group files in the folder that matches their layer. Never mix layers in one folder.
975
+
976
+ ---
977
+
978
+ ### Rule 3 — Naming Conventions in Code
979
+
980
+ ```js
981
+ // ✅ Variables & functions → camelCase
982
+ const userId = req.params.id;
983
+ const fetchedUser = await getUserByIdService(id);
984
+
985
+ // ✅ Classes & Models → PascalCase
986
+ class UserRepository { ... }
987
+ const UserModel = mongoose.model("User", userSchema);
988
+
989
+ // ✅ Constants that never change → UPPER_SNAKE_CASE
990
+ const MAX_RETRIES = 3;
991
+ const DEFAULT_TIMEOUT = 5000;
992
+
993
+ // ✅ Exported service functions → camelCase verb + noun
994
+ export const getUserByIdService = async (id) => { ... };
995
+ export const createUserService = async (data) => { ... };
996
+ export const deleteUserService = async (id) => { ... };
997
+
998
+ // ✅ Exported controllers → camelCase verb + noun
999
+ export const getUser = asyncHandler(async (req, res) => { ... });
1000
+ export const createUser = asyncHandler(async (req, res) => { ... });
1001
+ export const deleteUser = asyncHandler(async (req, res) => { ... });
1002
+
1003
+ // ✅ Validation exports → validate + PascalCase feature + action
1004
+ export const validateGetUser = (req, res, next) => { ... };
1005
+ export const validateCreateUser = (req, res, next) => { ... };
1006
+
1007
+ // ❌ Avoid vague names
1008
+ const data = ...; // what data?
1009
+ const result = ...; // result of what?
1010
+ const temp = ...; // always temporary — never commit this
1011
+ const x = ...; // never
1012
+ ```
1013
+
1014
+ ---
1015
+
1016
+ ### Rule 4 — Import Order
1017
+
1018
+ Organise imports in this order, with a blank line between groups:
1019
+
1020
+ ```js
1021
+ // 1. Node.js built-in modules
1022
+ import fs from "fs";
1023
+ import path from "path";
1024
+
1025
+ // 2. Third-party npm packages
1026
+ import express from "express";
1027
+ import axios from "axios";
1028
+
1029
+ // 3. Internal config
1030
+ import config from "../config/app.config.js";
1031
+
1032
+ // 4. Internal utils
1033
+ import { logService } from "../utils/log.util.js";
1034
+ import { ResponseUtil } from "../utils/response.util.js";
1035
+
1036
+ // 5. Internal app modules (routes, controllers, services, repos, models)
1037
+ import { userRepository } from "../repositories/user.repository.js";
1038
+ ```
1039
+
1040
+ - Always include the `.js` extension — ES Modules require it.
1041
+ - Always use relative paths within `src/`.
1042
+ - Never use `index.js` barrel re-exports — import the file directly.
1043
+
1044
+ ---
1045
+
1046
+ ### Rule 5 — Exports
1047
+
1048
+ ```js
1049
+ // Controllers → named export per handler
1050
+ export const getUser = asyncHandler(...);
1051
+ export const createUser = asyncHandler(...);
1052
+
1053
+ // Services → named export per function
1054
+ export const getUserByIdService = async (id) => { ... };
1055
+
1056
+ // Repositories → named export of a single object
1057
+ export const userRepository = {
1058
+ findById: async (id) => { ... },
1059
+ create: async (data) => { ... },
1060
+ };
1061
+
1062
+ // Config → single default export
1063
+ export default { port: ..., db: { ... } };
1064
+
1065
+ // Utils → named exports
1066
+ export const ResponseUtil = { ... };
1067
+ export const asyncHandler = (fn) => { ... };
1068
+ ```
1069
+
1070
+ **Never mix named and default exports in the same file.**
1071
+
1072
+ ---
1073
+
1074
+ ### Rule 6 — Async / Await
1075
+
1076
+ ```js
1077
+ // ✅ Always async/await
1078
+ const user = await userRepository.findById(id);
1079
+
1080
+ // ✅ Controller always wrapped in asyncHandler
1081
+ export const getUser = asyncHandler(async (req, res) => {
1082
+ const user = await getUserByIdService(req.params.id);
1083
+ return ResponseUtil.success(res, "OK", user);
1084
+ });
1085
+
1086
+ // ❌ Never raw .then().catch() chains
1087
+ userRepository.findById(id)
1088
+ .then(user => res.json(user))
1089
+ .catch(err => next(err)); // ← don't do this
1090
+
1091
+ // ❌ Never forget await — silent bugs
1092
+ const user = userRepository.findById(id); // returns a Promise, not data!
1093
+ ```
1094
+
1095
+ ---
1096
+
1097
+ ### Rule 7 — Error Handling
1098
+
1099
+ ```js
1100
+ // ✅ In a service — throw with a status code
1101
+ if (!user) {
1102
+ const err = new Error("User not found");
1103
+ err.status = 404;
1104
+ throw err;
1105
+ }
1106
+
1107
+ // ✅ Carry extra detail when needed
1108
+ if (existingUser) {
1109
+ const err = new Error("Email already registered");
1110
+ err.status = 409; // Conflict
1111
+ err.field = "email"; // optional extra context
1112
+ throw err;
1113
+ }
1114
+
1115
+ // ✅ In validation middleware — respond directly, don't throw
1116
+ if (errors.length > 0) {
1117
+ return ResponseUtil.validation(res, "Validation failed", errors);
1118
+ }
1119
+
1120
+ // ❌ Never swallow errors silently
1121
+ try {
1122
+ await doSomething();
1123
+ } catch (err) {
1124
+ // nothing here — the error disappears and the bug is hidden
1125
+ }
1126
+
1127
+ // ❌ Never send response from a service
1128
+ if (!user) {
1129
+ return res.status(404).json({ message: "Not found" }); // ← wrong layer
1130
+ }
1131
+
1132
+ // ❌ Never catch-and-ignore in repositories
1133
+ findById: async (id) => {
1134
+ try {
1135
+ return await UserModel.findById(id);
1136
+ } catch (err) {
1137
+ return null; // ← bug hidden, service thinks "not found" when it's a crash
1138
+ }
1139
+ }
1140
+ ```
1141
+
1142
+ **HTTP status codes to use:**
1143
+
1144
+ | Code | Meaning | When to use |
1145
+ |------|---------|-------------|
1146
+ | `400` | Bad Request | Malformed input the client sent |
1147
+ | `401` | Unauthorized | Not logged in / missing token |
1148
+ | `403` | Forbidden | Logged in but no permission |
1149
+ | `404` | Not Found | Resource does not exist |
1150
+ | `409` | Conflict | Duplicate resource (email already exists) |
1151
+ | `422` | Unprocessable | Validation failed (use from validation layer) |
1152
+ | `429` | Too Many Requests | Rate limit exceeded (handled by middleware) |
1153
+ | `500` | Server Error | Anything unexpected — default fallback |
1154
+
1155
+ ---
1156
+
1157
+ ### Rule 8 — Responses
1158
+
1159
+ ```js
1160
+ // ✅ Always use ResponseUtil
1161
+ return ResponseUtil.success(res, "User created", user); // 200
1162
+ return ResponseUtil.created(res, "User created", user); // 201
1163
+ return ResponseUtil.notFound(res, "User not found"); // 404
1164
+
1165
+ // ✅ Always return the ResponseUtil call in a controller
1166
+ export const getUser = asyncHandler(async (req, res) => {
1167
+ const user = await getUserByIdService(id);
1168
+ return ResponseUtil.success(res, "OK", user); // ← return prevents accidental double-send
1169
+ });
1170
+
1171
+ // ❌ Never call res directly
1172
+ res.json({ user }); // no standard shape
1173
+ res.status(200).send(user); // bypasses ResponseUtil
1174
+ res.status(201).json({ user }); // inconsistent shape
1175
+ ```
1176
+
1177
+ **Every API response has this exact shape — always:**
1178
+ ```json
1179
+ {
1180
+ "success": true,
1181
+ "statusCode": 200,
1182
+ "message": "User fetched successfully",
1183
+ "data": { },
1184
+ "errors": null
1185
+ }
1186
+ ```
1187
+
1188
+ ---
1189
+
1190
+ ### Rule 9 — Logging
1191
+
1192
+ ```js
1193
+ // ✅ Log meaningful business events in services
1194
+ await logService("info", "User registered", { userId: user.id, plan: "free" });
1195
+ await logService("warn", "Login failed", { email, attempts: failCount });
1196
+ await logService("error", "Payment failed", { orderId, reason });
1197
+
1198
+ // ✅ Always await logService
1199
+ await logService("info", "...", { ... }); // ← sync file writes need this
1200
+
1201
+ // ✅ Include relevant context in meta — not just the message
1202
+ await logService("info", "Order placed", { orderId, userId, amount, currency });
1203
+
1204
+ // ❌ Don't log in controllers — wrong layer
1205
+ export const getUser = asyncHandler(async (req, res) => {
1206
+ await logService("info", "getUser called"); // ← move this to the service
1207
+ ...
1208
+ });
1209
+
1210
+ // ❌ Don't use console.log in production code
1211
+ console.log(user); // won't be in log files, not structured, not sanitized
1212
+ console.error(err); // use logService("error", ...) instead
1213
+
1214
+ // ❌ Don't log sensitive data manually
1215
+ await logService("info", "User login", { password: req.body.password }); // ← add "password" to sensitiveKeys instead
1216
+ ```
1217
+
1218
+ **Log levels — when to use which:**
1219
+
1220
+ | Level | When |
1221
+ |-------|------|
1222
+ | `"info"` | Normal business events (user created, order placed, data fetched) |
1223
+ | `"warn"` | Something unexpected but not fatal (retry attempted, limit approaching) |
1224
+ | `"error"` | Something failed that needs attention (DB error, payment failed) |
1225
+ | `"debug"` | Detailed info for tracing — remove before production |
1226
+
1227
+ ---
1228
+
1229
+ ### Rule 10 — Configuration
1230
+
1231
+ ```js
1232
+ // ✅ Only ever read process.env in app.config.js
1233
+ // app.config.js
1234
+ export default {
1235
+ db: { uri: process.env.DB_URI || "mongodb://localhost/mydb" },
1236
+ };
1237
+
1238
+ // ✅ Everywhere else — import from config
1239
+ import config from "../config/app.config.js";
1240
+ const uri = config.db.uri;
1241
+
1242
+ // ❌ Never read process.env outside of app.config.js
1243
+ const timeout = Number(process.env.API_TIMEOUT); // ← scattered, untracked
1244
+ if (process.env.NODE_ENV === "production") { ... } // ← add NODE_ENV to config first
1245
+ ```
1246
+
1247
+ This means all env usage is visible in one file. If an env var is renamed or removed, there's one place to fix it.
1248
+
1249
+ ---
1250
+
1251
+ ### Rule 11 — Keep Functions Small and Flat
1252
+
1253
+ ```js
1254
+ // ✅ One function = one action. Short and readable.
1255
+ export const createUserService = async (data) => {
1256
+ const exists = await userRepository.findByEmail(data.email);
1257
+
1258
+ if (exists) {
1259
+ const err = new Error("Email already registered");
1260
+ err.status = 409;
1261
+ throw err;
1262
+ }
1263
+
1264
+ const user = await userRepository.create(data);
1265
+ await logService("info", "User created", { userId: user.id });
1266
+ return user;
1267
+ };
1268
+
1269
+ // ❌ Avoid deep nesting — flatten with early returns
1270
+ export const createUserService = async (data) => {
1271
+ const exists = await userRepository.findByEmail(data.email);
1272
+ if (!exists) {
1273
+ const user = await userRepository.create(data);
1274
+ if (user) {
1275
+ await logService("info", "created", { id: user.id });
1276
+ if (user.id) {
1277
+ return user; // ← 4 levels deep, hard to follow
1278
+ }
1279
+ }
1280
+ }
1281
+ };
1282
+ ```
1283
+
1284
+ **Target:** max **2 levels of nesting** inside a function. Use early returns to bail out.
1285
+
1286
+ ---
1287
+
1288
+ ### Rule 12 — Comments
1289
+
1290
+ ```js
1291
+ // ✅ Comment WHY, not WHAT — the code already says what
1292
+ // Rate window is 15 min to align with our SLA response commitment
1293
+ windowMs: 15 * 60 * 1000,
1294
+
1295
+ // ✅ Mark intentional decisions
1296
+ // LOG_TYPE 3 removes keys entirely — preferred for PCI-DSS compliance
1297
+ if (config.logging.logType === 3) { continue; }
1298
+
1299
+ // ❌ Don't comment obvious code
1300
+ // Get user by id
1301
+ const user = await userRepository.findById(id); // ← the code already says this
1302
+
1303
+ // ❌ Don't leave dead code commented out — delete it (git can restore it)
1304
+ // const oldService = await legacyUserService(id);
1305
+ // return oldService.data;
1306
+ ```
1307
+
1308
+ ---
1309
+
1310
+ ### Rule 13 — Repository Shape
1311
+
1312
+ Repositories must always be exported as an **object with named method keys**, not as individual exported functions:
1313
+
1314
+ ```js
1315
+ // ✅ Correct — object with methods (easy to mock in tests)
1316
+ export const userRepository = {
1317
+ findById: async (id) => { ... },
1318
+ findByEmail: async (email) => { ... },
1319
+ create: async (data) => { ... },
1320
+ update: async (id, data) => { ... },
1321
+ remove: async (id) => { ... },
1322
+ };
1323
+
1324
+ // ❌ Wrong — individual exports make mocking harder
1325
+ export const findById = async (id) => { ... };
1326
+ export const findByEmail = async (email) => { ... };
1327
+ ```
1328
+
1329
+ ---
1330
+
1331
+ ### Rule 14 — Never Pass `req` or `res` to a Service
1332
+
1333
+ Services are pure logic functions. They must never know they are inside an HTTP context.
1334
+
1335
+ ```js
1336
+ // ✅ Controller extracts, service receives plain values
1337
+ export const getUser = asyncHandler(async (req, res) => {
1338
+ const { id } = req.params; // ← controller extracts
1339
+ const user = await getUserByIdService(id); // ← service gets plain value
1340
+ return ResponseUtil.success(res, "OK", user);
1341
+ });
1342
+
1343
+ // ❌ Never pass req or res into a service
1344
+ const user = await getUserByIdService(req); // ← service now depends on HTTP layer
1345
+ ```
1346
+
1347
+ This makes services reusable and testable outside of Express (e.g. from a CLI script, a cron job, or a test).
1348
+
1349
+ ---
1350
+
1351
+ ### Quick Reference Card
1352
+
1353
+ ```
1354
+ Layer Receives Returns Can call
1355
+ ──────────────────────────────────────────────────────────────────
1356
+ Middleware req, res, next next() or response ResponseUtil
1357
+ Validation req, res, next next() or 422 ResponseUtil
1358
+ Controller req, res response Service, ResponseUtil
1359
+ Service plain values plain data Repository, logService
1360
+ Repository plain values plain data apiCall, DB model
1361
+ Util anything anything other utils only
1362
+ ```
1363
+
1364
+ ---
1365
+
1366
+ ## 13. What NOT to Do
1367
+
1368
+ | ❌ Don't | ✅ Do instead |
1369
+ |---------|--------------|
1370
+ | Read `process.env.X` directly in business code | Import from `config/app.config.js` |
1371
+ | Call `res.json()` or `res.status()` directly | Use `ResponseUtil` methods |
1372
+ | Write try/catch in every controller | Wrap with `asyncHandler` and throw |
1373
+ | Put business logic in a controller | Move it to a service |
1374
+ | Put DB queries in a service | Move them to a repository |
1375
+ | Call `axios` directly | Use `apiCall` from `axios.util.js` |
1376
+ | Log raw objects with sensitive fields | Let `logService` sanitize via `sensitiveKeys` |
1377
+ | Leave `LOG_TYPE=1` in production | Set `LOG_TYPE=3` in production `.env` |
1378
+ | Add routes directly in `app.js` | Add them in `routes/index.route.js` |
1379
+ | Omit `.js` in import paths | Always include `.js` (ES Module requirement) |
1380
+
1381
+ ---
1382
+
1383
+ ## 14. CLI Package — How It Works
1384
+
1385
+ ### What the CLI does
1386
+
1387
+ When you run `npx create-node-prodkit my-api`, it:
1388
+
1389
+ 1. Validates the project name (letters, numbers, hyphens, underscores only).
1390
+ 2. Copies everything inside `templates/` to a new folder named `my-api` in your current directory.
1391
+ 3. Replaces the `{{PROJECT_NAME}}` placeholder inside `templates/package.json` with `my-api`.
1392
+ 4. Renames `gitignore` → `.gitignore` (npm strips dotfiles from published tarballs).
1393
+ 5. Runs `npm install` inside the new project folder.
1394
+ 6. Prints the next-steps guide.
1395
+
1396
+ ### CLI usage
1397
+
1398
+ ```bash
1399
+ # Using npx (recommended — always gets the latest version)
1400
+ npx create-node-prodkit my-api
1401
+
1402
+ # Using npm init shorthand
1403
+ npm create node-prodkit my-api
1404
+
1405
+ # Globally installed
1406
+ npm install -g create-node-prodkit
1407
+ create-node-prodkit my-api
1408
+ ```
1409
+
1410
+ ### What gets published to npm
1411
+
1412
+ Only these three items are included in the npm tarball (controlled by `"files"` in `package.json`):
1413
+
1414
+ ```
1415
+ bin/ ← CLI entry point
1416
+ templates/ ← the full skeleton that gets copied to new projects
1417
+ README.md ← documentation
1418
+ ```
1419
+
1420
+ The `src/` folder, `logs/`, `.env`, and dev config files are excluded via `.npmignore`.
1421
+
1422
+ ### Project layout after scaffolding
1423
+
1424
+ ```
1425
+ my-api/
1426
+ ├── src/ ← full skeleton ready to use
1427
+ ├── .env.example ← copy to .env and fill in values
1428
+ ├── .gitignore
1429
+ └── package.json ← name set to "my-api", deps installed
1430
+ ```
1431
+
1432
+ ---
1433
+
1434
+ ## 15. Maintaining & Publishing Updates
1435
+
1436
+ ### The golden rule
1437
+
1438
+ > **Whenever you change a file in `src/`, copy the same change to the matching file in `templates/src/`.**
1439
+
1440
+ The `src/` folder is the live working skeleton (for developing and testing the skeleton itself). `templates/src/` is the snapshot that gets distributed via npm.
1441
+
1442
+ ---
1443
+
1444
+ ### Step-by-step: making a change
1445
+
1446
+ #### 1. Make the change in `src/`
1447
+
1448
+ Edit the file normally. Test it by running `npm run dev` and verifying the behaviour.
1449
+
1450
+ #### 2. Mirror the change to `templates/src/`
1451
+
1452
+ For a single file:
1453
+ ```bash
1454
+ cp src/utils/response.util.js templates/src/utils/response.util.js
1455
+ ```
1456
+
1457
+ For a full sync of everything at once:
1458
+ ```bash
1459
+ node --input-type=module --eval "
1460
+ import { cpSync } from 'fs';
1461
+ cpSync('src', 'templates/src', { recursive: true });
1462
+ console.log('templates/src synced');
1463
+ "
1464
+ ```
1465
+
1466
+ #### 3. Bump the version — choose the right level
1467
+
1468
+ | Change type | Version bump | Command |
1469
+ |-------------|-------------|---------|
1470
+ | Bug fix, minor tweak | patch `1.0.0 → 1.0.1` | `npm version patch` |
1471
+ | New feature, new utility | minor `1.0.0 → 1.1.0` | `npm version minor` |
1472
+ | Breaking change (renames, removals) | major `1.0.0 → 2.0.0` | `npm version major` |
1473
+
1474
+ `npm version` automatically:
1475
+ - Updates `version` in `package.json`
1476
+ - Creates a git commit
1477
+ - Creates a git tag
1478
+
1479
+ #### 4. Log the change in CHANGELOG.md
1480
+
1481
+ ```md
1482
+ ## [1.1.0] - 2026-02-21
1483
+ ### Added
1484
+ - `logService` now supports an `"external"` mode posting to a remote URL
1485
+
1486
+ ### Changed
1487
+ - `asyncHandler` improved error propagation
1488
+
1489
+ ### Fixed
1490
+ - nodemon config path corrected to `src/server.js`
1491
+ ```
1492
+
1493
+ #### 5. Publish to npm
1494
+
1495
+ **First-time setup:**
1496
+ ```bash
1497
+ npm login # log in to your npm account
1498
+ ```
1499
+
1500
+ **Every release:**
1501
+ ```bash
1502
+ # Preview exactly what will be published before pushing
1503
+ npm pack --dry-run
1504
+
1505
+ # Publish
1506
+ npm publish
1507
+
1508
+ # Or publish with a tag (e.g., for a beta)
1509
+ npm publish --tag beta
1510
+ ```
1511
+
1512
+ ---
1513
+
1514
+ ### Quick release checklist
1515
+
1516
+ ```
1517
+ [ ] Change made in src/
1518
+ [ ] Same change mirrored to templates/src/
1519
+ [ ] CHANGELOG.md updated
1520
+ [ ] npm version patch|minor|major (updates package.json + git tag)
1521
+ [ ] git push && git push --tags
1522
+ [ ] npm publish
1523
+ [ ] Verify: npx create-node-prodkit@latest test-project
1524
+ ```
1525
+
1526
+ ---
1527
+
1528
+ ### What each file in this repo does for the CLI
1529
+
1530
+ | File / Folder | Purpose |
1531
+ |--------------|---------|
1532
+ | `bin/create.js` | CLI executable — parses args, copies templates, runs npm install |
1533
+ | `templates/` | Snapshot of the skeleton distributed via npm |
1534
+ | `templates/package.json` | Has `{{PROJECT_NAME}}` placeholder replaced at scaffold time |
1535
+ | `templates/gitignore` | Renamed to `.gitignore` during scaffold (npm strips dotfiles) |
1536
+ | `templates/.env.example` | All env vars documented with safe defaults |
1537
+ | `src/` | Live working skeleton for development — **not published to npm** |
1538
+ | `.npmignore` | Excludes `src/`, logs, `.env`, editor config from the tarball |
1539
+ | `package.json` `"files"` | Allowlist: only `bin/`, `templates/`, `README.md` go to npm |
1540
+