xpresso-cli 1.3.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/LICENSE ADDED
@@ -0,0 +1,15 @@
1
+ ISC License
2
+
3
+ Copyright (c) 2026, Krish Dhiman
4
+
5
+ Permission to use, copy, modify, and/or distribute this software for any
6
+ purpose with or without fee is hereby granted, provided that the above
7
+ copyright notice and this permission notice appear in all copies.
8
+
9
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10
+ WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11
+ MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12
+ ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14
+ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15
+ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
package/bin/cli.js ADDED
@@ -0,0 +1,26 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { program } from 'commander';
4
+ import { initCommand } from './commands/init.js';
5
+ import { createServiceCommand } from './commands/createService.js';
6
+
7
+ // ==========================================
8
+ // CLI Application
9
+ // ==========================================
10
+
11
+ program
12
+ .name('create-node-app')
13
+ .description('Scaffold a new Node.js application');
14
+
15
+ program
16
+ .command('init [project-directory]', { isDefault: true })
17
+ .description('Initialize a new Node.js project (Default command)')
18
+ .action(initCommand);
19
+
20
+ program
21
+ .command('create-service <serviceName>')
22
+ .alias('create')
23
+ .description('Scaffold a new service (model, controller, routes)')
24
+ .action(createServiceCommand);
25
+
26
+ program.parse(process.argv);
@@ -0,0 +1,147 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import chalk from 'chalk';
4
+ import ora from 'ora';
5
+
6
+ export async function createServiceCommand(serviceName, targetDir = process.cwd()) {
7
+ if (!serviceName) {
8
+ console.error(chalk.red('Please provide a service name. Example: create-node-app create-service auth'));
9
+ process.exit(1);
10
+ }
11
+
12
+ const currentDir = targetDir;
13
+ const packageJsonPath = path.join(currentDir, 'package.json');
14
+
15
+ // Default to no database if not found
16
+ let dbType = 'none';
17
+
18
+ if (fs.existsSync(packageJsonPath)) {
19
+ try {
20
+ const pkg = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
21
+ const deps = pkg.dependencies || {};
22
+ if (deps.mongoose) {
23
+ dbType = 'mongoose';
24
+ } else if (deps.sequelize) {
25
+ dbType = 'sequelize';
26
+ }
27
+ } catch (error) {
28
+ console.warn(chalk.yellow('Could not parse package.json. Defaulting to standard model generation.'));
29
+ }
30
+ } else {
31
+ console.warn(chalk.yellow('No package.json found in current directory. Generating service without database dependencies.'));
32
+ }
33
+
34
+ const serviceDir = path.join(currentDir, 'services', serviceName);
35
+ const controllerDir = path.join(serviceDir, 'controller');
36
+ const modelDir = path.join(serviceDir, 'model');
37
+ const routesDir = path.join(serviceDir, 'routes');
38
+
39
+ const spinner = ora(`Generating ${serviceName} service...`).start();
40
+
41
+ try {
42
+ // Create directories
43
+ fs.mkdirSync(controllerDir, { recursive: true });
44
+ fs.mkdirSync(modelDir, { recursive: true });
45
+ fs.mkdirSync(routesDir, { recursive: true });
46
+
47
+ // Generate Controller
48
+ const controllerContent = `export default {
49
+ // Add your controller methods here
50
+ // exampleMethod: async (req, res, next) => { ... }
51
+ }
52
+ `;
53
+ fs.writeFileSync(path.join(controllerDir, `${serviceName}.controller.js`), controllerContent);
54
+
55
+ // Generate Routes
56
+ const routesContent = `import express from 'express'
57
+ import ${serviceName}Controller from '../controller/${serviceName}.controller.js'
58
+
59
+ const router = express.Router({ caseSensitive: true })
60
+
61
+ // Add your routes here
62
+ // router.get('/', ${serviceName}Controller.exampleMethod)
63
+
64
+ export default router
65
+ `;
66
+ fs.writeFileSync(path.join(routesDir, `${serviceName}.routes.js`), routesContent);
67
+
68
+ // Generate Model
69
+ let modelContent = '';
70
+ if (dbType === 'sequelize') {
71
+ modelContent = `import { DataTypes } from 'sequelize'
72
+ import sequelize from '../../../config/db.config.js'
73
+
74
+ const ${serviceName.charAt(0).toUpperCase() + serviceName.slice(1)} = sequelize.define('${serviceName}', {
75
+ name: {
76
+ type: DataTypes.STRING,
77
+ allowNull: false
78
+ }
79
+ }, {
80
+ timestamps: true
81
+ })
82
+
83
+ export default ${serviceName.charAt(0).toUpperCase() + serviceName.slice(1)}
84
+ `;
85
+ } else {
86
+ // Default to mongoose structure
87
+ modelContent = `import mongoose from "mongoose";
88
+
89
+ const ${serviceName}Schema = new mongoose.Schema({
90
+
91
+ })
92
+
93
+ export default mongoose.model('${serviceName}', ${serviceName}Schema);
94
+ `;
95
+ }
96
+
97
+ fs.writeFileSync(path.join(modelDir, `${serviceName}.model.js`), modelContent);
98
+
99
+ // Try to update app.js automatically
100
+ const appJsPath = path.join(currentDir, 'app.js');
101
+ if (fs.existsSync(appJsPath)) {
102
+ let appJsContent = fs.readFileSync(appJsPath, 'utf8');
103
+
104
+ const importStatement = `import ${serviceName}Routes from './services/${serviceName}/routes/${serviceName}.routes.js'`;
105
+ const useStatement = `app.use('/api/${serviceName}', ${serviceName}Routes)`;
106
+
107
+ // Inject import statement after the last import
108
+ if (!appJsContent.includes(importStatement)) {
109
+ const lastImportIndex = appJsContent.lastIndexOf('import ');
110
+ if (lastImportIndex !== -1) {
111
+ const endOfLastImport = appJsContent.indexOf('\n', lastImportIndex);
112
+ appJsContent = appJsContent.slice(0, endOfLastImport + 1) + importStatement + '\n' + appJsContent.slice(endOfLastImport + 1);
113
+ } else {
114
+ appJsContent = importStatement + '\n' + appJsContent;
115
+ }
116
+ }
117
+
118
+ // Inject app.use statement before app.get('/', or before export default
119
+ if (!appJsContent.includes(useStatement)) {
120
+ const appGetIndex = appJsContent.indexOf("app.get('/'");
121
+ if (appGetIndex !== -1) {
122
+ appJsContent = appJsContent.slice(0, appGetIndex) + useStatement + '\n\n' + appJsContent.slice(appGetIndex);
123
+ } else {
124
+ const exportIndex = appJsContent.lastIndexOf('export default app');
125
+ if (exportIndex !== -1) {
126
+ appJsContent = appJsContent.slice(0, exportIndex) + useStatement + '\n\n' + appJsContent.slice(exportIndex);
127
+ } else {
128
+ appJsContent += '\n' + useStatement + '\n';
129
+ }
130
+ }
131
+ }
132
+
133
+ fs.writeFileSync(appJsPath, appJsContent);
134
+ spinner.succeed(`Successfully created ${chalk.green(serviceName)} service and updated app.js`);
135
+ } else {
136
+ spinner.succeed(`Successfully created ${chalk.green(serviceName)} service at ./services/${serviceName}`);
137
+ console.log(`\nDon't forget to register your routes in ${chalk.cyan('app.js')}!`);
138
+ console.log(` import ${serviceName}Routes from './services/${serviceName}/routes/${serviceName}.routes.js'`);
139
+ console.log(` app.use('/api/${serviceName}', ${serviceName}Routes)\n`);
140
+ }
141
+
142
+ } catch (err) {
143
+ spinner.fail(`Failed to create ${serviceName} service.`);
144
+ console.error(err);
145
+ process.exit(1);
146
+ }
147
+ }
@@ -0,0 +1,33 @@
1
+ import path from 'path';
2
+ import chalk from 'chalk';
3
+ import { fileURLToPath } from 'url';
4
+ import { promptUser, copyTemplateFiles, installDependencies, showSuccessMessage } from '../utils/helpers.js';
5
+ import { configureDatabase } from '../utils/database.js';
6
+ import { createServiceCommand } from './createService.js';
7
+
8
+ const __filename = fileURLToPath(import.meta.url);
9
+ const __dirname = path.dirname(__filename);
10
+
11
+ export async function initCommand(projectDirectory) {
12
+ // 1. Prompt User
13
+ const { targetDir, packageManager, database } = await promptUser(projectDirectory);
14
+ const targetPath = path.resolve(process.cwd(), targetDir);
15
+ const templateDir = path.join(__dirname, '..', '..', 'template');
16
+
17
+ console.log(`\nCreating a new Node.js app in ${chalk.green(targetPath)}\n`);
18
+
19
+ // 2. Copy Template Files
20
+ copyTemplateFiles(templateDir, targetPath);
21
+
22
+ // 3. Configure Database
23
+ configureDatabase(targetPath, database);
24
+
25
+ // 4. Dynamically generate the 'auth' service so it uses the correct DB model
26
+ await createServiceCommand('auth', targetPath);
27
+
28
+ // 5. Install Dependencies
29
+ installDependencies(targetPath, packageManager);
30
+
31
+ // 6. Success Message
32
+ showSuccessMessage(targetDir, targetPath, packageManager);
33
+ }
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { program } from 'commander';
4
+ import { createServiceCommand } from './commands/createService.js';
5
+
6
+ program
7
+ .name('create-service')
8
+ .description('Scaffold a new service (model, controller, routes)')
9
+ .argument('<serviceName>', 'Name of the service to create')
10
+ .action(createServiceCommand);
11
+
12
+ program.parse(process.argv);
@@ -0,0 +1,81 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+
4
+ export function configureDatabase(targetPath, database) {
5
+ const packageJsonPath = path.join(targetPath, 'package.json');
6
+ if (!fs.existsSync(packageJsonPath)) return;
7
+
8
+ const pkg = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
9
+ pkg.name = path.basename(targetPath);
10
+
11
+ const dbConfigPath = path.join(targetPath, 'config', 'db.config.js');
12
+ const targetConfigDir = path.dirname(dbConfigPath);
13
+ if (!fs.existsSync(targetConfigDir)) {
14
+ fs.mkdirSync(targetConfigDir, { recursive: true });
15
+ }
16
+
17
+ if (database === 'MongoDB (Mongoose)') {
18
+ pkg.dependencies = pkg.dependencies || {};
19
+ pkg.dependencies.mongoose = '^8.0.0';
20
+
21
+ const mongooseContent = `import mongoose from "mongoose"
22
+
23
+ const options = {
24
+ serverSelectionTimeoutMS: 10000,
25
+ dbName: '',
26
+ }
27
+
28
+ const connectDB = async () => {
29
+ try {
30
+ await mongoose.connect('', options)
31
+ console.log(\`✅ MongoDB connected!\`)
32
+ } catch (error) {
33
+ console.error(\`❌ MongoDB connection error: \${error.message}\`)
34
+ process.exit(1)
35
+ }
36
+ }
37
+
38
+ export default connectDB`;
39
+ fs.writeFileSync(dbConfigPath, mongooseContent);
40
+
41
+ } else if (database === 'PostgreSQL (Sequelize)') {
42
+ pkg.dependencies = pkg.dependencies || {};
43
+ pkg.dependencies.sequelize = '^6.35.0';
44
+ pkg.dependencies.pg = '^8.11.3';
45
+ pkg.dependencies['pg-hstore'] = '^2.3.4';
46
+
47
+ const sequelizeContent = `import { Sequelize } from "sequelize"
48
+
49
+ export const sequelize = new Sequelize(
50
+ '', // DB name
51
+ '', // User
52
+ '', // Password
53
+ {
54
+ host: '',
55
+ dialect: 'postgres',
56
+ logging: false,
57
+ port: ''
58
+ }
59
+ )
60
+
61
+ // Test connection
62
+ // (async () => {
63
+ // try {
64
+ // await sequelize.authenticate();
65
+ // console.log("PostgreSQL connected with Sequelize");
66
+ // } catch (err) {
67
+ // console.error("Error:", err.message);
68
+ // }
69
+ // })()
70
+
71
+ export default sequelize
72
+ `;
73
+ fs.writeFileSync(dbConfigPath, sequelizeContent);
74
+
75
+ } else {
76
+ fs.writeFileSync(dbConfigPath, '// No database configured\n');
77
+ }
78
+
79
+ // Save updated package.json
80
+ fs.writeFileSync(packageJsonPath, JSON.stringify(pkg, null, 2));
81
+ }
@@ -0,0 +1,96 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import chalk from 'chalk';
4
+ import { execSync } from 'child_process';
5
+ import ora from 'ora';
6
+ import inquirer from 'inquirer';
7
+
8
+ export async function promptUser(projectDirectory) {
9
+ let targetDir = projectDirectory;
10
+
11
+ if (!targetDir) {
12
+ const answers = await inquirer.prompt([
13
+ {
14
+ type: 'input',
15
+ name: 'projectDirectory',
16
+ message: 'What is your project named?',
17
+ default: 'my-app'
18
+ }
19
+ ]);
20
+ targetDir = answers.projectDirectory;
21
+ }
22
+
23
+ const { packageManager, database } = await inquirer.prompt([
24
+ {
25
+ type: 'list',
26
+ name: 'packageManager',
27
+ message: 'Which package manager would you like to use?',
28
+ choices: ['npm', 'yarn', 'pnpm'],
29
+ default: 'npm'
30
+ },
31
+ {
32
+ type: 'list',
33
+ name: 'database',
34
+ message: 'Which database would you like to use?',
35
+ choices: ['MongoDB (Mongoose)', 'PostgreSQL (Sequelize)', 'None'],
36
+ default: 'MongoDB (Mongoose)'
37
+ }
38
+ ]);
39
+
40
+ return { targetDir, packageManager, database };
41
+ }
42
+
43
+ export function copyTemplateFiles(templateDir, targetPath) {
44
+ const spinner = ora('Copying template files...').start();
45
+
46
+ if (fs.existsSync(targetPath)) {
47
+ spinner.fail(`Directory ${path.basename(targetPath)} already exists.`);
48
+ process.exit(1);
49
+ }
50
+
51
+ fs.mkdirSync(targetPath, { recursive: true });
52
+
53
+ const copyRecursiveSync = (src, dest) => {
54
+ const exists = fs.existsSync(src);
55
+ const stats = exists && fs.statSync(src);
56
+ if (exists && stats.isDirectory()) {
57
+ if (!fs.existsSync(dest)) fs.mkdirSync(dest);
58
+ fs.readdirSync(src).forEach((child) => {
59
+ if (child === 'node_modules') return;
60
+ copyRecursiveSync(path.join(src, child), path.join(dest, child));
61
+ });
62
+ } else {
63
+ fs.copyFileSync(src, dest);
64
+ }
65
+ };
66
+
67
+ try {
68
+ copyRecursiveSync(templateDir, targetPath);
69
+ spinner.succeed('Template copied successfully.');
70
+ } catch (err) {
71
+ spinner.fail('Failed to copy template.');
72
+ console.error(err);
73
+ process.exit(1);
74
+ }
75
+ }
76
+
77
+ export function installDependencies(targetPath, packageManager) {
78
+ const installSpinner = ora(`Installing dependencies using ${packageManager}...`).start();
79
+ try {
80
+ execSync(`${packageManager} install`, { cwd: targetPath, stdio: 'pipe' });
81
+ installSpinner.succeed('Dependencies installed successfully.');
82
+ } catch (err) {
83
+ installSpinner.fail('Failed to install dependencies.');
84
+ console.error(err);
85
+ }
86
+ }
87
+
88
+ export function showSuccessMessage(targetDir, targetPath, packageManager) {
89
+ console.log(`\n${chalk.green('Success!')} Created ${path.basename(targetPath)} at ${targetPath}`);
90
+ console.log('\nInside that directory, you can run several commands:\n');
91
+ console.log(` ${chalk.cyan(`${packageManager} run dev`)}`);
92
+ console.log(' Starts the development server with nodemon.\n');
93
+ console.log('We suggest that you begin by typing:\n');
94
+ console.log(` ${chalk.cyan('cd')} ${targetDir}`);
95
+ console.log(` ${chalk.cyan(`${packageManager} run dev`)}\n`);
96
+ }
package/package.json ADDED
@@ -0,0 +1,52 @@
1
+ {
2
+ "name": "xpresso-cli",
3
+ "version": "1.3.0",
4
+ "description": "A CLI tool to scaffold Express.js applications.",
5
+ "main": "index.js",
6
+ "type": "module",
7
+ "private": false,
8
+ "bin": {
9
+ "xpresso-cli": "bin/cli.js",
10
+ "create-service": "bin/create-service-cli.js"
11
+ },
12
+ "scripts": {
13
+ "start": "node ./bin/cli.js",
14
+ "create-service": "node ./bin/cli.js create-service"
15
+ },
16
+ "dependencies": {
17
+ "chalk": "^5.3.0",
18
+ "commander": "^12.0.0",
19
+ "inquirer": "^9.2.14",
20
+ "ora": "^8.0.1"
21
+ },
22
+ "repository": {
23
+ "type": "git",
24
+ "url": "git+https://github.com/KrishDhimanOfficial/create_node_app.git"
25
+ },
26
+ "keywords": [
27
+ "express",
28
+ "express.js",
29
+ "create-express-app",
30
+ "node",
31
+ "nodejs",
32
+ "cli",
33
+ "scaffold",
34
+ "generator",
35
+ "express-generator",
36
+ "boilerplate",
37
+ "starter",
38
+ "mvc",
39
+ "modular",
40
+ "api",
41
+ "mongoose",
42
+ "mongodb",
43
+ "sequelize",
44
+ "postgresql"
45
+ ],
46
+ "author": "Krish Dhiman",
47
+ "license": "ISC",
48
+ "bugs": {
49
+ "url": "https://github.com/KrishDhimanOfficial/create_node_app/issues"
50
+ },
51
+ "homepage": "https://github.com/KrishDhimanOfficial/create_node_app#readme"
52
+ }
package/readme.md ADDED
@@ -0,0 +1,128 @@
1
+ # 🚀 xpresso-cli
2
+
3
+ [![npm version](https://img.shields.io/npm/v/xpresso-cli.svg)](https://www.npmjs.com/package/xpresso-cli)
4
+ [![License: ISC](https://img.shields.io/badge/License-ISC-blue.svg)](https://opensource.org/licenses/ISC)
5
+
6
+ A powerful, incredibly fast CLI tool to instantly scaffold production-ready Node.js Express applications. It comes packed with built-in database configurations and an intuitive modular service generator, saving you hours of boilerplate setup.
7
+
8
+ ---
9
+
10
+ ## ✨ Features
11
+
12
+ - **⚡ Instant Setup:** Scaffolds a complete, production-ready Express.js MVC/Modular server structure in seconds.
13
+ - **🗄️ Database Ready:** Automatic, hassle-free database setup for **MongoDB (Mongoose)** or **PostgreSQL (Sequelize)**.
14
+ - **🛠️ Service Generator:** Built-in CLI command to automatically generate new service modules (Controllers, Models, Routes) instantly.
15
+ - **🔒 Best Practices Built-in:** Pre-configured `app.js` with CORS, compression, and global error handlers.
16
+ - **📦 Package Manager Choice:** Support for `npm`, `yarn`, and `pnpm`.
17
+
18
+ ---
19
+
20
+ ## 📖 Step-by-Step Guide
21
+
22
+ ### Step 1: Scaffold a New Application
23
+
24
+ You can use `npx` to create a new application instantly without installing anything globally:
25
+
26
+ ```bash
27
+ npx xpresso-cli my-app
28
+ ```
29
+
30
+ **Alternatively, you can install the CLI globally:**
31
+
32
+ ```bash
33
+ npm install -g xpresso-cli
34
+ ```
35
+
36
+ Then you can use it directly anywhere:
37
+ ```bash
38
+ xpresso-cli my-app
39
+ ```
40
+
41
+ *(If you run the command without a folder name, the CLI will interactively ask you for it!)*
42
+
43
+ ### Step 2: Configure Your Project
44
+
45
+ The interactive CLI will prompt you to make a few quick decisions to tailor your app:
46
+
47
+ 1. **Package Manager:** Choose between `npm`, `yarn`, or `pnpm`.
48
+ 2. **Database:** Select your preferred database:
49
+ - `MongoDB` (uses Mongoose)
50
+ - `PostgreSQL` (uses Sequelize)
51
+ - `None` (if you want to set it up yourself later)
52
+
53
+ Once selected, the CLI will automatically:
54
+ - Generate the folder structure.
55
+ - Configure your database connection file.
56
+ - Generate an initial `auth` service tailored to your database choice.
57
+ - Install all necessary dependencies automatically!
58
+
59
+ ### Step 3: Run Your Application
60
+
61
+ Navigate into your newly created project and start the development server:
62
+
63
+ ```bash
64
+ cd my-app
65
+ npm start
66
+ ```
67
+ *(If you selected yarn or pnpm, use `yarn start` or `pnpm start` respectively).*
68
+
69
+ You now have a fully functional Node.js server running! 🎉
70
+
71
+ ---
72
+
73
+ ## 🏗️ Generating New Services (The Magic)
74
+
75
+ Once your application is created, `xpresso-cli` provides an amazing built-in code generator that saves you from writing repetitive boilerplate for new features.
76
+
77
+ To generate a new service (e.g., for `user` management), navigate into your project folder and run:
78
+
79
+ ```bash
80
+ npx create-service user
81
+ ```
82
+ *(If you installed the CLI globally, you can simply type `create-service user`)*
83
+
84
+ ### What happens behind the scenes?
85
+ When you run `create-service user`, the CLI automatically:
86
+ 1. Creates a Controller (`services/user/controller/user.controller.js`).
87
+ 2. Creates Routes (`services/user/routes/user.routes.js`).
88
+ 3. Creates a Model (`services/user/model/user.model.js`) specifically tailored to the database you selected during initial setup!
89
+ 4. **Auto-wires everything:** It automatically updates your main `app.js` file to import and register your new `user` routes. You don't have to touch a thing!
90
+
91
+ ---
92
+
93
+ ## 📂 Folder Structure
94
+
95
+ Here is the clean, modular structure generated for your app:
96
+
97
+ ```text
98
+ my-app/
99
+ ├── bin/
100
+ │ └── www # Server startup script
101
+ ├── config/
102
+ │ └── db.config.js # Database connection setup
103
+ ├── public/ # Static files
104
+ ├── services/
105
+ │ ├── auth/ # Automatically generated on init
106
+ │ │ ├── controller/
107
+ │ │ ├── model/
108
+ │ │ └── routes/
109
+ │ └── <your-service>/ # Automatically generated via 'create-service'
110
+ ├── utils/
111
+ │ └── helper.utils.js # Helper functions and utilities
112
+ ├── app.js # Express app configuration (auto-updated with new routes)
113
+ └── package.json # Project dependencies and scripts
114
+ ```
115
+
116
+ ---
117
+
118
+ ## 🤝 Contributing
119
+
120
+ Contributions, issues, and feature requests are always welcome! Feel free to check the [issues page](https://github.com/KrishDhimanOfficial/create_node_app/issues).
121
+
122
+ ---
123
+
124
+ ## 📄 License
125
+
126
+ This project is licensed under the [ISC License](LICENSE).
127
+
128
+ **Created with ❤️ by [Krish Dhiman](https://github.com/KrishDhimanOfficial)**
@@ -0,0 +1,36 @@
1
+ import express from 'express'
2
+ import cookieParser from 'cookie-parser'
3
+ import logger from 'morgan'
4
+ import { globalErrorHandler } from './utils/helper.utils.js'
5
+ import compression from 'compression'
6
+ import cors from 'cors'
7
+ import { fileURLToPath } from 'node:url'
8
+ import path from 'node:path'
9
+
10
+ const __filename = fileURLToPath(import.meta.url)
11
+ const __dirname = path.dirname(__filename)
12
+ const app = express()
13
+
14
+ app.use(cors())
15
+ app.use(logger('dev'))
16
+ app.use(express.json({ limit: '10kb' }))
17
+ app.use(express.urlencoded({ extended: true }))
18
+ app.use(compression(
19
+ {
20
+ level: 4, // compression level
21
+ threshold: 0, // Compress all
22
+ memLevel: 9, // memory usuage
23
+ filter: (req, res) => compression.filter(req, res)
24
+ }
25
+ ))
26
+ app.use(cookieParser())
27
+ app.use(express.static(path.join(__dirname, 'public')))
28
+
29
+ app.get('/', (req, res) => {
30
+ return res.send('Hello World!')
31
+ })
32
+
33
+ // error handler
34
+ app.use(globalErrorHandler)
35
+
36
+ export default app
@@ -0,0 +1,93 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Module dependencies.
5
+ */
6
+
7
+ import app from '../app.js';
8
+ import Debug from 'debug';
9
+ const debug = Debug('create-node:server');
10
+ import http from 'http';
11
+
12
+ /**
13
+ * Get port from environment and store in Express.
14
+ */
15
+
16
+ const port = normalizePort(process.env.PORT || '3000');
17
+ app.set('port', port);
18
+
19
+ /**
20
+ * Create HTTP server.
21
+ */
22
+
23
+ const server = http.createServer(app);
24
+
25
+ /**
26
+ * Listen on provided port, on all network interfaces.
27
+ */
28
+
29
+ server.listen(port, () => {
30
+ console.log(`✅ Server is running on port ${port}`)
31
+ })
32
+ server.on('error', onError);
33
+ server.on('listening', onListening);
34
+
35
+ /**
36
+ * Normalize a port into a number, string, or false.
37
+ */
38
+
39
+ function normalizePort(val) {
40
+ const port = parseInt(val, 10);
41
+
42
+ if (isNaN(port)) {
43
+ // named pipe
44
+ return val;
45
+ }
46
+
47
+ if (port >= 0) {
48
+ // port number
49
+ return port;
50
+ }
51
+
52
+ return false;
53
+ }
54
+
55
+ /**
56
+ * Event listener for HTTP server "error" event.
57
+ */
58
+
59
+ function onError(error) {
60
+ if (error.syscall !== 'listen') {
61
+ throw error;
62
+ }
63
+
64
+ const bind = typeof port === 'string'
65
+ ? 'Pipe ' + port
66
+ : 'Port ' + port;
67
+
68
+ // handle specific listen errors with friendly messages
69
+ switch (error.code) {
70
+ case 'EACCES':
71
+ console.error(bind + ' requires elevated privileges');
72
+ process.exit(1);
73
+ break;
74
+ case 'EADDRINUSE':
75
+ console.error(bind + ' is already in use');
76
+ process.exit(1);
77
+ break;
78
+ default:
79
+ throw error;
80
+ }
81
+ }
82
+
83
+ /**
84
+ * Event listener for HTTP server "listening" event.
85
+ */
86
+
87
+ function onListening() {
88
+ const addr = server.address();
89
+ const bind = typeof addr === 'string'
90
+ ? 'pipe ' + addr
91
+ : 'port ' + addr.port;
92
+ debug('Listening on ' + bind);
93
+ }
@@ -0,0 +1,6 @@
1
+ import dotenv from 'dotenv'
2
+ dotenv.config({ quiet: true })
3
+
4
+ export default {
5
+
6
+ }
@@ -0,0 +1,21 @@
1
+ {
2
+ "name": "backend",
3
+ "version": "0.0.0",
4
+ "private": false,
5
+ "type": "module",
6
+ "scripts": {
7
+ "start": "node ./bin/www",
8
+ "dev": "nodemon ./bin/www"
9
+ },
10
+ "dependencies": {
11
+ "compression": "^1.8.1",
12
+ "cookie-parser": "~1.4.4",
13
+ "cors": "^2.8.6",
14
+ "debug": "~2.6.9",
15
+ "express": "^4.22.1",
16
+ "morgan": "^1.10.1"
17
+ },
18
+ "devDependencies": {
19
+ "nodemon": "^3.1.14"
20
+ }
21
+ }
@@ -0,0 +1,8 @@
1
+ body {
2
+ padding: 50px;
3
+ font: 14px "Lucida Grande", Helvetica, Arial, sans-serif;
4
+ }
5
+
6
+ a {
7
+ color: #00B7FF;
8
+ }
@@ -0,0 +1,33 @@
1
+ import { deleteFile } from './removeFile.utils.js'
2
+
3
+ export const ApiError = (message, statusCode) => {
4
+ const error = new Error(message)
5
+ error.statusCode = statusCode
6
+ error.success = false
7
+ return error
8
+ }
9
+
10
+ export const asyncHandler = (fn, name = 'UnknownController') => {
11
+
12
+ return (req, res, next) => {
13
+ Promise.resolve(fn(req, res, next)).catch((err) => {
14
+ console.error(`🔥 Error in ${name} : ${err.message}`)
15
+ if (req.file?.filename) deleteFile(req.file?.path)
16
+ if (req.files && req.files?.length > 0) req.files?.forEach(file => deleteFile(file.path))
17
+ next(err)
18
+ })
19
+ }
20
+ }
21
+
22
+ export const globalErrorHandler = (err, req, res, next) => {
23
+ const statusCode = err.statusCode || 500;
24
+
25
+ // For API requests (JSON response)
26
+ return res.status(statusCode).json(
27
+ {
28
+ success: false,
29
+ message: err.message || 'Something went wrong',
30
+ }
31
+ )
32
+ }
33
+
@@ -0,0 +1,21 @@
1
+ import { fileURLToPath } from 'node:url'
2
+ import path from 'node:path'
3
+ import fs from 'node:fs'
4
+ import chalk from 'chalk'
5
+
6
+ const __filename = fileURLToPath(import.meta.url)
7
+ const __dirname = path.dirname(__filename)
8
+
9
+
10
+ const deleteFile = async (folderPath) => {
11
+ try {
12
+ const imagePath = path.join(__dirname, '..', folderPath)
13
+ if (!fs.existsSync(imagePath)) return
14
+
15
+ await fs.promises.rm(imagePath, { force: true })
16
+ } catch (error) {
17
+ console.error(chalk.red('deleteFile error:', error.message))
18
+ }
19
+ }
20
+
21
+ export { deleteFile }