express-ts-api-starter 1.2.0 → 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/.env.example +23 -0
- package/.gitignore +136 -0
- package/README.md +46 -17
- package/bin/cli.mjs +199 -0
- package/eslint.config.js +22 -0
- package/jest.config.ts +9 -0
- package/package.json +12 -1
- package/scripts/eslint-report.js +65 -0
- package/src/app.ts +56 -0
- package/src/config/dbConfig.ts +17 -0
- package/src/config/emailConfig.ts +0 -0
- package/src/config/envConfig.ts +18 -0
- package/src/config/rateLimitConfig.ts +7 -0
- package/src/config/throttleConfig.cjs +6 -0
- package/src/constants/index.ts +5 -0
- package/src/interfaces/userInterface.ts +11 -0
- package/src/messages/index.ts +29 -0
- package/src/middleware/auth.ts +54 -0
- package/src/middleware/errorMiddleware.ts +30 -0
- package/src/middleware/logMiddleware.ts +31 -0
- package/src/middleware/requestIdMiddleware.ts +13 -0
- package/src/middleware/uploadMiddleware.ts +37 -0
- package/src/middleware/validatorMiddleware.ts +21 -0
- package/src/models/UserModel.ts +30 -0
- package/src/modules/users/tests/userController.test.ts +171 -0
- package/src/modules/users/userController.ts +78 -0
- package/src/modules/users/userMessage.ts +9 -0
- package/src/modules/users/userService.ts +84 -0
- package/src/routes/index.ts +9 -0
- package/src/routes/usersRoute.ts +13 -0
- package/src/server.ts +56 -0
- package/src/types/index.ts +13 -0
- package/src/types/roleType.ts +1 -0
- package/src/types/userTypes.ts +22 -0
- package/src/utils/authFunction.ts +15 -0
- package/src/utils/responseUtil.ts +42 -0
- package/src/validators/index.ts +1 -0
- package/src/validators/userValidators.ts +38 -0
- package/tsconfig.json +16 -0
package/.env.example
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# Environment Configuration
|
|
2
|
+
NODE_ENV=development
|
|
3
|
+
|
|
4
|
+
# Server Configuration
|
|
5
|
+
PORT=5000
|
|
6
|
+
BASIC_API_URL=/api/v1
|
|
7
|
+
|
|
8
|
+
# Database Configuration
|
|
9
|
+
DB_CONNECTION=mongodb://localhost:27017/
|
|
10
|
+
DB_NAME=testDB
|
|
11
|
+
|
|
12
|
+
# Authentication
|
|
13
|
+
JWT_SECRET=your-super-secret-jwt-key-change-in-production
|
|
14
|
+
|
|
15
|
+
# Email Configuration (if using email features)
|
|
16
|
+
SMTP_HOST=smtp.gmail.com
|
|
17
|
+
SMTP_PORT=587
|
|
18
|
+
SMTP_USER=your-email@gmail.com
|
|
19
|
+
SMTP_PASSWORD=your-app-password
|
|
20
|
+
|
|
21
|
+
# Optional: Third-party APIs
|
|
22
|
+
# API_KEY=your-api-key
|
|
23
|
+
# EXTERNAL_SERVICE_URL=https://api.example.com
|
package/.gitignore
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
# Logs
|
|
2
|
+
logs
|
|
3
|
+
*.log
|
|
4
|
+
npm-debug.log*
|
|
5
|
+
yarn-debug.log*
|
|
6
|
+
yarn-error.log*
|
|
7
|
+
lerna-debug.log*
|
|
8
|
+
.pnpm-debug.log*
|
|
9
|
+
|
|
10
|
+
# Diagnostic reports (https://nodejs.org/api/report.html)
|
|
11
|
+
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
|
12
|
+
|
|
13
|
+
# Runtime data
|
|
14
|
+
pids
|
|
15
|
+
*.pid
|
|
16
|
+
*.seed
|
|
17
|
+
*.pid.lock
|
|
18
|
+
|
|
19
|
+
# Directory for instrumented libs generated by jscoverage/JSCover
|
|
20
|
+
lib-cov
|
|
21
|
+
|
|
22
|
+
# Coverage directory used by tools like istanbul
|
|
23
|
+
coverage
|
|
24
|
+
*.lcov
|
|
25
|
+
|
|
26
|
+
# nyc test coverage
|
|
27
|
+
.nyc_output
|
|
28
|
+
|
|
29
|
+
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
|
30
|
+
.grunt
|
|
31
|
+
|
|
32
|
+
# Bower dependency directory (https://bower.io/)
|
|
33
|
+
bower_components
|
|
34
|
+
|
|
35
|
+
# node-waf configuration
|
|
36
|
+
.lock-wscript
|
|
37
|
+
|
|
38
|
+
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
|
39
|
+
build/Release
|
|
40
|
+
|
|
41
|
+
# Dependency directories
|
|
42
|
+
node_modules/
|
|
43
|
+
jspm_packages/
|
|
44
|
+
|
|
45
|
+
# Snowpack dependency directory (https://snowpack.dev/)
|
|
46
|
+
web_modules/
|
|
47
|
+
|
|
48
|
+
# TypeScript cache
|
|
49
|
+
*.tsbuildinfo
|
|
50
|
+
|
|
51
|
+
# Optional npm cache directory
|
|
52
|
+
.npm
|
|
53
|
+
|
|
54
|
+
# Optional eslint cache
|
|
55
|
+
.eslintcache
|
|
56
|
+
|
|
57
|
+
# Optional stylelint cache
|
|
58
|
+
.stylelintcache
|
|
59
|
+
|
|
60
|
+
# Microbundle cache
|
|
61
|
+
.rpt2_cache/
|
|
62
|
+
.rts2_cache_cjs/
|
|
63
|
+
.rts2_cache_es/
|
|
64
|
+
.rts2_cache_umd/
|
|
65
|
+
|
|
66
|
+
# Optional REPL history
|
|
67
|
+
.node_repl_history
|
|
68
|
+
|
|
69
|
+
# Output of 'npm pack'
|
|
70
|
+
*.tgz
|
|
71
|
+
|
|
72
|
+
# Yarn Integrity file
|
|
73
|
+
.yarn-integrity
|
|
74
|
+
|
|
75
|
+
# dotenv environment variable files
|
|
76
|
+
.env
|
|
77
|
+
.env.development.local
|
|
78
|
+
.env.test.local
|
|
79
|
+
.env.production.local
|
|
80
|
+
.env.local
|
|
81
|
+
|
|
82
|
+
# parcel-bundler cache (https://parceljs.org/)
|
|
83
|
+
.cache
|
|
84
|
+
.parcel-cache
|
|
85
|
+
|
|
86
|
+
# Next.js build output
|
|
87
|
+
.next
|
|
88
|
+
out
|
|
89
|
+
|
|
90
|
+
# Nuxt.js build / generate output
|
|
91
|
+
.nuxt
|
|
92
|
+
dist
|
|
93
|
+
|
|
94
|
+
# Gatsby files
|
|
95
|
+
.cache/
|
|
96
|
+
# Comment in the public line in if your project uses Gatsby and not Next.js
|
|
97
|
+
# https://nextjs.org/blog/next-9-1#public-directory-support
|
|
98
|
+
# public
|
|
99
|
+
|
|
100
|
+
# vuepress build output
|
|
101
|
+
.vuepress/dist
|
|
102
|
+
|
|
103
|
+
# vuepress v2.x temp and cache directory
|
|
104
|
+
.temp
|
|
105
|
+
.cache
|
|
106
|
+
|
|
107
|
+
# vitepress build output
|
|
108
|
+
**/.vitepress/dist
|
|
109
|
+
|
|
110
|
+
# vitepress cache directory
|
|
111
|
+
**/.vitepress/cache
|
|
112
|
+
|
|
113
|
+
# Docusaurus cache and generated files
|
|
114
|
+
.docusaurus
|
|
115
|
+
|
|
116
|
+
# Serverless directories
|
|
117
|
+
.serverless/
|
|
118
|
+
|
|
119
|
+
# FuseBox cache
|
|
120
|
+
.fusebox/
|
|
121
|
+
|
|
122
|
+
# DynamoDB Local files
|
|
123
|
+
.dynamodb/
|
|
124
|
+
|
|
125
|
+
# TernJS port file
|
|
126
|
+
.tern-port
|
|
127
|
+
|
|
128
|
+
# Stores VSCode versions used for testing VSCode extensions
|
|
129
|
+
.vscode-test
|
|
130
|
+
|
|
131
|
+
# yarn v2
|
|
132
|
+
.yarn/cache
|
|
133
|
+
.yarn/unplugged
|
|
134
|
+
.yarn/build-state.yml
|
|
135
|
+
.yarn/install-state.gz
|
|
136
|
+
.pnp.*
|
package/README.md
CHANGED
|
@@ -13,34 +13,48 @@
|
|
|
13
13
|
|
|
14
14
|
## 📌 Quick Demo
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
// Error handling: Global middleware catches all errors
|
|
24
|
-
// Validation: Built-in express-validator ready
|
|
25
|
-
res.json({ message: 'API is running!' });
|
|
26
|
-
});
|
|
16
|
+
**Create a new project in seconds:**
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npx express-ts-api-starter my-api
|
|
20
|
+
cd my-api
|
|
21
|
+
npm install
|
|
22
|
+
npm run dev
|
|
27
23
|
```
|
|
28
24
|
|
|
25
|
+
Your API is ready with authentication, validation, and error handling out of the box!
|
|
26
|
+
|
|
29
27
|
---
|
|
30
28
|
|
|
31
29
|
## 🚀 Quick Start
|
|
32
30
|
|
|
33
|
-
###
|
|
31
|
+
### Option 1: Using CLI (Recommended)
|
|
32
|
+
|
|
33
|
+
Create a new project with a single command:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
npx express-ts-api-starter my-api
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
This will:
|
|
40
|
+
- ✅ Create a new project directory
|
|
41
|
+
- ✅ Copy all template files and folder structure
|
|
42
|
+
- ✅ Set up configuration files
|
|
43
|
+
- ✅ Create `.env` file from `.env.example`
|
|
44
|
+
|
|
45
|
+
Then:
|
|
34
46
|
|
|
35
47
|
```bash
|
|
36
|
-
|
|
48
|
+
cd my-api
|
|
49
|
+
npm install
|
|
50
|
+
npm run dev
|
|
37
51
|
```
|
|
38
52
|
|
|
39
|
-
###
|
|
53
|
+
### Option 2: Manual Installation
|
|
40
54
|
|
|
41
55
|
```bash
|
|
42
56
|
# Clone the repository
|
|
43
|
-
git clone https://github.com/nikhilpktcr/express-ts-starter.git my-api
|
|
57
|
+
git clone https://github.com/nikhilpktcr/express-ts-api-starter.git my-api
|
|
44
58
|
cd my-api
|
|
45
59
|
|
|
46
60
|
# Install dependencies
|
|
@@ -92,6 +106,20 @@ npm run dev
|
|
|
92
106
|
| **TypeScript** | Sometimes | ✅ **100% TypeScript with strict mode** |
|
|
93
107
|
| **Documentation** | Minimal | ✅ **Well-documented with examples** |
|
|
94
108
|
| **Testing** | Sometimes | ✅ **Jest with test examples included** |
|
|
109
|
+
| **CLI Tool** | Sometimes | ✅ **Built-in CLI generator** |
|
|
110
|
+
|
|
111
|
+
### vs. NestJS
|
|
112
|
+
|
|
113
|
+
| Feature | NestJS | express-ts-api-starter |
|
|
114
|
+
|---------|--------|------------------------|
|
|
115
|
+
| **Setup Time** | 10-15 minutes | ⚡ **2 minutes** |
|
|
116
|
+
| **Learning Curve** | High (new framework) | ✅ **Low (Express knowledge)** |
|
|
117
|
+
| **Bundle Size** | ~200KB+ | ✅ **~50KB (lightweight)** |
|
|
118
|
+
| **Flexibility** | Framework-driven | ✅ **High (minimal abstraction)** |
|
|
119
|
+
| **Request Tracking** | Manual setup | ✅ **Built-in request IDs** |
|
|
120
|
+
| **Security (Out of Box)** | Manual config | ✅ **Pre-configured** |
|
|
121
|
+
|
|
122
|
+
📖 **[See detailed NestJS comparison →](COMPARISON-NESTJS.md)**
|
|
95
123
|
|
|
96
124
|
---
|
|
97
125
|
|
|
@@ -152,6 +180,7 @@ npm run dev
|
|
|
152
180
|
|
|
153
181
|
### Developer Experience
|
|
154
182
|
|
|
183
|
+
- ✅ **CLI Tool** - Generate projects with one command
|
|
155
184
|
- ✅ **Hot reload** - See changes instantly
|
|
156
185
|
- ✅ **TypeScript declarations** - Full IntelliSense support
|
|
157
186
|
- ✅ **Pre-configured scripts** - dev, build, test, lint
|
|
@@ -349,7 +378,7 @@ Free for personal and commercial use! ✨
|
|
|
349
378
|
## 💬 Support
|
|
350
379
|
|
|
351
380
|
- **Email**: nikhil.pk.connect@gmail.com
|
|
352
|
-
- **GitHub Issues**: [Report bugs](https://github.com/nikhilpktcr/express-ts-starter/issues)
|
|
381
|
+
- **GitHub Issues**: [Report bugs](https://github.com/nikhilpktcr/express-ts-api-starter/issues)
|
|
353
382
|
- **GitHub**: [@nikhilpktcr](https://github.com/nikhilpktcr)
|
|
354
383
|
|
|
355
384
|
---
|
|
@@ -381,7 +410,7 @@ If this boilerplate helps your project:
|
|
|
381
410
|
**Ready to build?** Start your next API project in minutes:
|
|
382
411
|
|
|
383
412
|
```bash
|
|
384
|
-
|
|
413
|
+
npx express-ts-api-starter my-api
|
|
385
414
|
```
|
|
386
415
|
|
|
387
416
|
**Happy coding!** 🎉
|
package/bin/cli.mjs
ADDED
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import fs from 'fs';
|
|
4
|
+
import path from 'path';
|
|
5
|
+
import { fileURLToPath } from 'url';
|
|
6
|
+
import { dirname } from 'path';
|
|
7
|
+
|
|
8
|
+
// Get __dirname equivalent in ES modules
|
|
9
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
10
|
+
const __dirname = dirname(__filename);
|
|
11
|
+
|
|
12
|
+
// Colors for terminal output
|
|
13
|
+
const colors = {
|
|
14
|
+
reset: '\x1b[0m',
|
|
15
|
+
bright: '\x1b[1m',
|
|
16
|
+
green: '\x1b[32m',
|
|
17
|
+
yellow: '\x1b[33m',
|
|
18
|
+
blue: '\x1b[34m',
|
|
19
|
+
cyan: '\x1b[36m',
|
|
20
|
+
red: '\x1b[31m',
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
function log(message, color = 'reset') {
|
|
24
|
+
console.log(`${colors[color]}${message}${colors.reset}`);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function getProjectName() {
|
|
28
|
+
const args = process.argv.slice(2);
|
|
29
|
+
if (args.length > 0) {
|
|
30
|
+
return args[0];
|
|
31
|
+
}
|
|
32
|
+
return 'my-express-api';
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function isValidProjectName(name) {
|
|
36
|
+
return /^[a-z0-9-]+$/.test(name) && name.length > 0;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function copyDirectory(src, dest) {
|
|
40
|
+
if (!fs.existsSync(src)) {
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
45
|
+
const entries = fs.readdirSync(src, { withFileTypes: true });
|
|
46
|
+
|
|
47
|
+
for (const entry of entries) {
|
|
48
|
+
const srcPath = path.join(src, entry.name);
|
|
49
|
+
const destPath = path.join(dest, entry.name);
|
|
50
|
+
|
|
51
|
+
if (entry.isDirectory()) {
|
|
52
|
+
// Skip node_modules and dist directories
|
|
53
|
+
if (entry.name === 'node_modules' || entry.name === 'dist') {
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
copyDirectory(srcPath, destPath);
|
|
57
|
+
} else {
|
|
58
|
+
// Skip certain files
|
|
59
|
+
if (
|
|
60
|
+
entry.name === 'package-lock.json' ||
|
|
61
|
+
entry.name === '.env' ||
|
|
62
|
+
entry.name === 'eslint-report.json'
|
|
63
|
+
) {
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
fs.copyFileSync(srcPath, destPath);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function updatePackageJson(projectPath, projectName) {
|
|
72
|
+
const packageJsonPath = path.join(projectPath, 'package.json');
|
|
73
|
+
if (!fs.existsSync(packageJsonPath)) {
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
|
|
78
|
+
|
|
79
|
+
// Update package name
|
|
80
|
+
packageJson.name = projectName;
|
|
81
|
+
|
|
82
|
+
// Remove bin field if it exists (not needed in generated project)
|
|
83
|
+
delete packageJson.bin;
|
|
84
|
+
|
|
85
|
+
// Remove prepublishOnly script
|
|
86
|
+
delete packageJson.scripts.prepublishOnly;
|
|
87
|
+
|
|
88
|
+
fs.writeFileSync(
|
|
89
|
+
packageJsonPath,
|
|
90
|
+
JSON.stringify(packageJson, null, 2) + '\n',
|
|
91
|
+
'utf8'
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function createEnvFile(projectPath) {
|
|
96
|
+
const envExamplePath = path.join(projectPath, '.env.example');
|
|
97
|
+
const envPath = path.join(projectPath, '.env');
|
|
98
|
+
|
|
99
|
+
if (fs.existsSync(envExamplePath) && !fs.existsSync(envPath)) {
|
|
100
|
+
fs.copyFileSync(envExamplePath, envPath);
|
|
101
|
+
log('✓ Created .env file from .env.example', 'green');
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function main() {
|
|
106
|
+
log('\n🚀 Express TypeScript API Starter', 'bright');
|
|
107
|
+
log('=====================================\n', 'cyan');
|
|
108
|
+
|
|
109
|
+
const projectName = getProjectName();
|
|
110
|
+
|
|
111
|
+
if (!isValidProjectName(projectName)) {
|
|
112
|
+
log('❌ Error: Invalid project name!', 'red');
|
|
113
|
+
log(' Project name should only contain lowercase letters, numbers, and hyphens.\n', 'yellow');
|
|
114
|
+
process.exit(1);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const currentDir = process.cwd();
|
|
118
|
+
const projectPath = path.join(currentDir, projectName);
|
|
119
|
+
|
|
120
|
+
// Check if directory already exists
|
|
121
|
+
if (fs.existsSync(projectPath)) {
|
|
122
|
+
log(`❌ Error: Directory "${projectName}" already exists!`, 'red');
|
|
123
|
+
log(' Please choose a different name or remove the existing directory.\n', 'yellow');
|
|
124
|
+
process.exit(1);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
log(`📦 Creating project: ${projectName}...\n`, 'blue');
|
|
128
|
+
|
|
129
|
+
try {
|
|
130
|
+
// Get the template directory (where this package is installed)
|
|
131
|
+
const templateDir = path.resolve(__dirname, '..');
|
|
132
|
+
|
|
133
|
+
// Create project directory
|
|
134
|
+
fs.mkdirSync(projectPath, { recursive: true });
|
|
135
|
+
log(`✓ Created directory: ${projectName}`, 'green');
|
|
136
|
+
|
|
137
|
+
// Copy template files
|
|
138
|
+
log('📋 Copying template files...', 'blue');
|
|
139
|
+
|
|
140
|
+
// Copy src directory
|
|
141
|
+
const srcDir = path.join(templateDir, 'src');
|
|
142
|
+
if (fs.existsSync(srcDir)) {
|
|
143
|
+
copyDirectory(srcDir, path.join(projectPath, 'src'));
|
|
144
|
+
log(' ✓ Copied src/ directory', 'green');
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Copy scripts directory
|
|
148
|
+
const scriptsDir = path.join(templateDir, 'scripts');
|
|
149
|
+
if (fs.existsSync(scriptsDir)) {
|
|
150
|
+
copyDirectory(scriptsDir, path.join(projectPath, 'scripts'));
|
|
151
|
+
log(' ✓ Copied scripts/ directory', 'green');
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Copy configuration files
|
|
155
|
+
const configFiles = [
|
|
156
|
+
'tsconfig.json',
|
|
157
|
+
'eslint.config.js',
|
|
158
|
+
'jest.config.ts',
|
|
159
|
+
'.env.example',
|
|
160
|
+
'.gitignore',
|
|
161
|
+
'README.md',
|
|
162
|
+
'LICENSE',
|
|
163
|
+
];
|
|
164
|
+
|
|
165
|
+
for (const file of configFiles) {
|
|
166
|
+
const srcFile = path.join(templateDir, file);
|
|
167
|
+
if (fs.existsSync(srcFile)) {
|
|
168
|
+
fs.copyFileSync(srcFile, path.join(projectPath, file));
|
|
169
|
+
log(` ✓ Copied ${file}`, 'green');
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Update package.json
|
|
174
|
+
updatePackageJson(projectPath, projectName);
|
|
175
|
+
log(' ✓ Updated package.json', 'green');
|
|
176
|
+
|
|
177
|
+
// Create .env file
|
|
178
|
+
createEnvFile(projectPath);
|
|
179
|
+
|
|
180
|
+
log('\n✅ Project created successfully!\n', 'green');
|
|
181
|
+
|
|
182
|
+
// Next steps
|
|
183
|
+
log('📝 Next steps:', 'bright');
|
|
184
|
+
log(` cd ${projectName}`, 'cyan');
|
|
185
|
+
log(' npm install', 'cyan');
|
|
186
|
+
log(' npm run dev', 'cyan');
|
|
187
|
+
log('\n🎉 Happy coding!\n', 'green');
|
|
188
|
+
|
|
189
|
+
} catch (error) {
|
|
190
|
+
log(`\n❌ Error: ${error.message}`, 'red');
|
|
191
|
+
// Clean up on error
|
|
192
|
+
if (fs.existsSync(projectPath)) {
|
|
193
|
+
fs.rmSync(projectPath, { recursive: true, force: true });
|
|
194
|
+
}
|
|
195
|
+
process.exit(1);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
main();
|
package/eslint.config.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import js from '@eslint/js'
|
|
2
|
+
import globals from 'globals'
|
|
3
|
+
import tseslint from 'typescript-eslint'
|
|
4
|
+
|
|
5
|
+
export default tseslint.config({
|
|
6
|
+
ignores: ['dist/**'],
|
|
7
|
+
},
|
|
8
|
+
{
|
|
9
|
+
extends: [
|
|
10
|
+
js.configs.recommended,
|
|
11
|
+
...tseslint.configs.recommended,
|
|
12
|
+
],
|
|
13
|
+
files: ['**/*.{ts,js,cjs,mjs}'],
|
|
14
|
+
languageOptions: {
|
|
15
|
+
ecmaVersion: 2020,
|
|
16
|
+
globals: globals.browser,
|
|
17
|
+
},
|
|
18
|
+
rules: {
|
|
19
|
+
'@typescript-eslint/no-unused-vars': 'off',
|
|
20
|
+
'@typescript-eslint/no-explicit-any': "off"
|
|
21
|
+
},
|
|
22
|
+
});
|
package/jest.config.ts
ADDED
package/package.json
CHANGED
|
@@ -1,13 +1,24 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "express-ts-api-starter",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"description": "Production-ready Express.js + TypeScript boilerplate with MVC architecture, JWT auth, MongoDB, security, validation, and testing—build scalable REST APIs fast",
|
|
5
5
|
"main": "dist/server.js",
|
|
6
6
|
"types": "dist/server.d.ts",
|
|
7
|
+
"bin": {
|
|
8
|
+
"express-ts-api-starter": "./bin/cli.mjs"
|
|
9
|
+
},
|
|
7
10
|
"files": [
|
|
11
|
+
"src",
|
|
8
12
|
"dist",
|
|
9
13
|
"!dist/**/*.test.js",
|
|
10
14
|
"!dist/**/*.spec.js",
|
|
15
|
+
"scripts",
|
|
16
|
+
"bin",
|
|
17
|
+
"tsconfig.json",
|
|
18
|
+
"eslint.config.js",
|
|
19
|
+
"jest.config.ts",
|
|
20
|
+
".env.example",
|
|
21
|
+
".gitignore",
|
|
11
22
|
"README.md",
|
|
12
23
|
"LICENSE"
|
|
13
24
|
],
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import chalk from 'chalk';
|
|
4
|
+
import { execSync } from 'child_process';
|
|
5
|
+
import { fileURLToPath } from 'url';
|
|
6
|
+
|
|
7
|
+
// Emulate __dirname in ES modules
|
|
8
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
9
|
+
const __dirname = path.dirname(__filename);
|
|
10
|
+
|
|
11
|
+
const reportPath = path.resolve(process.cwd(), 'eslint-report.json');
|
|
12
|
+
|
|
13
|
+
// Step 1: Run ESLint and generate report
|
|
14
|
+
try {
|
|
15
|
+
console.log(chalk.blue('🔍 Running ESLint...'));
|
|
16
|
+
execSync('npx eslint . --ext .ts,.js --format json -o eslint-report.json', { stdio: 'inherit' });
|
|
17
|
+
} catch (err) {
|
|
18
|
+
console.error(chalk.red('❌ ESLint execution failed.'));
|
|
19
|
+
process.exit(1);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Step 2: Read and display the report
|
|
23
|
+
if (!fs.existsSync(reportPath)) {
|
|
24
|
+
console.error(chalk.red('❌ ESLint report not found.'));
|
|
25
|
+
process.exit(1);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const report = JSON.parse(fs.readFileSync(reportPath, 'utf-8'));
|
|
29
|
+
|
|
30
|
+
let totalErrors = 0;
|
|
31
|
+
let totalWarnings = 0;
|
|
32
|
+
let passedFiles = 0;
|
|
33
|
+
|
|
34
|
+
console.log(chalk.bold('\nTest Suites: ESLint Report\n'));
|
|
35
|
+
|
|
36
|
+
report.forEach((file) => {
|
|
37
|
+
const fileName = path.relative(process.cwd(), file.filePath);
|
|
38
|
+
const errors = file.errorCount;
|
|
39
|
+
const warnings = file.warningCount;
|
|
40
|
+
|
|
41
|
+
totalErrors += errors;
|
|
42
|
+
totalWarnings += warnings;
|
|
43
|
+
|
|
44
|
+
if (errors === 0 && warnings === 0) {
|
|
45
|
+
passedFiles++;
|
|
46
|
+
console.log(chalk.green(`✓ ${fileName}`));
|
|
47
|
+
} else {
|
|
48
|
+
console.log(chalk.red(`✖ ${fileName}`));
|
|
49
|
+
file.messages.forEach((msg) => {
|
|
50
|
+
const severity = msg.severity === 2 ? chalk.red('error') : chalk.yellow('warning');
|
|
51
|
+
console.log(` ${chalk.gray(msg.ruleId || 'unknown')} - ${severity}: ${msg.message}`);
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
console.log('\n' + chalk.bold('Summary:'));
|
|
57
|
+
console.log(chalk.green(` Passed files: ${passedFiles}/${report.length}`));
|
|
58
|
+
console.log(chalk.red(` Total Errors: ${totalErrors}`));
|
|
59
|
+
console.log(chalk.yellow(` Total Warnings: ${totalWarnings}\n`));
|
|
60
|
+
|
|
61
|
+
if (totalErrors === 0) {
|
|
62
|
+
console.log(chalk.green.bold('✔ All files passed linting!\n'));
|
|
63
|
+
} else {
|
|
64
|
+
console.log(chalk.red.bold('✖ Linting issues detected.\n'));
|
|
65
|
+
}
|
package/src/app.ts
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import express, { Application, Request } from "express";
|
|
2
|
+
import cors from "cors";
|
|
3
|
+
import morgan from "morgan";
|
|
4
|
+
import { env } from "./config/envConfig";
|
|
5
|
+
import routes from "./routes";
|
|
6
|
+
import helmet from "helmet";
|
|
7
|
+
// import { limiterConfig } from "./config/rateLimitConfig";
|
|
8
|
+
import connectDB from "./config/dbConfig";
|
|
9
|
+
import {
|
|
10
|
+
globalErrorHandler,
|
|
11
|
+
notFoundHandler,
|
|
12
|
+
} from "./middleware/errorMiddleware";
|
|
13
|
+
import { sendErrorResponse } from "./utils/responseUtil";
|
|
14
|
+
import { StatusCodes } from "http-status-codes";
|
|
15
|
+
import { requestIdMiddleware } from "./middleware/requestIdMiddleware";
|
|
16
|
+
|
|
17
|
+
const app: Application = express();
|
|
18
|
+
|
|
19
|
+
// ===== Middleware =====
|
|
20
|
+
// app.use(limiterConfig);
|
|
21
|
+
app.use(helmet());
|
|
22
|
+
app.use(cors());
|
|
23
|
+
app.use(express.json());
|
|
24
|
+
app.use(express.urlencoded({ extended: true }));
|
|
25
|
+
connectDB();
|
|
26
|
+
|
|
27
|
+
// ===== Request ID Middleware =====
|
|
28
|
+
app.use(requestIdMiddleware);
|
|
29
|
+
|
|
30
|
+
// ===== Morgan Logging with Request ID =====
|
|
31
|
+
|
|
32
|
+
morgan.token("id", (req: Request) => {
|
|
33
|
+
const requestId = req.headers["x-request-id"];
|
|
34
|
+
return typeof requestId === "string"
|
|
35
|
+
? requestId
|
|
36
|
+
: Array.isArray(requestId)
|
|
37
|
+
? requestId[0]
|
|
38
|
+
: "unknown";
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
app.use(
|
|
42
|
+
morgan(
|
|
43
|
+
"🧾 requestId=:id 🚀 :method :url 📦 status=:status ⏱️ :response-time ms"
|
|
44
|
+
)
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
// ===== API Routes =====
|
|
48
|
+
app.use(env.BASIC_API_URL, routes);
|
|
49
|
+
|
|
50
|
+
// ===== Un handled Routes =====
|
|
51
|
+
app.use(notFoundHandler);
|
|
52
|
+
|
|
53
|
+
// ===== Global Error Handler =====
|
|
54
|
+
app.use(globalErrorHandler);
|
|
55
|
+
|
|
56
|
+
export default app;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import mongoose from "mongoose";
|
|
2
|
+
import { env } from "./envConfig";
|
|
3
|
+
import messages from "../messages";
|
|
4
|
+
|
|
5
|
+
const connectDB = async (): Promise<void> => {
|
|
6
|
+
try {
|
|
7
|
+
await mongoose.connect(env.DB_CONNECTION, {
|
|
8
|
+
dbName: env.DB_NAME,
|
|
9
|
+
});
|
|
10
|
+
console.log(`${env.DB_NAME} ${messages.DB_CONNECT_SUCESS}`);
|
|
11
|
+
} catch (error) {
|
|
12
|
+
console.error(`${env.DB_NAME} ${messages.DB_CONNECT_FAILED}`, error);
|
|
13
|
+
process.exit(1);
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export default connectDB;
|
|
File without changes
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import dotenv from "dotenv";
|
|
2
|
+
dotenv.config();
|
|
3
|
+
|
|
4
|
+
function getEnvVar(key: string, fallback?: string): string {
|
|
5
|
+
const value = process.env[key];
|
|
6
|
+
if (!value && fallback === undefined) {
|
|
7
|
+
throw new Error(`Missing required environment variable: ${key}`);
|
|
8
|
+
}
|
|
9
|
+
return value || fallback!;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const env = {
|
|
13
|
+
PORT: getEnvVar("PORT", "5000"),
|
|
14
|
+
BASIC_API_URL: getEnvVar("BASIC_API_URL", "/api/v1"),
|
|
15
|
+
DB_NAME: getEnvVar("DB_NAME", "testDB"),
|
|
16
|
+
JWT_SECRET: getEnvVar("JWT_SECRET", "supersecret"),
|
|
17
|
+
DB_CONNECTION: getEnvVar("DB_CONNECTION", "mongodb://localhost:27017/"),
|
|
18
|
+
};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { rateLimit } from "express-rate-limit";
|
|
2
|
+
|
|
3
|
+
export const limiterConfig = rateLimit({
|
|
4
|
+
windowMs: 15 * 60 * 1000, // 15 minutes
|
|
5
|
+
limit: 100, // Limit each IP to 100 requests per `window` (here, per 15 minutes).
|
|
6
|
+
legacyHeaders: false, // Disable the `X-RateLimit-*` headers.
|
|
7
|
+
});
|