create-express-mongoose-app 1.0.0 → 1.0.2

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 CHANGED
@@ -1,4 +1,4 @@
1
- # create-express-mongo-app
1
+ # create-express-mongoose-app
2
2
 
3
3
  A CLI tool to quickly bootstrap a production-grade Express.js + MongoDB/Mongoose project with TypeScript.
4
4
 
@@ -17,13 +17,13 @@ A CLI tool to quickly bootstrap a production-grade Express.js + MongoDB/Mongoose
17
17
  ## Installation
18
18
 
19
19
  ```bash
20
- npm install -g create-express-mongo-app
20
+ npm install -g create-express-mongoose-app
21
21
  ```
22
22
 
23
23
  Or use npx without installation:
24
24
 
25
25
  ```bash
26
- npx create-express-mongo-app
26
+ npx create-express-mongoose-app
27
27
  ```
28
28
 
29
29
  ## Usage
@@ -31,13 +31,13 @@ npx create-express-mongo-app
31
31
  Run the CLI:
32
32
 
33
33
  ```bash
34
- create-express-mongo-app
34
+ create-express-mongoose-app
35
35
  ```
36
36
 
37
37
  Or with npx:
38
38
 
39
39
  ```bash
40
- npx create-express-mongo-app
40
+ npx create-express-mongoose-app
41
41
  ```
42
42
 
43
43
  The CLI will ask for:
package/package.json CHANGED
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "name": "create-express-mongoose-app",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
+ "type": "module",
4
5
  "description": "CLI tool to create production-grade Express.js with Mongoose starter projects",
5
6
  "main": "src/cli.js",
