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 ADDED
@@ -0,0 +1,8 @@
1
+ PORT=8000
2
+ MONGODB_URI=mongodb://localhost:27017/
3
+ CORS_ORIGIN=*
4
+ NODE_ENV=development
5
+
6
+ # Rate Limiting
7
+ RATE_LIMIT_WINDOW_MS=900000 # 15 minutes in milliseconds
8
+ RATE_LIMIT_MAX=100 # Maximum requests per windowMs
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
+ [![Node.js](https://img.shields.io/badge/Node.js-Production_Ready-339933?style=for-the-badge&logo=node.js)](https://nodejs.org/)
4
+ [![Express.js](https://img.shields.io/badge/Express.js-Backend-000000?style=for-the-badge&logo=express)](https://expressjs.com/)
5
+ [![License: ISC](https://img.shields.io/badge/License-ISC-blue.svg?style=for-the-badge)](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 };
@@ -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
+ });
@@ -0,0 +1,9 @@
1
+ import { Router } from 'express';
2
+ import { healthcheck, triggerError } from '#controllers/healthcheck.controller.js';
3
+
4
+ const router = Router();
5
+
6
+ router.route('/').get(healthcheck);
7
+ router.route('/error').get(triggerError);
8
+
9
+ export default router;
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,7 @@
1
+ const asyncHandler = (requestHandler) => {
2
+ return (req, res, next) => {
3
+ Promise.resolve(requestHandler(req, res, next)).catch((err) => next(err));
4
+ };
5
+ };
6
+
7
+ export { asyncHandler };
@@ -0,0 +1 @@
1
+ export const DB_NAME = "my_app_db";