create-modular-repository-backend 1.0.3

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.
Files changed (31) hide show
  1. package/bin/index.js +32 -0
  2. package/package.json +19 -0
  3. package/template/app.ts +19 -0
  4. package/template/config/serverConfig.ts +14 -0
  5. package/template/di/.gitkeep +0 -0
  6. package/template/infrastructure/db/index.ts +1 -0
  7. package/template/infrastructure/db/mongo.ts +13 -0
  8. package/template/infrastructure/runtime-framework/express/async.handler.ts +11 -0
  9. package/template/infrastructure/runtime-framework/express/error.handler.ts +7 -0
  10. package/template/modules/_module/api/controllers/.gitkeep +0 -0
  11. package/template/modules/_module/api/enums/.gitkeep +0 -0
  12. package/template/modules/_module/api/middlewares/.gitkeep +0 -0
  13. package/template/modules/_module/api/routes/.gitkeep +0 -0
  14. package/template/modules/_module/domain/entity.ts +0 -0
  15. package/template/modules/_module/domain/projection.ts +0 -0
  16. package/template/modules/_module/errors/userExists.ts +10 -0
  17. package/template/modules/_module/index.ts +0 -0
  18. package/template/modules/_module/mappers/.gitkeep +0 -0
  19. package/template/modules/_module/persistance/interface/.gitkeep +0 -0
  20. package/template/modules/_module/persistance/model/.gitkeep +0 -0
  21. package/template/modules/_module/repository/.gitkeep +0 -0
  22. package/template/modules/_module/services/.gitkeep +0 -0
  23. package/template/package.json +22 -0
  24. package/template/readme.md +217 -0
  25. package/template/sever.ts +17 -0
  26. package/template/shared/base/BaseRepository.ts +0 -0
  27. package/template/shared/base/IBaseRepository.ts +0 -0
  28. package/template/shared/base/errors.ts +39 -0
  29. package/template/shared/types/general.d.ts +55 -0
  30. package/template/shared/utils/normalizeError.ts +8 -0
  31. package/template/tsconfig.json +11 -0
