express-genix 1.1.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/README.md +110 -0
- package/index.js +114 -0
- package/lib/cleanup.js +129 -0
- package/lib/generator.js +205 -0
- package/lib/utils.js +92 -0
- package/package.json +49 -0
- package/templates/config/database.mongo.js.ejs +36 -0
- package/templates/config/database.postgres.js.ejs +41 -0
- package/templates/config/swagger.json.ejs +194 -0
- package/templates/controllers/authController.js.ejs +129 -0
- package/templates/controllers/exampleController.js.ejs +152 -0
- package/templates/controllers/userController.js.ejs +60 -0
- package/templates/core/Dockerfile.ejs +32 -0
- package/templates/core/README.md.ejs +179 -0
- package/templates/core/app.js.ejs +65 -0
- package/templates/core/docker-compose.yml.ejs +48 -0
- package/templates/core/env.ejs +20 -0
- package/templates/core/eslintrc.json.ejs +20 -0
- package/templates/core/gitignore.ejs +51 -0
- package/templates/core/healthcheck.js.ejs +25 -0
- package/templates/core/index.js.ejs +24 -0
- package/templates/core/jest.config.js.ejs +23 -0
- package/templates/core/package.json.ejs +33 -0
- package/templates/core/prettierrc.json.ejs +12 -0
- package/templates/core/server.js.ejs +44 -0
- package/templates/middleware/auth.js.ejs +66 -0
- package/templates/middleware/errorHandler.js.ejs +47 -0
- package/templates/middleware/validation.js.ejs +48 -0
- package/templates/models/User.mongo.js.ejs +33 -0
- package/templates/models/User.postgres.js.ejs +41 -0
- package/templates/models/index.mongo.js.ejs +8 -0
- package/templates/models/index.postgres.js.ejs +12 -0
- package/templates/routes/authRoutes.js.ejs +14 -0
- package/templates/routes/exampleRoutes.js.ejs +13 -0
- package/templates/routes/index.js.ejs +24 -0
- package/templates/routes/userRoutes.js.ejs +16 -0
- package/templates/services/authService.js.ejs +36 -0
- package/templates/services/exampleService.js.ejs +113 -0
- package/templates/services/userService.mongo.js.ejs +34 -0
- package/templates/services/userService.postgres.js.ejs +31 -0
- package/templates/tests/auth.test.js.ejs +67 -0
- package/templates/tests/example.test.js.ejs +113 -0
- package/templates/tests/setup.js.ejs +12 -0
- package/templates/tests/users.test.js.ejs +43 -0
- package/templates/utils/errors.js.ejs +13 -0
- package/templates/utils/logger.js.ejs +28 -0
- package/templates/utils/validators.js.ejs +35 -0
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
# <%= projectName %>
|
|
2
|
+
|
|
3
|
+
A production-grade Express.js boilerplate<% if (hasDatabase) { %> with JWT authentication, <%= db %> support<% } %>, rate-limiting, security middleware, logging, API documentation, testing, and more.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
<% if (hasDatabase) { %>- **Authentication**: JWT-based authentication with refresh tokens
|
|
8
|
+
- **Database**: <%= db === 'mongodb' ? 'MongoDB with Mongoose' : 'PostgreSQL with Sequelize' %>
|
|
9
|
+
- **User Management**: Complete user registration, login, profile management<% } else { %>- **No Database**: Perfect for microservices, proxy servers, or computational APIs
|
|
10
|
+
- **Example API**: CRUD operations with in-memory storage (easily replaceable)<% } %>
|
|
11
|
+
- **Security**: Helmet, CORS, rate limiting
|
|
12
|
+
- **Logging**: Morgan HTTP request logger with custom logging utilities
|
|
13
|
+
- **API Documentation**: Swagger UI
|
|
14
|
+
- **Testing**: Jest and Supertest
|
|
15
|
+
- **Code Quality**: ESLint with Airbnb configuration, Prettier formatting
|
|
16
|
+
- **Deployment**: Docker support with clustering
|
|
17
|
+
- **Project Structure**: Clean, modular Express.js architecture
|
|
18
|
+
|
|
19
|
+
## Project Structure
|
|
20
|
+
|
|
21
|
+
```
|
|
22
|
+
├── src/
|
|
23
|
+
│ ├── config/ # Configuration files (swagger<% if (hasDatabase) { %>, database<% } %>)
|
|
24
|
+
│ ├── controllers/ # Request handlers and business logic orchestration<% if (hasDatabase) { %>
|
|
25
|
+
│ ├── middleware/ # Custom middleware (auth, validation, errors)<% } else { %>
|
|
26
|
+
│ ├── middleware/ # Custom middleware (errors)<% } %>
|
|
27
|
+
<% if (hasDatabase) { %>│ ├── models/ # Database models<% } %>
|
|
28
|
+
│ ├── routes/ # API endpoint definitions
|
|
29
|
+
│ ├── services/ # Business logic layer
|
|
30
|
+
│ ├── utils/ # Utility functions and helpers
|
|
31
|
+
│ ├── app.js # Express app configuration
|
|
32
|
+
│ └── server.js # Server setup and clustering
|
|
33
|
+
├── tests/ # Test files
|
|
34
|
+
├── .env # Environment variables
|
|
35
|
+
├── .gitignore # Git ignore rules
|
|
36
|
+
├── package.json # Dependencies and scripts
|
|
37
|
+
└── README.md # Project documentation
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Setup
|
|
41
|
+
|
|
42
|
+
1. **Install Dependencies**
|
|
43
|
+
```bash
|
|
44
|
+
npm install
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
2. **Environment Configuration**
|
|
48
|
+
- Copy `.env` and configure your environment variables<% if (hasDatabase) { %>
|
|
49
|
+
- Update database connection settings
|
|
50
|
+
- Change JWT secrets for production<% } %>
|
|
51
|
+
|
|
52
|
+
<% if (hasDatabase) { %>3. **Database Setup**<% if (db === 'mongodb') { %>
|
|
53
|
+
- Ensure MongoDB is running locally or update MONGO_URI
|
|
54
|
+
- Database will be created automatically<% } else if (db === 'postgresql') { %>
|
|
55
|
+
- Ensure PostgreSQL is running locally
|
|
56
|
+
- Create a database matching your DATABASE_URL
|
|
57
|
+
- Run migrations if needed<% } %>
|
|
58
|
+
|
|
59
|
+
4.<% } else { %>3.<% } %> **Start Development Server**
|
|
60
|
+
```bash
|
|
61
|
+
npm run dev
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
<% if (hasDatabase) { %>5.<% } else { %>4.<% } %> **Production**
|
|
65
|
+
```bash
|
|
66
|
+
npm start
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Available Scripts
|
|
70
|
+
|
|
71
|
+
- `npm run dev` - Start development server with nodemon
|
|
72
|
+
- `npm start` - Start production server with clustering
|
|
73
|
+
- `npm test` - Run tests with coverage
|
|
74
|
+
- `npm run lint` - Run ESLint
|
|
75
|
+
- `npm run lint:fix` - Fix ESLint issues automatically
|
|
76
|
+
- `npm run format` - Format code with Prettier
|
|
77
|
+
- `npm run format:check` - Check code formatting
|
|
78
|
+
|
|
79
|
+
## API Documentation
|
|
80
|
+
|
|
81
|
+
Visit `http://localhost:3000/api-docs` for interactive Swagger documentation.
|
|
82
|
+
|
|
83
|
+
## API Endpoints
|
|
84
|
+
|
|
85
|
+
<% if (hasDatabase) { %>### Authentication
|
|
86
|
+
- `POST /api/auth/register` - Register new user
|
|
87
|
+
- `POST /api/auth/login` - User login
|
|
88
|
+
- `POST /api/auth/refresh` - Refresh access token
|
|
89
|
+
- `POST /api/auth/logout` - User logout
|
|
90
|
+
|
|
91
|
+
### Users
|
|
92
|
+
- `GET /api/users/profile` - Get user profile (protected)
|
|
93
|
+
- `PUT /api/users/profile` - Update user profile (protected)
|
|
94
|
+
- `DELETE /api/users/profile` - Delete user profile (protected)<% } else { %>### Examples
|
|
95
|
+
- `GET /api/examples` - Get all examples (with pagination)
|
|
96
|
+
- `GET /api/examples/:id` - Get example by ID
|
|
97
|
+
- `POST /api/examples` - Create new example
|
|
98
|
+
- `PUT /api/examples/:id` - Update example
|
|
99
|
+
- `DELETE /api/examples/:id` - Delete example<% } %>
|
|
100
|
+
|
|
101
|
+
### Health Check
|
|
102
|
+
- `GET /health` - Application health status
|
|
103
|
+
|
|
104
|
+
## Environment Variables
|
|
105
|
+
|
|
106
|
+
| Variable | Description | Default |
|
|
107
|
+
|----------|-------------|---------|
|
|
108
|
+
| NODE_ENV | Environment mode | development |
|
|
109
|
+
| PORT | Server port | 3000 |<% if (hasDatabase) { %>
|
|
110
|
+
| JWT_SECRET | JWT signing secret | - |
|
|
111
|
+
| JWT_REFRESH_SECRET | Refresh token secret | - |
|
|
112
|
+
| JWT_EXPIRE | Access token expiry | 15m |
|
|
113
|
+
| JWT_REFRESH_EXPIRE | Refresh token expiry | 7d |
|
|
114
|
+
| <%= db === 'mongodb' ? 'MONGO_URI' : 'DATABASE_URL' %> | Database connection | - |<% } %>
|
|
115
|
+
| RATE_LIMIT_WINDOW_MS | Rate limit window | 900000 |
|
|
116
|
+
| RATE_LIMIT_MAX | Max requests per window | 100 |
|
|
117
|
+
|
|
118
|
+
## Testing
|
|
119
|
+
|
|
120
|
+
```bash
|
|
121
|
+
# Run all tests
|
|
122
|
+
npm test
|
|
123
|
+
|
|
124
|
+
# Run tests with coverage
|
|
125
|
+
npm run test
|
|
126
|
+
|
|
127
|
+
# Run tests in watch mode
|
|
128
|
+
npm run test:watch
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
## Deployment
|
|
132
|
+
|
|
133
|
+
### Docker
|
|
134
|
+
|
|
135
|
+
1. **Build Image**
|
|
136
|
+
```bash
|
|
137
|
+
docker build -t <%= projectName %> .
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
2. **Run Container**
|
|
141
|
+
```bash
|
|
142
|
+
docker run -p 3000:3000 --env-file .env <%= projectName %>
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
3. **Docker Compose**
|
|
146
|
+
```bash
|
|
147
|
+
docker-compose up
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
## Security Features
|
|
151
|
+
|
|
152
|
+
- **Helmet**: Sets various HTTP headers for security
|
|
153
|
+
- **CORS**: Configurable cross-origin resource sharing
|
|
154
|
+
- **Rate Limiting**: Prevents abuse and DDoS attacks<% if (hasDatabase) { %>
|
|
155
|
+
- **JWT**: Stateless authentication with refresh tokens
|
|
156
|
+
- **Password Hashing**: bcrypt for secure password storage
|
|
157
|
+
- **Input Validation**: Request validation middleware<% } %>
|
|
158
|
+
|
|
159
|
+
<% if (!hasDatabase) { %>## Extending the Application
|
|
160
|
+
|
|
161
|
+
This boilerplate is designed for APIs that don't require database persistence. You can easily:
|
|
162
|
+
|
|
163
|
+
- Add database support by integrating MongoDB or PostgreSQL
|
|
164
|
+
- Replace in-memory storage with external services (Redis, etc.)
|
|
165
|
+
- Add authentication by integrating with OAuth providers
|
|
166
|
+
- Build microservices by adding service-to-service communication
|
|
167
|
+
|
|
168
|
+
<% } %>## Contributing
|
|
169
|
+
|
|
170
|
+
1. Fork the repository
|
|
171
|
+
2. Create a feature branch
|
|
172
|
+
3. Make your changes
|
|
173
|
+
4. Add tests for new functionality
|
|
174
|
+
5. Ensure all tests pass
|
|
175
|
+
6. Submit a pull request
|
|
176
|
+
|
|
177
|
+
## License
|
|
178
|
+
|
|
179
|
+
MIT License - see LICENSE file for details
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
const express = require('express');
|
|
2
|
+
const cors = require('cors');
|
|
3
|
+
const helmet = require('helmet');
|
|
4
|
+
const morgan = require('morgan');
|
|
5
|
+
const rateLimit = require('express-rate-limit');
|
|
6
|
+
const swaggerUi = require('swagger-ui-express');
|
|
7
|
+
const dotenv = require('dotenv');
|
|
8
|
+
|
|
9
|
+
const routes = require('./routes');
|
|
10
|
+
const errorHandler = require('./middleware/errorHandler');
|
|
11
|
+
const swaggerConfig = require('./config/swagger.json');
|
|
12
|
+
|
|
13
|
+
// Load environment variables
|
|
14
|
+
dotenv.config();
|
|
15
|
+
|
|
16
|
+
const app = express();
|
|
17
|
+
|
|
18
|
+
// Security middleware
|
|
19
|
+
app.use(helmet());
|
|
20
|
+
app.use(cors());
|
|
21
|
+
|
|
22
|
+
// Logging
|
|
23
|
+
app.use(morgan('dev'));
|
|
24
|
+
|
|
25
|
+
// Body parsing middleware
|
|
26
|
+
app.use(express.json({ limit: '10mb' }));
|
|
27
|
+
app.use(express.urlencoded({ extended: true }));
|
|
28
|
+
|
|
29
|
+
// Rate limiting
|
|
30
|
+
const limiter = rateLimit({
|
|
31
|
+
windowMs: 15 * 60 * 1000, // 15 minutes
|
|
32
|
+
max: 100, // Limit each IP to 100 requests per windowMs
|
|
33
|
+
message: 'Too many requests from this IP, please try again later.',
|
|
34
|
+
});
|
|
35
|
+
app.use(limiter);
|
|
36
|
+
|
|
37
|
+
// Health check endpoint
|
|
38
|
+
app.get('/health', (req, res) => {
|
|
39
|
+
res.status(200).json({
|
|
40
|
+
status: 'OK',
|
|
41
|
+
timestamp: new Date().toISOString(),
|
|
42
|
+
uptime: process.uptime(),<% if (hasDatabase) { %>
|
|
43
|
+
database: '<%= db %>'<% } else { %>
|
|
44
|
+
database: 'none'<% } %>,
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
// API documentation
|
|
49
|
+
app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerConfig));
|
|
50
|
+
|
|
51
|
+
// API routes
|
|
52
|
+
app.use('/api', routes);
|
|
53
|
+
|
|
54
|
+
// 404 handler
|
|
55
|
+
app.use('*', (req, res) => {
|
|
56
|
+
res.status(404).json({
|
|
57
|
+
error: 'Route not found',
|
|
58
|
+
path: req.originalUrl,
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
// Error handling middleware (should be last)
|
|
63
|
+
app.use(errorHandler);
|
|
64
|
+
|
|
65
|
+
module.exports = app;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# templates/core/docker-compose.yml.ejs
|
|
2
|
+
version: '3.8'
|
|
3
|
+
|
|
4
|
+
services:
|
|
5
|
+
app:
|
|
6
|
+
build: .
|
|
7
|
+
ports:
|
|
8
|
+
- "3000:3000"
|
|
9
|
+
environment:
|
|
10
|
+
- NODE_ENV=production
|
|
11
|
+
env_file:
|
|
12
|
+
- .env<% if (hasDatabase) { %>
|
|
13
|
+
depends_on:
|
|
14
|
+
- <%= db === 'mongodb' ? 'mongodb' : 'postgres' %><% } %>
|
|
15
|
+
restart: unless-stopped
|
|
16
|
+
networks:
|
|
17
|
+
- app-network
|
|
18
|
+
|
|
19
|
+
<% if (db === 'mongodb') { %> mongodb:
|
|
20
|
+
image: mongo:7-jammy
|
|
21
|
+
ports:
|
|
22
|
+
- "27017:27017"
|
|
23
|
+
environment:
|
|
24
|
+
- MONGO_INITDB_DATABASE=<%= projectName %>
|
|
25
|
+
volumes:
|
|
26
|
+
- mongodb_data:/data/db
|
|
27
|
+
restart: unless-stopped
|
|
28
|
+
networks:
|
|
29
|
+
- app-network<% } else if (db === 'postgresql') { %> postgres:
|
|
30
|
+
image: postgres:15-alpine
|
|
31
|
+
ports:
|
|
32
|
+
- "5432:5432"
|
|
33
|
+
environment:
|
|
34
|
+
- POSTGRES_DB=<%= projectName %>
|
|
35
|
+
- POSTGRES_USER=user
|
|
36
|
+
- POSTGRES_PASSWORD=password
|
|
37
|
+
volumes:
|
|
38
|
+
- postgres_data:/var/lib/postgresql/data
|
|
39
|
+
restart: unless-stopped
|
|
40
|
+
networks:
|
|
41
|
+
- app-network<% } %>
|
|
42
|
+
|
|
43
|
+
<% if (hasDatabase) { %>volumes:
|
|
44
|
+
<%= db === 'mongodb' ? 'mongodb_data:' : 'postgres_data:' %><% } %>
|
|
45
|
+
|
|
46
|
+
networks:
|
|
47
|
+
app-network:
|
|
48
|
+
driver: bridge
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# Server Configuration
|
|
2
|
+
NODE_ENV=development
|
|
3
|
+
PORT=3000<% if (hasDatabase) { %>
|
|
4
|
+
|
|
5
|
+
# JWT Configuration
|
|
6
|
+
JWT_SECRET=your-super-secret-jwt-key-change-this-in-production
|
|
7
|
+
JWT_REFRESH_SECRET=your-super-secret-refresh-key-change-this-in-production
|
|
8
|
+
JWT_EXPIRE=15m
|
|
9
|
+
JWT_REFRESH_EXPIRE=7d
|
|
10
|
+
|
|
11
|
+
# Database Configuration<% if (db === 'mongodb') { %>
|
|
12
|
+
MONGO_URI=mongodb://localhost:27017/<%= projectName %><% } else if (db === 'postgresql') { %>
|
|
13
|
+
DATABASE_URL=postgresql://user:password@localhost:5432/<%= projectName %><% } %><% } %>
|
|
14
|
+
|
|
15
|
+
# Rate Limiting
|
|
16
|
+
RATE_LIMIT_WINDOW_MS=900000
|
|
17
|
+
RATE_LIMIT_MAX=100
|
|
18
|
+
|
|
19
|
+
# Logging
|
|
20
|
+
LOG_LEVEL=info
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": ["airbnb-base"],
|
|
3
|
+
"env": {
|
|
4
|
+
"node": true,
|
|
5
|
+
"jest": true
|
|
6
|
+
},
|
|
7
|
+
"rules": {
|
|
8
|
+
"no-console": "off",
|
|
9
|
+
"consistent-return": "off",
|
|
10
|
+
"func-names": "off",
|
|
11
|
+
"object-shorthand": "off",
|
|
12
|
+
"no-process-exit": "off",
|
|
13
|
+
"no-param-reassign": "off",
|
|
14
|
+
"no-return-await": "off",
|
|
15
|
+
"no-underscore-dangle": "off",
|
|
16
|
+
"class-methods-use-this": "off",
|
|
17
|
+
"prefer-destructuring": ["error", { "object": true, "array": false }],
|
|
18
|
+
"no-unused-vars": ["error", { "argsIgnorePattern": "req|res|next|val" }]
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
// templates/core/gitignore.ejs
|
|
2
|
+
# Dependencies
|
|
3
|
+
node_modules/
|
|
4
|
+
npm-debug.log*
|
|
5
|
+
yarn-debug.log*
|
|
6
|
+
yarn-error.log*
|
|
7
|
+
|
|
8
|
+
# Environment variables
|
|
9
|
+
.env
|
|
10
|
+
.env.local
|
|
11
|
+
.env.development.local
|
|
12
|
+
.env.test.local
|
|
13
|
+
.env.production.local
|
|
14
|
+
|
|
15
|
+
# Build outputs
|
|
16
|
+
dist/
|
|
17
|
+
build/
|
|
18
|
+
|
|
19
|
+
# Logs
|
|
20
|
+
logs/
|
|
21
|
+
*.log
|
|
22
|
+
|
|
23
|
+
# Runtime data
|
|
24
|
+
pids/
|
|
25
|
+
*.pid
|
|
26
|
+
*.seed
|
|
27
|
+
*.pid.lock
|
|
28
|
+
|
|
29
|
+
# Coverage directory used by tools like istanbul
|
|
30
|
+
coverage/
|
|
31
|
+
*.lcov
|
|
32
|
+
|
|
33
|
+
# OS generated files
|
|
34
|
+
.DS_Store
|
|
35
|
+
.DS_Store?
|
|
36
|
+
._*
|
|
37
|
+
.Spotlight-V100
|
|
38
|
+
.Trashes
|
|
39
|
+
ehthumbs.db
|
|
40
|
+
Thumbs.db
|
|
41
|
+
|
|
42
|
+
# IDE files
|
|
43
|
+
.vscode/
|
|
44
|
+
.idea/
|
|
45
|
+
*.swp
|
|
46
|
+
*.swo
|
|
47
|
+
*~
|
|
48
|
+
|
|
49
|
+
# Temporary files
|
|
50
|
+
tmp/
|
|
51
|
+
temp/
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// templates/core/healthcheck.js.ejs
|
|
2
|
+
const http = require('http');
|
|
3
|
+
|
|
4
|
+
const options = {
|
|
5
|
+
host: 'localhost',
|
|
6
|
+
port: process.env.PORT || 3000,
|
|
7
|
+
path: '/health',
|
|
8
|
+
timeout: 2000,
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
const request = http.request(options, (res) => {
|
|
12
|
+
console.log(`STATUS: ${res.statusCode}`);
|
|
13
|
+
if (res.statusCode === 200) {
|
|
14
|
+
process.exit(0);
|
|
15
|
+
} else {
|
|
16
|
+
process.exit(1);
|
|
17
|
+
}
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
request.on('error', (err) => {
|
|
21
|
+
console.log('ERROR:', err);
|
|
22
|
+
process.exit(1);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
request.end();
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
const express = require('express');<% if (hasDatabase) { %>
|
|
2
|
+
const authRoutes = require('./authRoutes');
|
|
3
|
+
const userRoutes = require('./userRoutes');<% } else { %>
|
|
4
|
+
const exampleRoutes = require('./exampleRoutes');<% } %>
|
|
5
|
+
|
|
6
|
+
const router = express.Router();
|
|
7
|
+
|
|
8
|
+
// Mount route modules<% if (hasDatabase) { %>
|
|
9
|
+
router.use('/auth', authRoutes);
|
|
10
|
+
router.use('/users', userRoutes);<% } else { %>
|
|
11
|
+
router.use('/examples', exampleRoutes);<% } %>
|
|
12
|
+
|
|
13
|
+
// API root endpoint
|
|
14
|
+
router.get('/', (req, res) => {
|
|
15
|
+
res.json({
|
|
16
|
+
message: 'Welcome to the API',
|
|
17
|
+
version: '1.0.0',
|
|
18
|
+
documentation: '/api-docs',<% if (hasDatabase) { %>
|
|
19
|
+
features: ['authentication', 'user-management', '<%= db %>-database']<% } else { %>
|
|
20
|
+
features: ['rate-limiting', 'logging', 'swagger-docs']<% } %>,
|
|
21
|
+
});
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
module.exports = router;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
|
|
2
|
+
// templates/core/jest.config.js.ejs
|
|
3
|
+
module.exports = {
|
|
4
|
+
testEnvironment: 'node',
|
|
5
|
+
roots: ['<rootDir>/src', '<rootDir>/tests'],
|
|
6
|
+
testMatch: [
|
|
7
|
+
'**/__tests__/**/*.js',
|
|
8
|
+
'**/?(*.)+(spec|test).js'
|
|
9
|
+
],
|
|
10
|
+
collectCoverageFrom: [
|
|
11
|
+
'src/**/*.js',
|
|
12
|
+
'!src/server.js',
|
|
13
|
+
'!src/config/**',
|
|
14
|
+
],
|
|
15
|
+
coverageDirectory: 'coverage',
|
|
16
|
+
coverageReporters: [
|
|
17
|
+
'text',
|
|
18
|
+
'lcov',
|
|
19
|
+
'html'
|
|
20
|
+
],
|
|
21
|
+
setupFilesAfterEnv: ['<rootDir>/tests/setup.js'],
|
|
22
|
+
testTimeout: 10000,
|
|
23
|
+
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "<%= projectName %>",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"main": "src/server.js",
|
|
5
|
+
"scripts": {
|
|
6
|
+
"start": "node src/server.js",
|
|
7
|
+
"dev": "nodemon src/server.js",
|
|
8
|
+
"test": "jest --coverage",
|
|
9
|
+
"lint": "eslint ."
|
|
10
|
+
},
|
|
11
|
+
"dependencies": {
|
|
12
|
+
"express": "^4.18.2",
|
|
13
|
+
"express-rate-limit": "^7.1.5",
|
|
14
|
+
"dotenv": "^16.3.1",
|
|
15
|
+
"cors": "^2.8.5",
|
|
16
|
+
"helmet": "^7.1.0",
|
|
17
|
+
"morgan": "^1.10.0",
|
|
18
|
+
"swagger-ui-express": "^5.0.1"<% if (hasDatabase) { %>,
|
|
19
|
+
"jsonwebtoken": "^9.0.2",
|
|
20
|
+
"bcryptjs": "^2.4.3"<% if (db === 'mongodb') { %>,
|
|
21
|
+
"mongoose": "^8.0.3"<% } else if (db === 'postgresql') { %>,
|
|
22
|
+
"pg": "^8.11.3",
|
|
23
|
+
"sequelize": "^6.35.1"<% } %><% } %>
|
|
24
|
+
},
|
|
25
|
+
"devDependencies": {
|
|
26
|
+
"nodemon": "^3.0.3",
|
|
27
|
+
"jest": "^29.7.0",
|
|
28
|
+
"supertest": "^6.3.4",
|
|
29
|
+
"eslint": "^8.56.0",
|
|
30
|
+
"eslint-config-airbnb-base": "^15.0.0",
|
|
31
|
+
"eslint-plugin-import": "^2.29.1"
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
const cluster = require('cluster');
|
|
2
|
+
const os = require('os');
|
|
3
|
+
const app = require('./app');<% if (hasDatabase) { %>
|
|
4
|
+
const db = require('./config/database');<% } %>
|
|
5
|
+
|
|
6
|
+
const port = process.env.PORT || 3000;
|
|
7
|
+
|
|
8
|
+
const startServer = async () => {<% if (hasDatabase) { %>
|
|
9
|
+
await db.connect();<% } %>
|
|
10
|
+
|
|
11
|
+
if (cluster.isMaster) {
|
|
12
|
+
const numCPUs = os.cpus().length;
|
|
13
|
+
console.log(`Master ${process.pid} is running`);
|
|
14
|
+
|
|
15
|
+
// Fork workers
|
|
16
|
+
for (let i = 0; i < numCPUs; i++) {
|
|
17
|
+
cluster.fork();
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
cluster.on('exit', (worker, code, signal) => {
|
|
21
|
+
console.log(`Worker ${worker.process.pid} died with code ${code} and signal ${signal}`);
|
|
22
|
+
cluster.fork();
|
|
23
|
+
});
|
|
24
|
+
} else {
|
|
25
|
+
app.listen(port, () => {
|
|
26
|
+
console.log(`Worker ${process.pid} running on http://localhost:${port}`);
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
startServer().catch(console.error);
|
|
32
|
+
|
|
33
|
+
// Graceful Shutdown
|
|
34
|
+
process.on('SIGTERM', async () => {
|
|
35
|
+
console.log('SIGTERM signal received: closing HTTP server');<% if (hasDatabase) { %>
|
|
36
|
+
await db.disconnect();<% } %>
|
|
37
|
+
process.exit(0);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
process.on('SIGINT', async () => {
|
|
41
|
+
console.log('SIGINT signal received: closing HTTP server');<% if (hasDatabase) { %>
|
|
42
|
+
await db.disconnect();<% } %>
|
|
43
|
+
process.exit(0);
|
|
44
|
+
});
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
const jwt = require('jsonwebtoken');
|
|
2
|
+
const { AppError } = require('../utils/errors');
|
|
3
|
+
|
|
4
|
+
const jwtSecret = process.env.JWT_SECRET || 'your-secret-key';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Middleware to authenticate JWT tokens
|
|
8
|
+
*/
|
|
9
|
+
const authenticateToken = (req, res, next) => {
|
|
10
|
+
const authHeader = req.headers['authorization'];
|
|
11
|
+
const token = authHeader && authHeader.split(' ')[1];
|
|
12
|
+
|
|
13
|
+
if (!token) {
|
|
14
|
+
return next(new AppError('Access token is required', 401));
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
jwt.verify(token, jwtSecret, (err, decoded) => {
|
|
18
|
+
if (err) {
|
|
19
|
+
return next(new AppError('Invalid or expired token', 403));
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
req.user = decoded;
|
|
23
|
+
next();
|
|
24
|
+
});
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Optional authentication middleware - doesn't require token
|
|
29
|
+
*/
|
|
30
|
+
const optionalAuth = (req, res, next) => {
|
|
31
|
+
const authHeader = req.headers['authorization'];
|
|
32
|
+
const token = authHeader && authHeader.split(' ')[1];
|
|
33
|
+
|
|
34
|
+
if (token) {
|
|
35
|
+
jwt.verify(token, jwtSecret, (err, decoded) => {
|
|
36
|
+
if (!err) {
|
|
37
|
+
req.user = decoded;
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
next();
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Middleware to check if user has specific role
|
|
47
|
+
*/
|
|
48
|
+
const requireRole = (role) => {
|
|
49
|
+
return (req, res, next) => {
|
|
50
|
+
if (!req.user) {
|
|
51
|
+
return next(new AppError('Authentication required', 401));
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (req.user.role !== role) {
|
|
55
|
+
return next(new AppError('Insufficient permissions', 403));
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
next();
|
|
59
|
+
};
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
module.exports = {
|
|
63
|
+
authenticateToken,
|
|
64
|
+
optionalAuth,
|
|
65
|
+
requireRole,
|
|
66
|
+
};
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
|
|
2
|
+
// templates/middleware/errorHandler.js.ejs
|
|
3
|
+
const { AppError } = require('../utils/errors');
|
|
4
|
+
|
|
5
|
+
const errorHandler = (err, req, res, next) => {
|
|
6
|
+
let error = { ...err };
|
|
7
|
+
error.message = err.message;
|
|
8
|
+
|
|
9
|
+
console.error(err);
|
|
10
|
+
|
|
11
|
+
<% if (db === 'mongodb') { %> // Mongoose bad ObjectId
|
|
12
|
+
if (err.name === 'CastError') {
|
|
13
|
+
const message = 'Resource not found';
|
|
14
|
+
error = new AppError(message, 404);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// Mongoose duplicate key
|
|
18
|
+
if (err.code === 11000) {
|
|
19
|
+
const message = 'Duplicate field value entered';
|
|
20
|
+
error = new AppError(message, 400);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Mongoose validation error
|
|
24
|
+
if (err.name === 'ValidationError') {
|
|
25
|
+
const message = Object.values(err.errors).map(val => val.message).join(', ');
|
|
26
|
+
error = new AppError(message, 400);
|
|
27
|
+
}<% } %>
|
|
28
|
+
|
|
29
|
+
<% if (hasDatabase) { %> // JWT errors
|
|
30
|
+
if (err.name === 'JsonWebTokenError') {
|
|
31
|
+
const message = 'Invalid token';
|
|
32
|
+
error = new AppError(message, 401);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (err.name === 'TokenExpiredError') {
|
|
36
|
+
const message = 'Token expired';
|
|
37
|
+
error = new AppError(message, 401);
|
|
38
|
+
}<% } %>
|
|
39
|
+
|
|
40
|
+
res.status(error.statusCode || 500).json({
|
|
41
|
+
success: false,
|
|
42
|
+
error: error.message || 'Server Error',
|
|
43
|
+
...(process.env.NODE_ENV === 'development' && { stack: err.stack }),
|
|
44
|
+
});
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
module.exports = errorHandler;
|