create-express-kickstart 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/.env.example +8 -0
- package/LICENSE +21 -0
- package/README.md +82 -0
- package/bin/cli.js +176 -0
- package/package.json +26 -0
- package/src/app.js +75 -0
- package/src/controllers/healthcheck.controller.js +17 -0
- package/src/db/index.js +14 -0
- package/src/middlewares/errorHandler.middleware.js +37 -0
- package/src/models/example-model.js +18 -0
- package/src/routes/healthcheck.routes.js +9 -0
- package/src/server.js +27 -0
- package/src/utils/ApiError.js +23 -0
- package/src/utils/ApiResponse.js +10 -0
- package/src/utils/asyncHandler.js +7 -0
- package/src/utils/constants.js +1 -0
package/.env.example
ADDED
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Aasif Ashraf
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# create-express-kickstart
|
|
2
|
+
|
|
3
|
+
[](https://nodejs.org/)
|
|
4
|
+
[](https://expressjs.com/)
|
|
5
|
+
[](https://opensource.org/licenses/ISC)
|
|
6
|
+
|
|
7
|
+
A powerful CLI tool to instantly scaffold a production-ready, feature-rich backend Node.js template specifically tailored for Express API applications. It adheres to modern best practices, providing standard structures for error handling, CORS setups, routing, and middlewares right out of the box.
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## 🚀 Getting Started
|
|
12
|
+
|
|
13
|
+
You do not need to clone this repository, install dependencies manually, or write an initial configuration yourself. Use `npx` (which comes with npm 5.2+) to instantly generate your backend boilerplate!
|
|
14
|
+
|
|
15
|
+
### 1. Initialize a New Project
|
|
16
|
+
|
|
17
|
+
Run the following command anywhere in your terminal:
|
|
18
|
+
```bash
|
|
19
|
+
npx create-express-kickstart <your-project-name>
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
**Example:**
|
|
23
|
+
```bash
|
|
24
|
+
npx create-express-kickstart my-awesome-api
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
### 2. What happens under the hood?
|
|
28
|
+
1. **Scaffolding:** It instantly generates your API boilerplate with built-in `errorHandler`, `ApiResponse`, and `asyncHandler` classes/utilities.
|
|
29
|
+
2. **Setup:** It automatically configures `.env`, path resolutions, and modern ES setups inside `package.json`.
|
|
30
|
+
3. **Latest Dependencies:** It automatically runs `npm install` and fetches the absolute **latest** stable versions of `express`, `cors`, `helmet`, `mongoose`, `dotenv` and others so you're never starting with outdated software.
|
|
31
|
+
|
|
32
|
+
### 3. Run Your Application
|
|
33
|
+
|
|
34
|
+
Navigate into your newly created folder and fire up the development server!
|
|
35
|
+
```bash
|
|
36
|
+
cd my-awesome-api
|
|
37
|
+
npm run dev
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## 🌟 Features
|
|
43
|
+
|
|
44
|
+
- **Modern JavaScript**: ES6 Modules (`import`/`export`) enabled by default.
|
|
45
|
+
- **Robust Error Handling**: Centralized error management using custom `ApiError` and `errorHandler` middleware.
|
|
46
|
+
- **Standardized Responses**: Consistent API responses using the `ApiResponse` utility class.
|
|
47
|
+
- **No Try-Catch Hell**: `asyncHandler` wrapper to effortlessly catch unhandled promise rejections.
|
|
48
|
+
- **Security First**: Pre-configured with `helmet`, `cors`, and `express-rate-limit`.
|
|
49
|
+
- **Database Ready**: Built-in support and structural setup for MongoDB with `mongoose`.
|
|
50
|
+
- **Developer Experience**: Hot reloading with `nodemon` and request logging with `pino`.
|
|
51
|
+
- **Path Aliasing Native**: Pre-configured subpath imports (`#utils/...`).
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
## 🛠️ Core Utilities Built-In
|
|
56
|
+
|
|
57
|
+
This template shines in its standardized utilities available out of the box for you:
|
|
58
|
+
|
|
59
|
+
### `ApiResponse`
|
|
60
|
+
Guarantees a standard format for all successful payload JSON responses.
|
|
61
|
+
```javascript
|
|
62
|
+
import { ApiResponse } from "#utils/ApiResponse.js";
|
|
63
|
+
|
|
64
|
+
const getUserInfo = asyncHandler(async (req, res) => {
|
|
65
|
+
const data = { id: 1, name: "Alice" };
|
|
66
|
+
return res.status(200).json(new ApiResponse(200, data, "User retrieved successfully"));
|
|
67
|
+
});
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### `ApiError` & `errorHandler`
|
|
71
|
+
Throw operational errors anywhere, and the global `errorHandler` will format them predictably for the client.
|
|
72
|
+
```javascript
|
|
73
|
+
import { ApiError } from "#utils/ApiError.js";
|
|
74
|
+
|
|
75
|
+
const restrictedRoute = asyncHandler(async (req, res) => {
|
|
76
|
+
// Automatically caught by the async handler and forwarded to the error handler
|
|
77
|
+
throw new ApiError(403, "You do not have permission to view this content.");
|
|
78
|
+
});
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### `asyncHandler`
|
|
82
|
+
A wrapper for your async route handlers that eliminates the need for repetitive `try-catch` blocks.
|
package/bin/cli.js
ADDED
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import fs from 'fs';
|
|
4
|
+
import path from 'path';
|
|
5
|
+
import { execSync } from 'child_process';
|
|
6
|
+
import { fileURLToPath } from 'url';
|
|
7
|
+
import readline from 'readline';
|
|
8
|
+
|
|
9
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
10
|
+
const __dirname = path.dirname(__filename);
|
|
11
|
+
|
|
12
|
+
const rl = readline.createInterface({
|
|
13
|
+
input: process.stdin,
|
|
14
|
+
output: process.stdout
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
const question = (query) => new Promise((resolve) => rl.question(query, resolve));
|
|
18
|
+
|
|
19
|
+
async function init() {
|
|
20
|
+
const projectNameArg = process.argv[2];
|
|
21
|
+
|
|
22
|
+
let projectName = projectNameArg;
|
|
23
|
+
if (!projectName) {
|
|
24
|
+
projectName = await question('\n👉 Project name (e.g. my-awesome-api): ');
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (!projectName) {
|
|
28
|
+
console.error('\n❌ Error: Project name is required.');
|
|
29
|
+
process.exit(1);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const currentPath = process.cwd();
|
|
33
|
+
const projectPath = path.join(currentPath, projectName);
|
|
34
|
+
|
|
35
|
+
if (fs.existsSync(projectPath)) {
|
|
36
|
+
console.error(`\n❌ Error: Folder ${projectName} already exists. Please choose a different name.\n`);
|
|
37
|
+
process.exit(1);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const description = await question('👉 Project description: ');
|
|
41
|
+
const author = await question('👉 Author name: ');
|
|
42
|
+
|
|
43
|
+
console.log('\n--- 📦 Select Dependencies ---');
|
|
44
|
+
console.log('Press Enter for Yes (Y), type "n" for No.\n');
|
|
45
|
+
|
|
46
|
+
const deps = {
|
|
47
|
+
express: true, // Always required
|
|
48
|
+
mongoose: (await question('Include Mongoose (MongoDB)? [Y/n] ')).toLowerCase() !== 'n',
|
|
49
|
+
cors: (await question('Include CORS? [Y/n] ')).toLowerCase() !== 'n',
|
|
50
|
+
helmet: (await question('Include Helmet (Security headers)? [Y/n] ')).toLowerCase() !== 'n',
|
|
51
|
+
'cookie-parser': (await question('Include cookie-parser? [Y/n] ')).toLowerCase() !== 'n',
|
|
52
|
+
'pino-http': (await question('Include Pino (HTTP Logger)? [Y/n] ')).toLowerCase() !== 'n',
|
|
53
|
+
'express-rate-limit': (await question('Include Rate Limiting? [Y/n] ')).toLowerCase() !== 'n',
|
|
54
|
+
dotenv: (await question('Include dotenv (Environment variables)? [Y/n] ')).toLowerCase() !== 'n',
|
|
55
|
+
prettier: (await question('Include Prettier (Code formatter)? [Y/n] ')).toLowerCase() !== 'n'
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
let installPinoPretty = false;
|
|
59
|
+
if (deps['pino-http']) {
|
|
60
|
+
installPinoPretty = (await question('Include pino-pretty for clean development logs? [Y/n] ')).toLowerCase() !== 'n';
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
rl.close();
|
|
64
|
+
|
|
65
|
+
console.log(`\n🚀 Creating a new Node.js Express API in ${projectPath}...`);
|
|
66
|
+
fs.mkdirSync(projectPath, { recursive: true });
|
|
67
|
+
|
|
68
|
+
function copyRecursiveSync(src, dest) {
|
|
69
|
+
const exists = fs.existsSync(src);
|
|
70
|
+
const stats = exists && fs.statSync(src);
|
|
71
|
+
const isDirectory = exists && stats.isDirectory();
|
|
72
|
+
if (isDirectory) {
|
|
73
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
74
|
+
fs.readdirSync(src).forEach((childItemName) => {
|
|
75
|
+
copyRecursiveSync(path.join(src, childItemName), path.join(dest, childItemName));
|
|
76
|
+
});
|
|
77
|
+
} else {
|
|
78
|
+
fs.copyFileSync(src, dest);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// 1. Copy src directory
|
|
83
|
+
const sourceDir = path.join(__dirname, '..', 'src');
|
|
84
|
+
const targetSrcDir = path.join(projectPath, 'src');
|
|
85
|
+
|
|
86
|
+
if (!fs.existsSync(sourceDir)) {
|
|
87
|
+
console.error('\n❌ Error: Could not find "src" directory in the template generator.');
|
|
88
|
+
process.exit(1);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
console.log(`📂 Bootstrapping application structure (errorHandler, ApiResponse, async handlers)...`);
|
|
92
|
+
copyRecursiveSync(sourceDir, targetSrcDir);
|
|
93
|
+
|
|
94
|
+
// 2. Copy .env.example
|
|
95
|
+
console.log(`🔧 Generating environment files...`);
|
|
96
|
+
const envExamplePath = path.join(__dirname, '..', '.env.example');
|
|
97
|
+
if (fs.existsSync(envExamplePath)) {
|
|
98
|
+
fs.copyFileSync(envExamplePath, path.join(projectPath, '.env.example'));
|
|
99
|
+
fs.copyFileSync(envExamplePath, path.join(projectPath, '.env'));
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// 3. Create package.json
|
|
103
|
+
console.log(`📦 Setting up package.json...`);
|
|
104
|
+
const packageJsonTemplate = {
|
|
105
|
+
name: projectName,
|
|
106
|
+
version: "1.0.0",
|
|
107
|
+
description: description || "A production-ready Node.js Express API",
|
|
108
|
+
main: "src/server.js",
|
|
109
|
+
type: "module",
|
|
110
|
+
scripts: {
|
|
111
|
+
"start": "node src/server.js",
|
|
112
|
+
"dev": "nodemon src/server.js"
|
|
113
|
+
},
|
|
114
|
+
imports: {
|
|
115
|
+
"#*": "./src/*"
|
|
116
|
+
},
|
|
117
|
+
keywords: ["express", "node", "api"],
|
|
118
|
+
author: author || "",
|
|
119
|
+
license: "ISC"
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
if (deps.prettier) {
|
|
123
|
+
packageJsonTemplate.scripts.format = "prettier --write \"src/**/*.{js,json}\"";
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Remove the readline dependency from the generated boilerplate if mistakenly mixed
|
|
127
|
+
fs.writeFileSync(
|
|
128
|
+
path.join(projectPath, 'package.json'),
|
|
129
|
+
JSON.stringify(packageJsonTemplate, null, 2)
|
|
130
|
+
);
|
|
131
|
+
|
|
132
|
+
// 4. Install Dependencies
|
|
133
|
+
const dependenciesToInstall = Object.keys(deps).filter(dep => deps[dep] && dep !== 'prettier');
|
|
134
|
+
if (deps['pino-http']) {
|
|
135
|
+
dependenciesToInstall.push('pino');
|
|
136
|
+
}
|
|
137
|
+
const depString = dependenciesToInstall.join(' ');
|
|
138
|
+
|
|
139
|
+
const devDependenciesToInstall = ['nodemon'];
|
|
140
|
+
if (deps.prettier) devDependenciesToInstall.push('prettier');
|
|
141
|
+
if (installPinoPretty) devDependenciesToInstall.push('pino-pretty');
|
|
142
|
+
const devDepString = devDependenciesToInstall.join(' ');
|
|
143
|
+
|
|
144
|
+
console.log(`\n⏳ Installing selected core dependencies (${dependenciesToInstall.join(', ')}). This might take a minute...`);
|
|
145
|
+
try {
|
|
146
|
+
if (depString) {
|
|
147
|
+
execSync(`npm install ${depString}`, {
|
|
148
|
+
cwd: projectPath,
|
|
149
|
+
stdio: 'inherit'
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
console.log(`\n⏳ Installing latest dev dependencies (${devDepString})...`);
|
|
154
|
+
execSync(`npm install ${devDepString} --save-dev`, {
|
|
155
|
+
cwd: projectPath,
|
|
156
|
+
stdio: 'inherit'
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
console.log(`\n✅ Success! Created "${projectName}" at ${projectPath}`);
|
|
160
|
+
console.log('\nInside that directory, you can run several commands:');
|
|
161
|
+
console.log('\n npm run dev');
|
|
162
|
+
console.log(' Starts the development server on localhost.');
|
|
163
|
+
console.log('\n npm start');
|
|
164
|
+
console.log(' Starts the production server.');
|
|
165
|
+
console.log('\nWe suggest that you begin by typing:');
|
|
166
|
+
console.log(`\n cd ${projectName}`);
|
|
167
|
+
console.log(' npm run dev\n');
|
|
168
|
+
} catch (err) {
|
|
169
|
+
console.error('\n❌ Failed to install dependencies. You may need to run npm install manually inside the folder.', err);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
init().catch(err => {
|
|
174
|
+
console.error('\n❌ Unexpected error occurred:', err);
|
|
175
|
+
process.exit(1);
|
|
176
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "create-express-kickstart",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Production-ready CLI starter for Express APIs",
|
|
5
|
+
"main": "bin/cli.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"create-express-kickstart": "./bin/cli.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
11
|
+
},
|
|
12
|
+
"keywords": [
|
|
13
|
+
"cli",
|
|
14
|
+
"create",
|
|
15
|
+
"express",
|
|
16
|
+
"nodejs",
|
|
17
|
+
"starter",
|
|
18
|
+
"template",
|
|
19
|
+
"boilerplate"
|
|
20
|
+
],
|
|
21
|
+
"author": "Your Name",
|
|
22
|
+
"license": "ISC",
|
|
23
|
+
"type": "module",
|
|
24
|
+
"dependencies": {
|
|
25
|
+
}
|
|
26
|
+
}
|
package/src/app.js
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import express from "express";
|
|
2
|
+
import cors from "cors";
|
|
3
|
+
import cookieParser from "cookie-parser";
|
|
4
|
+
import helmet from "helmet";
|
|
5
|
+
import pinoHttp from "pino-http";
|
|
6
|
+
import rateLimit from "express-rate-limit";
|
|
7
|
+
import { errorHandler } from "#middlewares/errorHandler.middleware.js";
|
|
8
|
+
|
|
9
|
+
// Import routers
|
|
10
|
+
import healthcheckRouter from "#routes/healthcheck.routes.js";
|
|
11
|
+
|
|
12
|
+
const app = express();
|
|
13
|
+
|
|
14
|
+
// Security HTTP headers
|
|
15
|
+
app.use(helmet());
|
|
16
|
+
|
|
17
|
+
// Rate Limiting
|
|
18
|
+
const limiter = rateLimit({
|
|
19
|
+
windowMs: process.env.RATE_LIMIT_WINDOW_MS || 15 * 60 * 1000, // Default 15 minutes
|
|
20
|
+
limit: process.env.RATE_LIMIT_MAX || 100, // Limit each IP to 100 requests per `window` (here, per 15 minutes)
|
|
21
|
+
standardHeaders: 'draft-7', // draft-6: `RateLimit-*` headers; draft-7: combined `RateLimit` header
|
|
22
|
+
legacyHeaders: false, // Disable the `X-RateLimit-*` headers
|
|
23
|
+
message: "Too many requests from this IP, please try again later"
|
|
24
|
+
});
|
|
25
|
+
app.use("/api", limiter); // Apply rate limiting to all API routes
|
|
26
|
+
|
|
27
|
+
// Logging
|
|
28
|
+
app.use(pinoHttp({
|
|
29
|
+
customLogLevel: function (req, res, err) {
|
|
30
|
+
if (res.statusCode >= 400 && res.statusCode < 500) {
|
|
31
|
+
return 'warn'
|
|
32
|
+
} else if (res.statusCode >= 500 || err) {
|
|
33
|
+
return 'error'
|
|
34
|
+
} else if (res.statusCode >= 300 && res.statusCode < 400) {
|
|
35
|
+
return 'silent'
|
|
36
|
+
}
|
|
37
|
+
return 'info'
|
|
38
|
+
},
|
|
39
|
+
// Dynamically require pino-pretty if in dev and it exists, else undefined
|
|
40
|
+
transport: process.env.NODE_ENV === "development" ? (function() {
|
|
41
|
+
try {
|
|
42
|
+
import("pino-pretty");
|
|
43
|
+
return {
|
|
44
|
+
target: 'pino-pretty',
|
|
45
|
+
options: { colorize: true }
|
|
46
|
+
};
|
|
47
|
+
} catch (e) {
|
|
48
|
+
return undefined;
|
|
49
|
+
}
|
|
50
|
+
})() : undefined
|
|
51
|
+
}));
|
|
52
|
+
|
|
53
|
+
// CORS setup
|
|
54
|
+
app.use(
|
|
55
|
+
cors({
|
|
56
|
+
origin: process.env.CORS_ORIGIN || "*", // Fallback to allowing everything
|
|
57
|
+
credentials: true, // Allow cookies with requests
|
|
58
|
+
})
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
// Payload sizes and forms
|
|
62
|
+
app.use(express.json({ limit: "16kb" }));
|
|
63
|
+
app.use(express.urlencoded({ extended: true, limit: "16kb" }));
|
|
64
|
+
app.use(express.static("public"));
|
|
65
|
+
app.use(cookieParser());
|
|
66
|
+
|
|
67
|
+
// -------- API ROUTES ---------
|
|
68
|
+
// Mount routers
|
|
69
|
+
app.use("/api/v1/healthcheck", healthcheckRouter);
|
|
70
|
+
|
|
71
|
+
// Global Error Handler
|
|
72
|
+
// Always add this as the very last middleware
|
|
73
|
+
app.use(errorHandler);
|
|
74
|
+
|
|
75
|
+
export { app };
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { ApiError } from "#utils/ApiError.js";
|
|
2
|
+
import { ApiResponse } from "#utils/ApiResponse.js";
|
|
3
|
+
import { asyncHandler } from "#utils/asyncHandler.js";
|
|
4
|
+
|
|
5
|
+
const healthcheck = asyncHandler(async (req, res) => {
|
|
6
|
+
// Basic health check
|
|
7
|
+
return res
|
|
8
|
+
.status(200)
|
|
9
|
+
.json(new ApiResponse(200, { status: "OK", timestamp: Date.now() }, "App is running smoothly"));
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
const triggerError = asyncHandler(async (req, res) => {
|
|
13
|
+
// Dummy route to test the global error handler
|
|
14
|
+
throw new ApiError(400, "This is a custom error thrown for testing purposes.");
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
export { healthcheck, triggerError };
|
package/src/db/index.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import mongoose from "mongoose";
|
|
2
|
+
import { DB_NAME } from "#utils/constants.js";
|
|
3
|
+
|
|
4
|
+
const connectDB = async () => {
|
|
5
|
+
try {
|
|
6
|
+
const connectionInstance = await mongoose.connect(`${process.env.MONGODB_URI}/${DB_NAME}`);
|
|
7
|
+
console.log(`\n MongoDB connected !! DB HOST: ${connectionInstance.connection.host}`);
|
|
8
|
+
} catch (error) {
|
|
9
|
+
console.error("MONGODB connection FAILED ", error);
|
|
10
|
+
process.exit(1);
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export default connectDB;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { ApiError } from '#utils/ApiError.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Global Error Handler Middleware
|
|
5
|
+
* @param {Error} err
|
|
6
|
+
* @param {Request} req
|
|
7
|
+
* @param {Response} res
|
|
8
|
+
* @param {NextFunction} next
|
|
9
|
+
*/
|
|
10
|
+
const errorHandler = (err, req, res, next) => {
|
|
11
|
+
let error = err;
|
|
12
|
+
|
|
13
|
+
// If the error is not an instance of ApiError, transform it into one
|
|
14
|
+
if (!(error instanceof ApiError)) {
|
|
15
|
+
const statusCode = error.statusCode ? error.statusCode : 500;
|
|
16
|
+
const message = error.message || "Internal Server Error";
|
|
17
|
+
|
|
18
|
+
error = new ApiError(
|
|
19
|
+
statusCode,
|
|
20
|
+
message,
|
|
21
|
+
error?.errors || [], // Pass down any validation errors
|
|
22
|
+
err.stack // Keep the original stack trace
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Now format the consistent response
|
|
27
|
+
const response = {
|
|
28
|
+
...error,
|
|
29
|
+
message: error.message,
|
|
30
|
+
...(process.env.NODE_ENV === 'development' ? { stack: error.stack } : {})
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
// Send the JSON response
|
|
34
|
+
return res.status(error.statusCode).json(response);
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export { errorHandler };
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
//example-model.js
|
|
2
|
+
import mongoose from "mongoose";
|
|
3
|
+
|
|
4
|
+
const exampleSchema = new mongoose.Schema({
|
|
5
|
+
name: {
|
|
6
|
+
type: String,
|
|
7
|
+
required: [true, "Name is required"],
|
|
8
|
+
},
|
|
9
|
+
age: {
|
|
10
|
+
type: Number,
|
|
11
|
+
required: [true, "Age is required"],
|
|
12
|
+
},
|
|
13
|
+
email: {
|
|
14
|
+
type: String,
|
|
15
|
+
required: [true, "Email is required"],
|
|
16
|
+
unique: true,
|
|
17
|
+
},
|
|
18
|
+
});
|
package/src/server.js
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import dotenv from "dotenv";
|
|
2
|
+
import { app } from "#app.js";
|
|
3
|
+
|
|
4
|
+
// Load environment variables from .env file
|
|
5
|
+
dotenv.config({
|
|
6
|
+
path: './.env'
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
import connectDB from "#db/index.js";
|
|
10
|
+
|
|
11
|
+
const PORT = process.env.PORT || 8000;
|
|
12
|
+
|
|
13
|
+
connectDB()
|
|
14
|
+
.then(() => {
|
|
15
|
+
app.listen(PORT, () => {
|
|
16
|
+
console.log(`Server is running at port : ${PORT}`);
|
|
17
|
+
});
|
|
18
|
+
})
|
|
19
|
+
.catch((err) => {
|
|
20
|
+
console.log("MONGO db connection failed !!! ", err);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
process.on("unhandledRejection", (err) => {
|
|
24
|
+
console.log("UNHANDLED REJECTION! Shutting down...");
|
|
25
|
+
console.log(err.name, err.message);
|
|
26
|
+
process.exit(1);
|
|
27
|
+
});
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
class ApiError extends Error {
|
|
2
|
+
constructor(
|
|
3
|
+
statusCode,
|
|
4
|
+
message = "Something went wrong",
|
|
5
|
+
errors = [],
|
|
6
|
+
stack = ""
|
|
7
|
+
) {
|
|
8
|
+
super(message);
|
|
9
|
+
this.statusCode = statusCode;
|
|
10
|
+
this.data = null;
|
|
11
|
+
this.message = message;
|
|
12
|
+
this.success = false;
|
|
13
|
+
this.errors = errors;
|
|
14
|
+
|
|
15
|
+
if (stack) {
|
|
16
|
+
this.stack = stack;
|
|
17
|
+
} else {
|
|
18
|
+
Error.captureStackTrace(this, this.constructor);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export { ApiError }
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
class ApiResponse {
|
|
2
|
+
constructor(statusCode, data, message = "Success") {
|
|
3
|
+
this.statusCode = statusCode;
|
|
4
|
+
this.data = data;
|
|
5
|
+
this.message = message;
|
|
6
|
+
this.success = statusCode < 400; // Success is true if status code is not an error level
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export { ApiResponse }
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const DB_NAME = "my_app_db";
|