package/bin/index.js ADDED
@@ -0,0 +1,32 @@
1
+ #!/usr/bin/env node
2
+
3
+ const path = require("path");
4
+ const fs = require("fs");
5
+ const { execSync } = require("child_process");
6
+
7
+ const projectName = process.argv[2] || "repository-backend";
8
+ const targetDir = path.join(process.cwd(), projectName);
9
+
10
+ if (fs.existsSync(targetDir)) {
11
+ console.error("❌ Folder already exists");
12
+ process.exit(1);
13
+ };
14
+
15
+ console.log("🚀 Creating Moduler Repository Backend:", projectName);
16
+
17
+ // copy template
18
+ fs.cpSync(
19
+ path.join(__dirname, "../template"),
20
+ targetDir,
21
+ { recursive: true }
22
+ );
23
+
24
+ // install deps
25
+ console.log("📦 Installing dependencies...");
26
+ execSync("npm install", { cwd: targetDir, stdio: "inherit" });
27
+
28
+ //initiate git
29
+ execSync("git init", { cwd: targetDir, stdio: "inherit" } );
30
+
31
+ console.log("✅ Done!");
32
+ console.log(`👉 cd ${projectName} && npm run dev`);
package/package.json ADDED
@@ -0,0 +1,19 @@
1
+ {
2
+ "name": "create-modular-repository-backend",
3
+ "version": "1.0.3",
4
+ "description": "Scaffold backend with repository pattern",
5
+ "bin": {
6
+ "create-modular-repository-backend": "./bin/index.js"
7
+ },
8
+ "files": [
9
+ "bin",
10
+ "template"
11
+ ],
12
+ "keywords": [
13
+ "backend",
14
+ "repository-pattern",
15
+ "express",
16
+ "typescript"
17
+ ],
18
+ "license": "MIT"
19
+ }
@@ -0,0 +1,19 @@
1
+ import express from 'express';
2
+ import cors from 'cors';
3
+ import config from './config/serverConfig';
4
+ import {errorMiddleware} from './infrastructure/runtime-framework/express/error.handler'
5
+
6
+ const app = express();
7
+
8
+ app.use(cors(config.cors));
9
+
10
+ app.use(express.json());
11
+ app.use(express.urlencoded({ extended: true }));
12
+
13
+ // Register routes here (example)
14
+ // app.use('/users', userRoutes);
15
+
16
+ // Register global error handler last
17
+ app.use(errorMiddleware);
18
+
19
+ export default app;
@@ -0,0 +1,14 @@
1
+ import { ServerConfig } from '../shared/types/general';
2
+
3
+ const serverConfig: ServerConfig = {
4
+ port: 4001,
5
+ nodeEnv:'development',
6
+ cors: {
7
+ origin: [''],
8
+ credentials: true,
9
+ method: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS']
10
+ },
11
+
12
+ };
13
+
14
+ export default serverConfig;
File without changes
@@ -0,0 +1 @@
1
+ export * from "./mongo";
@@ -0,0 +1,13 @@
1
+ import mongoose from "mongoose";
2
+
3
+ export const connectDB = async () => {
4
+ if (!process.env.MONGO_URI) throw new Error("MONGO_URI not defined");
5
+
6
+ try {
7
+ await mongoose.connect(process.env.MONGO_URI);
8
+ console.log("✅ MongoDB connected");
9
+ } catch (err) {
10
+ console.error("❌ MongoDB connection failed", err);
11
+ process.exit(1);
12
+ }
13
+ };
@@ -0,0 +1,11 @@
1
+ //Express does not catch async errors automatically.
2
+ export const asyncHandler =
3
+ (fn: any) =>
4
+ (req:any, res: any, next: any) =>
5
+ Promise.resolve(fn(req, res, next)).catch(next);
6
+
7
+ //use it like this
8
+ // router.get(
9
+ // "/profile",
10
+ // asyncHandler(getProfile)
11
+ // );
@@ -0,0 +1,7 @@
1
+ import { Request, Response, NextFunction } from "express";
2
+ import { normalizeError } from "../../../shared/utils/normalizeError"
3
+
4
+ export const errorMiddleware = (err: unknown, req: Request, res: Response, next: NextFunction) => {
5
+ const { statusCode, message } = normalizeError(err);
6
+ res.status(statusCode).json({ message });
7
+ };
File without changes
File without changes
File without changes
File without changes
@@ -0,0 +1,10 @@
1
+ import { AppError } from "../../../shared/base/errors";
2
+
3
+ export class UserAlreadyExistsError extends AppError {
4
+ statusCode = 409;
5
+ isOperational = true;
6
+
7
+ constructor() {
8
+ super("User already exists");
9
+ }
10
+ }
File without changes
File without changes
File without changes
File without changes
@@ -0,0 +1,22 @@
1
+ {
2
+ "name": "repository-backend",
3
+ "private": true,
4
+ "scripts": {
5
+ "dev": "ts-node-dev src/server.ts",
6
+ "build": "tsc",
7
+ "start": "node dist/server.js"
8
+ },
9
+ "dependencies": {
10
+ "express": "^4.18.2",
11
+ "jsonwebtoken": "^9.0.3",
12
+ "bcryptjs": "^3.0.3",
13
+ "cors": "^2.8.5"
14
+ },
15
+ "devDependencies": {
16
+ "typescript": "^5.3.0",
17
+ "ts-node-dev": "^2.0.0",
18
+ "@types/express": "^4.17.21",
19
+ "@types/jsonwebtoken": "^9.0.10",
20
+ "@types/cors": "^2.8.19"
21
+ }
22
+ }
@@ -0,0 +1,217 @@
1
+ What a Modular Monolith actually is -
2
+ A single deployable app, but organized by feature, not by layer
3
+ A module owns its data, API, services, and repositories
4
+
5
+ [Highlights]
6
+ Modules are self-contained: models, entities, services, repositories, controllers.
7
+ Shared folder: reusable, framework-agnostic utilities, types, base classes.
8
+ Infrastructure: Express setup, DB connection, runtime-specific wrappers, error middleware.
9
+ Server.ts: initializes DB connection first, then starts app.
10
+ app.ts: only configures Express (middleware, routes, error handler).
11
+
12
+ [Folder Structure & Responsibilities]
13
+ -----------------------------------
14
+ template/
15
+ ├── config/
16
+ │ └── serverConfig.ts # Server configuration (port, CORS, etc.)
17
+ ├── di/ # Dependency injection setup
18
+ ├── infrastructure/
19
+ │ ├── db/
20
+ │ │ ├── index.ts # Export DB connections
21
+ │ │ └── mongo.ts # MongoDB connection logic
22
+ │ └── runtime-framework/express/
23
+ │ ├── async.handler.ts # Wrapper for async route handlers
24
+ │ └── error.handler.ts # Express global error middleware
25
+ ├── modules/_module/
26
+ │ ├── api/
27
+ │ │ ├── controllers/ # Route controllers
28
+ │ │ ├── enums/ # Module-specific enums
29
+ │ │ ├── middlewares/ # Module-specific middlewares
30
+ │ │ └── routes/ # Express routes for the module
31
+ │ ├── domain/
32
+ │ │ ├── entity.ts # Module-specific business entity
33
+ │ │ └── projection.ts # DTOs / projections for queries
34
+ │ ├── errors/
35
+ │ │ └── userExists.ts # Module-specific errors
36
+ │ ├── mappers/ # Maps between entities, DTOs, DB models
37
+ │ ├── persistance/
38
+ │ │ ├── interface/ # Repository interfaces
39
+ │ │ └── model/ # Module-specific DB models
40
+ │ ├── repository/ # Repository implementations
41
+ │ ├── services/ # Business logic / service layer
42
+ │ └── index.ts # Module export point
43
+ ├── shared/
44
+ │ ├── base/
45
+ │ │ ├── BaseRepository.ts # Generic base repository for CRUD
46
+ │ │ ├── errors.ts # AppError, NotFoundError, ValidationError, etc.
47
+ │ │ └── IBaseRepository.ts# Interface for base repository
48
+ │ ├── types/
49
+ │ │ └── general.d.ts # Global types/interfaces
50
+ │ └── utils/
51
+ │ └── normalizeError.ts # Normalizes errors for middleware
52
+ ├── .env # Environment variables
53
+ ├── .gitignore
54
+ ├── app.ts # Express app configuration
55
+ ├── package.json
56
+ └── server.ts # Server entry point; connects DB & starts app
57
+
58
+
59
+ [Responsibilities / Rules]
60
+ ------------------------
61
+ 1. config/
62
+ Store configuration variables (server port, CORS, JWT secrets).
63
+ Should not contain logic.
64
+
65
+ 2. di/
66
+ Handles dependency injection if required.
67
+ Modules should remain decoupled, use DI for services/repositories.
68
+
69
+ 3. infrastructure/
70
+ Any code that interacts with the outside world:
71
+ DB connections
72
+ Express-specific middleware
73
+ Async handler wrappers
74
+ No business logic here.
75
+
76
+ 4. modules/
77
+ Encapsulates domain, services, repositories, controllers for a single feature.
78
+ Each module owns:
79
+ Models: persistance/model → DB schema
80
+ Entities: domain/entity.ts → business objects
81
+ Repository interfaces: persistance/interface
82
+ Repository implementations: repository/
83
+ Services: business logic
84
+ Controllers: handle HTTP requests
85
+ Modules should not know about other modules.
86
+
87
+ 5. shared/
88
+ Contains generic reusable code:
89
+ Base repository
90
+ Shared types
91
+ Global errors
92
+ Utilities (like normalizeError)
93
+ Must be framework-agnostic — no Express-specific code here.
94
+
95
+ 6. app.ts
96
+ Sets up Express app:
97
+ Middleware
98
+ Routes
99
+ Global error handler
100
+ Does not start server or connect DB.
101
+
102
+ 7. server.ts
103
+ Entry point:
104
+ Connects to DB (connectDB())
105
+ Starts server (app.listen())
106
+ Handles runtime concerns only.
107
+
108
+
109
+ [Data Flow (Request → Response)]
110
+ ------------------------------
111
+ -HTTP request → controller (modules/<module>/api/controllers)
112
+ -Controller → service (modules/<module>/services)
113
+ -Service → repository (modules/<module>/repository)
114
+ -Repository → DB model (modules/<module>/persistance/model)
115
+ -DB returns → repository → service → controller
116
+ -Controller returns → response
117
+ Errors:
118
+ -Services / repositories throw AppError (or module-specific errors)
119
+ -normalizeError (shared/utils) converts errors into {statusCode, message}
120
+ -error.handler.ts (infra) sends JSON HTTP response
121
+
122
+ [Rules / Best Practices]
123
+ ----------------------
124
+ -Modules are independent → avoid cross-module imports.
125
+ -Shared folder is framework-agnostic.
126
+ -Infrastructure folder handles DB, HTTP, queues, and other external systems.
127
+ Error handling:
128
+ -Throw AppError or module-specific error
129
+ -Use async handler wrapper in controllers
130
+ -Let errorMiddleware handle HTTP response
131
+ -DB connections happen in server.ts only; modules access DB through repositories.
132
+ -app.ts is testable without server or DB.
133
+
134
+
135
+ [Modular Monolith Flow]
136
+ ---------------------
137
+
138
+ ┌──────────────┐
139
+ │ HTTP Client│
140
+ └───────┬──────┘
141
+
142
+
143
+ ┌───────────────────┐
144
+ │ modules/*/api │
145
+ │ (controllers + │
146
+ │ routes) │
147
+ └────────┬──────────┘
148
+ │ calls
149
+
150
+ ┌───────────────────┐
151
+ │ modules/*/services│
152
+ │ (business logic) │
153
+ └────────┬──────────┘
154
+ │ calls
155
+
156
+ ┌───────────────────────┐
157
+ │ modules/*/repository │
158
+ │(implements interfaces │
159
+ │ for DB access) │
160
+ └────────┬──────────────┘
161
+ │ interacts with
162
+
163
+ ┌─────────────────────┐
164
+ │modules/*/persistance│
165
+ │(DB models/schemas) │
166
+ └────────┬────────────┘
167
+ │ connects to
168
+
169
+ ┌───────────────────┐
170
+ │ infrastructure/db │
171
+ │ (connectDB, │
172
+ │ Mongo/PG) │
173
+ └────────┬──────────┘
174
+ │ initializes
175
+
176
+ Database
177
+ (Mongo/Postgres)
178
+
179
+
180
+ [Shared & Utilities Flow]
181
+ -----------------------
182
+ ┌─────────────────────────┐
183
+ │ shared/ │
184
+ │ ┌─────────────────────┐ │
185
+ │ │ base/ │ │
186
+ │ │ - BaseRepository │ │
187
+ │ │ - errors.ts │ │
188
+ │ │ - IBaseRepository │ │
189
+ │ └─────────────────────┘ │
190
+ │ ┌─────────────────────┐ │
191
+ │ │ types/ │ │
192
+ │ │ - general.d.ts │ │
193
+ │ └─────────────────────┘ │
194
+ │ ┌─────────────────────┐ │
195
+ │ │ utils/ │ │
196
+ │ │ - normalizeError │ │
197
+ │ └─────────────────────┘ │
198
+ └─────────────┬───────────┘
199
+ │ used by
200
+
201
+ modules/* and infrastructure
202
+
203
+
204
+ [Error Handling Flow]
205
+ -------------------
206
+ Modules Services / Repository
207
+
208
+ │ throw AppError or module-specific errors
209
+
210
+ normalizeError() in shared/utils
211
+
212
+
213
+ errorMiddleware in infrastructure/runtime-framework/express
214
+
215
+
216
+ Client HTTP Response
217
+ (Status code + JSON message)
@@ -0,0 +1,17 @@
1
+ import app from './app';
2
+ import serverConfig from './config/serverConfig';
3
+ import { connectDB } from './infrastructure/db';
4
+
5
+ const startServer = async () => {
6
+ try {
7
+ await connectDB(); // connect to DB first
8
+ app.listen(serverConfig.port, () => {
9
+ console.log(`Server running on http://localhost:${serverConfig.port}`);
10
+ });
11
+ } catch (err) {
12
+ console.error('Failed to start server', err);
13
+ process.exit(1);
14
+ }
15
+ };
16
+
17
+ startServer();
File without changes
File without changes
@@ -0,0 +1,39 @@
1
+ // shared/base/errors.ts
2
+ export abstract class AppError extends Error {
3
+ abstract statusCode: number;
4
+ abstract isOperational: boolean;
5
+
6
+ constructor(message: string) {
7
+ super(message);
8
+ Error.captureStackTrace(this, this.constructor);
9
+ }
10
+ }
11
+
12
+ export class NotFoundError extends AppError {
13
+ statusCode = 404;
14
+ isOperational = true;
15
+
16
+ constructor(resource = "Resource") {
17
+ super(`${resource} not found`);
18
+ }
19
+ }
20
+
21
+ export class ValidationError extends AppError {
22
+ statusCode = 400;
23
+ isOperational = true;
24
+ }
25
+
26
+ export class UnauthorizedError extends AppError {
27
+ statusCode = 401;
28
+ isOperational = true;
29
+ }
30
+
31
+ export class ForbiddenError extends AppError {
32
+ statusCode = 403;
33
+ isOperational = true;
34
+ }
35
+
36
+ export class ConflictError extends AppError {
37
+ statusCode = 409;
38
+ isOperational = true;
39
+ }
@@ -0,0 +1,55 @@
1
+ export { ParsedQs } from 'qs';
2
+
3
+ export type ServerConfig = {
4
+ port: number,
5
+ nodeEnv: string,
6
+ cors: Cors
7
+ };
8
+
9
+ export type Cors = {
10
+ origin: string[],
11
+ credentials: boolean,
12
+ method: string[],
13
+ };
14
+
15
+ type StringKeys<T> = {
16
+ [K in keyof T]: T[K] extends string ? K : never
17
+ }[keyof T];
18
+
19
+ export type SearchConfig<T> = {
20
+ searchableFields: StringKeys<T>[];
21
+ sortableFields: (keyof T)[];
22
+ statusField?: keyof T;
23
+ defaultSortField: keyof T;
24
+ defaultSortOrder?: "asc" | "desc";
25
+ dateField?: keyof T;
26
+ };
27
+
28
+ export type PaginatedResponse<T> = {
29
+ data: T[];
30
+ pagination: {
31
+ page: number;
32
+ pageSize: number;
33
+ totalRecords: number;
34
+ totalPages: number;
35
+ };
36
+ };
37
+
38
+
39
+ export type SearchFilterSortParamsType = {
40
+ search: string,
41
+ startDate: string,
42
+ endDate: string,
43
+ status: boolean | undefined,
44
+ sortColumn: string,
45
+ sortDirection: string,
46
+ page: number,
47
+ pageSize: number,
48
+ role?: string,
49
+ isBlocked?: boolean,
50
+ }
51
+
52
+ export type TokenType = {
53
+ accessToken: string;
54
+ refreshToken: string
55
+ };
@@ -0,0 +1,8 @@
1
+ import { AppError } from "../base/errors"
2
+
3
+ export function normalizeError(err: unknown) {
4
+ if (err instanceof AppError) {
5
+ return { statusCode: err.statusCode, message: err.message };
6
+ }
7
+ return { statusCode: 500, message: "Internal server error" };
8
+ };
@@ -0,0 +1,11 @@
1
+ {
2
+ "compilerOptions": {
3
+ "module": "commonjs",
4
+ "esModuleInterop": true,
5
+ "target": "es6",
6
+ "moduleResolution": "node",
7
+ "sourceMap": true,
8
+ "outDir": "dist"
9
+ },
10
+ "lib": ["es2015"]
11
+ }