6
7
  "bin": {
@@ -1,21 +0,0 @@
1
- # Server Configuration
2
- NODE_ENV=development
3
- PORT=3000
4
- API_PREFIX=/api
5
-
6
- # Database Configuration
7
- MONGODB_URI=mongodb://localhost:27017/express-mongo-starter
8
- DB_MIN_POOL_SIZE=5
9
- DB_MAX_POOL_SIZE=10
10
-
11
- # Logging Configuration
12
- LOG_LEVEL=info
13
- ENABLE_FILE_LOGGING=false
14
-
15
- # Security & Rate Limiting
16
- CORS_ORIGIN=http://localhost:3000
17
- RATE_LIMIT_WINDOW_MS=900000
18
- RATE_LIMIT_MAX_REQUESTS=100
19
-
20
- # Request Configuration
21
- REQUEST_BODY_LIMIT=10kb
package/my-test/README.md DELETED
@@ -1,137 +0,0 @@
1
- # Express MongoDB Starter Template
2
-
3
- A production-grade Express.js + MongoDB/Mongoose starter template with TypeScript, comprehensive error handling, Winston logging, Joi validation, and security best practices.
4
-
5
- ## Features
6
-
7
- - ✅ **TypeScript** - Strict mode enabled
8
- - ✅ **Error Handling** - Centralized error middleware with standardized responses
9
- - ✅ **Response Handler** - Universal ApiResponseHandler with error/success/info methods
10
- - ✅ **Logging** - Winston logger with file rotation
11
- - ✅ **Validation** - Joi schema validation
12
- - ✅ **Security** - Helmet, CORS, Rate Limiting
13
- - ✅ **Database** - MongoDB with connection retry logic and graceful shutdown
14
- - ✅ **Async Handler** - Eliminate try-catch boilerplate
15
- - ✅ **Health Checks** - /health and /ready endpoints
16
-
17
- ## Quick Start
18
-
19
- 1. Clone this repository
20
- 2. Install dependencies:
21
-
22
- ```bash
23
- npm install
24
- # or
25
- yarn install
26
- # or
27
- pnpm install
28
- ```
29
-
30
- 3. Copy `.env.example` to `.env` and configure:
31
-
32
- ```bash
33
- cp .env.example .env
34
- ```
35
-
36
- 4. Start development server:
37
-
38
- ```bash
39
- npm run dev
40
- ```
41
-
42
- 5. Build for production:
43
- ```bash
44
- npm run build
45
- npm start
46
- ```
47
-
48
- ## Project Structure
49
-
50
- ```
51
- src/
52
- ├── config/ # Configuration files
53
- │ ├── env.ts # Environment variables
54
- │ ├── database.ts # MongoDB connection
55
- │ └── winston-logger.ts # Logger setup
56
- ├── middleware/ # Express middleware
57
- │ ├── error.middleware.ts
58
- │ ├── auth.middleware.ts
59
- │ └── request-id.middleware.ts
60
- ├── modules/ # Feature modules
61
- │ └── example/ # Example module
62
- │ ├── example.controller.ts
63
- │ ├── example.service.ts
64
- │ ├── example.model.ts
65
- │ ├── example.route.ts
66
- │ └── example.validation.ts
67
- ├── utils/ # Utility functions
68
- │ ├── response.ts # Response handler
69
- │ ├── asyncHandler.ts
70
- │ ├── apiError.ts
71
- │ └── validationMiddleware.ts
72
- ├── app.ts # Express app setup
73
- ├── index.ts # Entry point
74
- └── generate-module.ts # Module generator CLI
75
- ```
76
-
77
- ## API Response Format
78
-
79
- All endpoints return standardized responses:
80
-
81
- ```typescript
82
- // Success Response
83
- {
84
- "status": "success",
85
- "data": {...},
86
- "message": "Retrieved successfully"
87
- }
88
-
89
- // Error Response
90
- {
91
- "status": "error",
92
- "message": "Error message",
93
- "errors": {...},
94
- "statusCode": 400
95
- }
96
-
97
- // Info Response
98
- {
99
- "status": "info",
100
- "message": "Info message",
101
- "data": {...}
102
- }
103
- ```
104
-
105
- ## Generating New Modules
106
-
107
- Generate a new module with all CRUD operations:
108
-
109
- ```bash
110
- npm run generate-module <module-name>
111
- ```
112
-
113
- This creates:
114
-
115
- - Controller
116
- - Service
117
- - Model
118
- - Routes
119
- - Validation schema
120
-
121
- ## Environment Variables
122
-
123
- See `.env.example` for all available configuration options.
124
-
125
- ## Error Handling
126
-
127
- Errors are automatically caught by the centralized error middleware and formatted consistently. Supports:
128
-
129
- - Mongoose validation errors
130
- - MongoDB duplicate key errors (11000)
131
- - JWT errors
132
- - Custom ApiError instances
133
- - Unhandled exceptions
134
-
135
- ## License
136
-
137
- MIT
@@ -1,38 +0,0 @@
1
- {
2
- "name": "express-mongo-starter",
3
- "version": "1.0.0",
4
- "description": "Production-grade Express.js with MongoDB/Mongoose starter template",
5
- "main": "dist/index.js",
6
- "scripts": {
7
- "dev": "ts-node src/index.ts",
8
- "build": "tsc",
9
- "start": "node dist/index.js",
10
- "generate-module": "ts-node src/generate-module.ts"
11
- },
12
- "dependencies": {
13
- "cors": "^2.8.5",
14
- "dotenv": "^16.3.1",
15
- "express": "^4.18.2",
16
- "express-async-errors": "^3.1.1",
17
- "express-rate-limit": "^7.1.5",
18
- "express-validator": "^7.0.0",
19
- "helmet": "^7.1.0",
20
- "http-status-codes": "^2.3.0",
21
- "joi": "^17.11.0",
22
- "mongoose": "^8.1.0",
23
- "morgan": "^1.10.0",
24
- "uuid": "^13.0.0",
25
- "winston": "^3.11.0"
26
- },
27
- "devDependencies": {
28
- "@types/cors": "^2.8.19",
29
- "@types/express": "^4.17.21",
30
- "@types/morgan": "^1.9.10",
31
- "@types/node": "^20.10.6",
32
- "ts-node": "^10.9.2",
33
- "typescript": "^5.3.3"
34
- },
35
- "engines": {
36
- "node": ">=16.0.0"
37
- }
38
- }
@@ -1,77 +0,0 @@
1
- import express, { Express, Request, Response } from "express";
2
- import helmet from "helmet";
3
- import cors from "cors";
4
- import rateLimit from "express-rate-limit";
5
- import morgan from "morgan";
6
- import "express-async-errors";
7
- import { config } from "./config/env";
8
- import { errorMiddleware } from "./middleware/error.middleware";
9
- import { requestIdMiddleware } from "./middleware/request-id.middleware";
10
- import { logger } from "./config/winston-logger";
11
- import { ApiResponseHandler } from "./utils/response";
12
-
13
- export const createApp = (): Express => {
14
- const app = express();
15
-
16
- // Security middleware
17
- app.use(helmet());
18
- app.use(
19
- cors({
20
- origin: config.corsOrigin,
21
- credentials: true,
22
- }),
23
- );
24
-
25
- // Request ID middleware
26
- app.use(requestIdMiddleware);
27
-
28
- // Body parser middleware
29
- app.use(express.json({ limit: config.requestBodyLimit }));
30
- app.use(
31
- express.urlencoded({ limit: config.requestBodyLimit, extended: true }),
32
- );
33
-
34
- // Rate limiting
35
- const limiter = rateLimit({
36
- windowMs: config.rateLimitWindowMs,
37
- max: config.rateLimitMaxRequests,
38
- message: "Too many requests from this IP, please try again later.",
39
- });
40
- app.use(limiter);
41
-
42
- // Logging middleware
43
- app.use(
44
- morgan("combined", {
45
- stream: {
46
- write: (message) => logger.info(message.trim()),
47
- },
48
- }),
49
- );
50
-
51
- // Health check endpoints
52
- app.get("/health", (req: Request, res: Response) => {
53
- ApiResponseHandler.success(res, { status: "healthy" }, "Server is running");
54
- });
55
-
56
- app.get("/ready", (req: Request, res: Response) => {
57
- ApiResponseHandler.success(res, { ready: true }, "Server is ready");
58
- });
59
-
60
- // API routes
61
- app.use(config.apiPrefix, (req: Request, res: Response) => {
62
- ApiResponseHandler.info(res, "API is running", {
63
- version: "1.0.0",
64
- apiPrefix: config.apiPrefix,
65
- });
66
- });
67
-
68
- // 404 handler
69
- app.use("*", (req: Request, res: Response) => {
70
- ApiResponseHandler.error(res, "Route not found", 404);
71
- });
72
-
73
- // Error middleware (must be last)
74
- app.use(errorMiddleware);
75
-
76
- return app;
77
- };
@@ -1,54 +0,0 @@
1
- import mongoose from "mongoose";
2
- import { config } from "./env";
3
- import { logger } from "./winston-logger";
4
-
5
- export const connectDatabase = async () => {
6
- try {
7
- const mongoOptions = {
8
- minPoolSize: 5,
9
- maxPoolSize: 10,
10
- };
11
-
12
- await mongoose.connect(config.mongodbUri, mongoOptions);
13
-
14
- logger.info("Database connected successfully");
15
-
16
- mongoose.connection.on("disconnected", () => {
17
- logger.warn("Database disconnected");
18
- });
19
-
20
- mongoose.connection.on("error", (error) => {
21
- logger.error("Database connection error:", error);
22
- });
23
-
24
- mongoose.connection.on("reconnected", () => {
25
- logger.info("Database reconnected");
26
- });
27
- } catch (error: any) {
28
- logger.error("Failed to connect to database:", error.message);
29
- // Retry logic
30
- const retryAttempts = 5;
31
- for (let i = 1; i <= retryAttempts; i++) {
32
- logger.info(`Retrying database connection (${i}/${retryAttempts})...`);
33
- await new Promise((resolve) => setTimeout(resolve, 5000));
34
- try {
35
- await mongoose.connect(config.mongodbUri);
36
- logger.info("Database connected successfully on retry");
37
- return;
38
- } catch (retryError: any) {
39
- logger.error(`Retry attempt ${i} failed:`, retryError.message);
40
- }
41
- }
42
- process.exit(1);
43
- }
44
- };
45
-
46
- export const disconnectDatabase = async () => {
47
- try {
48
- await mongoose.disconnect();
49
- logger.info("Database disconnected");
50
- } catch (error: any) {
51
- logger.error("Error disconnecting database:", error.message);
52
- process.exit(1);
53
- }
54
- };
@@ -1,42 +0,0 @@
1
- import dotenv from "dotenv";
2
-
3
- dotenv.config();
4
-
5
- export interface IConfig {
6
- port: number;
7
- nodeEnv: string;
8
- mongodbUri: string;
9
- apiPrefix: string;
10
- logLevel: string;
11
- corsOrigin: string;
12
- rateLimitWindowMs: number;
13
- rateLimitMaxRequests: number;
14
- requestBodyLimit: string;
15
- enableFileLogging: boolean;
16
- }
17
-
18
- export const config: IConfig = {
19
- port: parseInt(process.env.PORT || "3000"),
20
- nodeEnv: process.env.NODE_ENV || "development",
21
- mongodbUri:
22
- process.env.MONGODB_URI ||
23
- "mongodb://localhost:27017/express-mongo-starter",
24
- apiPrefix: process.env.API_PREFIX || "/api",
25
- logLevel: process.env.LOG_LEVEL || "info",
26
- corsOrigin: process.env.CORS_ORIGIN || "http://localhost:3000",
27
- rateLimitWindowMs: parseInt(process.env.RATE_LIMIT_WINDOW_MS || "900000"),
28
- rateLimitMaxRequests: parseInt(process.env.RATE_LIMIT_MAX_REQUESTS || "100"),
29
- requestBodyLimit: process.env.REQUEST_BODY_LIMIT || "10kb",
30
- enableFileLogging: process.env.ENABLE_FILE_LOGGING === "true",
31
- };
32
-
33
- // Validate required environment variables in production
34
- if (config.nodeEnv === "production") {
35
- const requiredVars = ["MONGODB_URI"];
36
- const missingVars = requiredVars.filter((v) => !process.env[v]);
37
- if (missingVars.length > 0) {
38
- throw new Error(
39
- `Missing required environment variables: ${missingVars.join(", ")}`,
40
- );
41
- }
42
- }
@@ -1,75 +0,0 @@
1
- import winston from "winston";
2
- import path from "path";
3
- import { config } from "./env";
4
-
5
- const logsDir = path.join(process.cwd(), "logs");
6
-
7
- const levels = {
8
- error: 0,
9
- warn: 1,
10
- info: 2,
11
- debug: 3,
12
- };
13
-
14
- const colors = {
15
- error: "red",
16
- warn: "yellow",
17
- info: "green",
18
- debug: "blue",
19
- };
20
-
21
- winston.addColors(colors);
22
-
23
- const format = winston.format.combine(
24
- winston.format.timestamp({ format: "YYYY-MM-DD HH:mm:ss" }),
25
- winston.format.printf(
26
- (info) =>
27
- `${info.timestamp} [${info.level}]: ${info.message}${info.error ? "\n" + info.error : ""}`,
28
- ),
29
- );
30
-
31
- const transports: winston.transport[] = [
32
- new winston.transports.Console({
33
- format: winston.format.combine(
34
- winston.format.colorize({ all: true }),
35
- format,
36
- ),
37
- }),
38
- ];
39
-
40
- if (config.nodeEnv === "production" || config.enableFileLogging) {
41
- transports.push(
42
- new winston.transports.File({
43
- filename: path.join(logsDir, "error.log"),
44
- level: "error",
45
- format,
46
- maxsize: 5242880,
47
- maxFiles: 5,
48
- }),
49
- new winston.transports.File({
50
- filename: path.join(logsDir, "combined.log"),
51
- format,
52
- maxsize: 5242880,
53
- maxFiles: 5,
54
- }),
55
- );
56
- }
57
-
58
- export const logger = winston.createLogger({
59
- level: config.logLevel || "info",
60
- levels,
61
- format,
62
- transports,
63
- exceptionHandlers: [
64
- new winston.transports.File({
65
- filename: path.join(logsDir, "exceptions.log"),
66
- }),
67
- ],
68
- rejectionHandlers: [
69
- new winston.transports.File({
70
- filename: path.join(logsDir, "rejections.log"),
71
- }),
72
- ],
73
- });
74
-
75
- export default logger;
@@ -1,3 +0,0 @@
1
- import { startServer } from "./server";
2
-
3
- startServer();
@@ -1,88 +0,0 @@
1
- import { Request, Response, NextFunction } from "express";
2
- import { ApiResponseHandler } from "../utils/response";
3
- import { ApiError } from "../utils/apiError";
4
- import { logger } from "../config/winston-logger";
5
- import { StatusCodes } from "http-status-codes";
6
-
7
- export const errorMiddleware = (
8
- err: any,
9
- req: Request,
10
- res: Response,
11
- next: NextFunction,
12
- ) => {
13
- const requestId = req.id || "unknown";
14
-
15
- // Handle Mongoose validation errors
16
- if (err.name === "ValidationError") {
17
- const errors: Record<string, any> = {};
18
- Object.keys(err.errors).forEach((key) => {
19
- errors[key] = err.errors[key].message;
20
- });
21
- logger.error(`[${requestId}] Validation Error:`, errors);
22
- return ApiResponseHandler.error(
23
- res,
24
- "Validation failed",
25
- StatusCodes.UNPROCESSABLE_ENTITY,
26
- errors,
27
- );
28
- }
29
-
30
- // Handle Mongoose cast errors
31
- if (err.name === "CastError") {
32
- logger.error(`[${requestId}] Invalid ID format`);
33
- return ApiResponseHandler.error(
34
- res,
35
- "Invalid ID format",
36
- StatusCodes.BAD_REQUEST,
37
- );
38
- }
39
-
40
- // Handle duplicate key errors
41
- if (err.code === 11000) {
42
- const field = Object.keys(err.keyPattern)[0];
43
- logger.error(`[${requestId}] Duplicate key error on field: ${field}`);
44
- return ApiResponseHandler.error(
45
- res,
46
- `${field} already exists`,
47
- StatusCodes.CONFLICT,
48
- );
49
- }
50
-
51
- // Handle JWT errors
52
- if (err.name === "JsonWebTokenError") {
53
- logger.error(`[${requestId}] JWT Error: ${err.message}`);
54
- return ApiResponseHandler.error(
55
- res,
56
- "Invalid token",
57
- StatusCodes.UNAUTHORIZED,
58
- );
59
- }
60
-
61
- if (err.name === "TokenExpiredError") {
62
- logger.error(`[${requestId}] Token Expired`);
63
- return ApiResponseHandler.error(
64
- res,
65
- "Token expired",
66
- StatusCodes.UNAUTHORIZED,
67
- );
68
- }
69
-
70
- // Handle custom ApiError
71
- if (err instanceof ApiError) {
72
- logger.error(`[${requestId}] ApiError: ${err.message}`, err.errors);
73
- return ApiResponseHandler.error(
74
- res,
75
- err.message,
76
- err.statusCode,
77
- err.errors,
78
- );
79
- }
80
-
81
- // Handle unexpected errors
82
- logger.error(`[${requestId}] Unexpected Error:`, err.message);
83
- return ApiResponseHandler.error(
84
- res,
85
- "Internal server error",
86
- StatusCodes.INTERNAL_SERVER_ERROR,
87
- );
88
- };
@@ -1,20 +0,0 @@
1
- import { Request, Response, NextFunction } from "express";
2
- import { v4 as uuidv4 } from "uuid";
3
-
4
- declare global {
5
- namespace Express {
6
- interface Request {
7
- id?: string;
8
- }
9
- }
10
- }
11
-
12
- export const requestIdMiddleware = (
13
- req: Request,
14
- res: Response,
15
- next: NextFunction,
16
- ) => {
17
- req.id = uuidv4();
18
- res.setHeader("X-Request-ID", req?.id!);
19
- next();
20
- };
@@ -1,45 +0,0 @@
1
- import { Request, Response } from "express";
2
- import { ExampleService } from "./example.service";
3
- import { ApiResponseHandler } from "../../utils/response";
4
- import { asyncHandler } from "../../utils/asyncHandler";
5
-
6
- const exampleService = new ExampleService();
7
-
8
- export const getAllExamples = asyncHandler(
9
- async (req: Request, res: Response) => {
10
- const examples = await exampleService.findAll();
11
- ApiResponseHandler.success(
12
- res,
13
- examples,
14
- "Examples retrieved successfully",
15
- );
16
- },
17
- );
18
-
19
- export const getExampleById = asyncHandler(
20
- async (req: Request, res: Response) => {
21
- const example = await exampleService.findById(req.params.id);
22
- ApiResponseHandler.success(res, example, "Example retrieved successfully");
23
- },
24
- );
25
-
26
- export const createExample = asyncHandler(
27
- async (req: Request, res: Response) => {
28
- const example = await exampleService.create(req.body);
29
- ApiResponseHandler.created(res, example, "Example created successfully");
30
- },
31
- );
32
-
33
- export const updateExample = asyncHandler(
34
- async (req: Request, res: Response) => {
35
- const example = await exampleService.update(req.params.id, req.body);
36
- ApiResponseHandler.success(res, example, "Example updated successfully");
37
- },
38
- );
39
-
40
- export const deleteExample = asyncHandler(
41
- async (req: Request, res: Response) => {
42
- await exampleService.delete(req.params.id);
43
- ApiResponseHandler.noContent(res);
44
- },
45
- );
@@ -1,31 +0,0 @@
1
- import mongoose, { Schema, Document } from "mongoose";
2
-
3
- export interface IExample extends Document {
4
- name: string;
5
- description?: string;
6
- isActive: boolean;
7
- createdAt: Date;
8
- updatedAt: Date;
9
- }
10
-
11
- const ExampleSchema = new Schema(
12
- {
13
- name: {
14
- type: String,
15
- required: [true, "Name is required"],
16
- trim: true,
17
- unique: true,
18
- },
19
- description: {
20
- type: String,
21
- trim: true,
22
- },
23
- isActive: {
24
- type: Boolean,
25
- default: true,
26
- },
27
- },
28
- { timestamps: true },
29
- );
30
-
31
- export default mongoose.model<IExample>("Example", ExampleSchema);