nodejs-quickstart-structure 1.3.2 → 1.3.9
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/bin/index.js +1 -1
- package/lib/generator.js +30 -4
- package/package.json +39 -39
- package/templates/clean-architecture/js/src/index.js.ejs +3 -3
- package/templates/clean-architecture/js/src/infrastructure/log/logger.js +5 -5
- package/templates/clean-architecture/js/src/infrastructure/webserver/server.js.ejs +2 -1
- package/templates/clean-architecture/js/src/interfaces/controllers/UserController.js +6 -1
- package/templates/clean-architecture/ts/src/index.ts.ejs +8 -8
- package/templates/clean-architecture/ts/src/infrastructure/log/logger.ts +5 -5
- package/templates/clean-architecture/ts/src/infrastructure/repositories/{UserRepository.ts.ejs → userRepository.ts.ejs} +2 -2
- package/templates/clean-architecture/ts/src/interfaces/controllers/userController.ts +7 -6
- package/templates/clean-architecture/ts/src/interfaces/routes/userRoutes.ts +1 -1
- package/templates/clean-architecture/ts/src/usecases/createUser.ts +2 -2
- package/templates/clean-architecture/ts/src/usecases/getAllUsers.ts +1 -1
- package/templates/common/Dockerfile +1 -0
- package/templates/common/database/ts/models/User.ts.ejs +1 -1
- package/templates/common/jest.config.js.ejs +4 -1
- package/templates/common/kafka/js/services/{kafkaService.js → kafkaService.js.ejs} +2 -1
- package/templates/common/kafka/ts/services/{kafkaService.ts → kafkaService.ts.ejs} +3 -2
- package/templates/common/package.json.ejs +4 -2
- package/templates/common/public/css/style.css +147 -0
- package/templates/common/tsconfig.json +5 -1
- package/templates/common/views/ejs/index.ejs +44 -20
- package/templates/common/views/pug/index.pug +32 -18
- package/templates/mvc/js/src/controllers/userController.js.ejs +3 -1
- package/templates/mvc/js/src/index.js.ejs +1 -1
- package/templates/mvc/js/src/utils/logger.js +5 -5
- package/templates/mvc/ts/src/controllers/userController.ts.ejs +5 -2
- package/templates/mvc/ts/src/index.ts.ejs +9 -9
- package/templates/mvc/ts/src/routes/api.ts +1 -1
- package/templates/mvc/ts/src/utils/logger.ts +5 -5
- package/templates/mvc/js/src/controllers/userController.js +0 -14
package/bin/index.js
CHANGED
|
@@ -50,7 +50,7 @@ program
|
|
|
50
50
|
await generateProject(answers);
|
|
51
51
|
|
|
52
52
|
console.log(chalk.green('\n✔ Project generated successfully!'));
|
|
53
|
-
console.log(chalk.cyan(`\nNext steps:\n cd ${answers.projectName}\n npm install\n docker-compose up\n-----------------------\nStart the app manually:\n npm install\n npm run dev`));
|
|
53
|
+
console.log(chalk.cyan(`\nNext steps:\n cd ${answers.projectName}\n npm install\n docker-compose up\n-----------------------\nStart the app manually:\n cd ${answers.projectName}\n npm install\n npm run dev`));
|
|
54
54
|
|
|
55
55
|
} catch (error) {
|
|
56
56
|
console.error(chalk.red('Error generating project:'), error);
|
package/lib/generator.js
CHANGED
|
@@ -126,6 +126,24 @@ export const generateProject = async (config) => {
|
|
|
126
126
|
const kafkaSource = path.join(templatesDir, 'common', 'kafka', langExt);
|
|
127
127
|
await fs.copy(kafkaSource, path.join(targetDir, 'src'));
|
|
128
128
|
|
|
129
|
+
// Render Kafka Service with dynamic logger path
|
|
130
|
+
const kafkaServiceFileName = `kafkaService.${langExt}`;
|
|
131
|
+
const kafkaServiceTemplate = path.join(targetDir, 'src', 'services', `${kafkaServiceFileName}.ejs`);
|
|
132
|
+
|
|
133
|
+
if (await fs.pathExists(kafkaServiceTemplate)) {
|
|
134
|
+
let loggerPath = architecture === 'Clean Architecture' ? '../log/logger' : '../utils/logger';
|
|
135
|
+
let configPath = '../config/kafka';
|
|
136
|
+
|
|
137
|
+
if (language === 'TypeScript') {
|
|
138
|
+
loggerPath = architecture === 'Clean Architecture' ? '@/infrastructure/log/logger' : '@/utils/logger';
|
|
139
|
+
configPath = architecture === 'Clean Architecture' ? '@/infrastructure/config/kafka' : '@/config/kafka';
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const content = ejs.render(await fs.readFile(kafkaServiceTemplate, 'utf-8'), { loggerPath, configPath });
|
|
143
|
+
await fs.writeFile(path.join(targetDir, 'src', 'services', kafkaServiceFileName), content);
|
|
144
|
+
await fs.remove(kafkaServiceTemplate);
|
|
145
|
+
}
|
|
146
|
+
|
|
129
147
|
if (architecture === 'Clean Architecture') {
|
|
130
148
|
// Clean Architecture Restructuring
|
|
131
149
|
await fs.ensureDir(path.join(targetDir, 'src/infrastructure/messaging'));
|
|
@@ -234,14 +252,20 @@ export const generateProject = async (config) => {
|
|
|
234
252
|
|
|
235
253
|
// 8. View Engine (MVC)
|
|
236
254
|
if (architecture === 'MVC' && viewEngine && viewEngine !== 'None') {
|
|
255
|
+
const publicDir = path.join(templatesDir, 'common', 'public');
|
|
256
|
+
if (await fs.pathExists(publicDir)) {
|
|
257
|
+
await fs.copy(publicDir, path.join(targetDir, 'public'));
|
|
258
|
+
}
|
|
237
259
|
}
|
|
238
260
|
|
|
239
261
|
// 9. Render Swagger Config (if .ejs exists)
|
|
240
262
|
// MVC TS
|
|
241
263
|
const swaggerMvcTs = path.join(targetDir, 'src', 'config', 'swagger.ts.ejs');
|
|
242
264
|
if (await fs.pathExists(swaggerMvcTs)) {
|
|
243
|
-
|
|
244
|
-
|
|
265
|
+
if (communication === 'REST APIs') {
|
|
266
|
+
const content = ejs.render(await fs.readFile(swaggerMvcTs, 'utf-8'), { communication });
|
|
267
|
+
await fs.writeFile(path.join(targetDir, 'src', 'config', 'swagger.ts'), content);
|
|
268
|
+
}
|
|
245
269
|
await fs.remove(swaggerMvcTs);
|
|
246
270
|
}
|
|
247
271
|
// Clean Architecture TS
|
|
@@ -249,8 +273,10 @@ export const generateProject = async (config) => {
|
|
|
249
273
|
// Note: In Clean Arch, it might be in src/infrastructure/webserver or src/config depending on refactor.
|
|
250
274
|
// Based on previous moves, we saw it in src/config for TS.
|
|
251
275
|
if (await fs.pathExists(swaggerCleanTs)) {
|
|
252
|
-
|
|
253
|
-
|
|
276
|
+
if (communication === 'REST APIs') {
|
|
277
|
+
const content = ejs.render(await fs.readFile(swaggerCleanTs, 'utf-8'), { communication });
|
|
278
|
+
await fs.writeFile(path.join(targetDir, 'src', 'config', 'swagger.ts'), content);
|
|
279
|
+
}
|
|
254
280
|
await fs.remove(swaggerCleanTs);
|
|
255
281
|
}
|
|
256
282
|
|
package/package.json
CHANGED
|
@@ -1,40 +1,40 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "nodejs-quickstart-structure",
|
|
3
|
-
"version": "1.3.
|
|
4
|
-
"type": "module",
|
|
5
|
-
"description": "A CLI to scaffold Node.js microservices with MVC or Clean Architecture",
|
|
6
|
-
"main": "bin/index.js",
|
|
7
|
-
"bin": {
|
|
8
|
-
"nodejs-quickstart": "./bin/index.js"
|
|
9
|
-
},
|
|
10
|
-
"scripts": {
|
|
11
|
-
"test": "echo \"Error: no test specified\" && exit 1",
|
|
12
|
-
"test:e2e": "npm run test:e2e:windows",
|
|
13
|
-
"test:e2e:windows": "node scripts/validate-windows.js",
|
|
14
|
-
"test:e2e:linux": "node scripts/validate-linux.js"
|
|
15
|
-
},
|
|
16
|
-
"keywords": [
|
|
17
|
-
"nodejs",
|
|
18
|
-
"cli",
|
|
19
|
-
"scaffold",
|
|
20
|
-
"mvc",
|
|
21
|
-
"clean-architecture"
|
|
22
|
-
],
|
|
23
|
-
"author": "Pau Dang <phucdangb1400718@gmail.com>",
|
|
24
|
-
"repository": {
|
|
25
|
-
"type": "git",
|
|
26
|
-
"url": "git+https://github.com/paudang/nodejs-quickstart-structure.git"
|
|
27
|
-
},
|
|
28
|
-
"bugs": {
|
|
29
|
-
"url": "https://github.com/paudang/nodejs-quickstart-structure/issues"
|
|
30
|
-
},
|
|
31
|
-
"homepage": "https://github.com/paudang/nodejs-quickstart-structure#readme",
|
|
32
|
-
"license": "ISC",
|
|
33
|
-
"dependencies": {
|
|
34
|
-
"chalk": "^5.4.1",
|
|
35
|
-
"commander": "^13.1.0",
|
|
36
|
-
"ejs": "^3.1.10",
|
|
37
|
-
"fs-extra": "^11.3.0",
|
|
38
|
-
"inquirer": "^12.4.1"
|
|
39
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "nodejs-quickstart-structure",
|
|
3
|
+
"version": "1.3.9",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "A CLI to scaffold Node.js microservices with MVC or Clean Architecture",
|
|
6
|
+
"main": "bin/index.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"nodejs-quickstart": "./bin/index.js"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"test": "echo \"Error: no test specified\" && exit 1",
|
|
12
|
+
"test:e2e": "npm run test:e2e:windows",
|
|
13
|
+
"test:e2e:windows": "node scripts/validate-windows.js",
|
|
14
|
+
"test:e2e:linux": "node scripts/validate-linux.js"
|
|
15
|
+
},
|
|
16
|
+
"keywords": [
|
|
17
|
+
"nodejs",
|
|
18
|
+
"cli",
|
|
19
|
+
"scaffold",
|
|
20
|
+
"mvc",
|
|
21
|
+
"clean-architecture"
|
|
22
|
+
],
|
|
23
|
+
"author": "Pau Dang <phucdangb1400718@gmail.com>",
|
|
24
|
+
"repository": {
|
|
25
|
+
"type": "git",
|
|
26
|
+
"url": "git+https://github.com/paudang/nodejs-quickstart-structure.git"
|
|
27
|
+
},
|
|
28
|
+
"bugs": {
|
|
29
|
+
"url": "https://github.com/paudang/nodejs-quickstart-structure/issues"
|
|
30
|
+
},
|
|
31
|
+
"homepage": "https://github.com/paudang/nodejs-quickstart-structure#readme",
|
|
32
|
+
"license": "ISC",
|
|
33
|
+
"dependencies": {
|
|
34
|
+
"chalk": "^5.4.1",
|
|
35
|
+
"commander": "^13.1.0",
|
|
36
|
+
"ejs": "^3.1.10",
|
|
37
|
+
"fs-extra": "^11.3.0",
|
|
38
|
+
"inquirer": "^12.4.1"
|
|
39
|
+
}
|
|
40
40
|
}
|
|
@@ -26,10 +26,10 @@ const syncDatabase = async () => {
|
|
|
26
26
|
});
|
|
27
27
|
<% } -%>
|
|
28
28
|
break;
|
|
29
|
-
} catch (
|
|
30
|
-
logger.error('
|
|
29
|
+
} catch (error) {
|
|
30
|
+
logger.error('Error syncing database:', error);
|
|
31
31
|
retries -= 1;
|
|
32
|
-
logger.info(`Retries left: ${retries}
|
|
32
|
+
logger.info(`Retries left: ${retries}`);
|
|
33
33
|
await new Promise(res => setTimeout(res, 5000));
|
|
34
34
|
}
|
|
35
35
|
}
|
|
@@ -13,10 +13,10 @@ const logger = winston.createLogger({
|
|
|
13
13
|
],
|
|
14
14
|
});
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
}
|
|
16
|
+
logger.add(new winston.transports.Console({
|
|
17
|
+
format: process.env.NODE_ENV !== 'production'
|
|
18
|
+
? winston.format.simple()
|
|
19
|
+
: winston.format.json(),
|
|
20
|
+
}));
|
|
21
21
|
|
|
22
22
|
module.exports = logger;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
const express = require('express');
|
|
2
2
|
const cors = require('cors');
|
|
3
3
|
require('dotenv').config();
|
|
4
|
+
const logger = require('../log/logger');
|
|
4
5
|
<% if (communication === 'REST APIs') { %>const apiRoutes = require('../../interfaces/routes/api');<% } -%>
|
|
5
6
|
<% if (communication === 'REST APIs') { -%>
|
|
6
7
|
const swaggerUi = require('swagger-ui-express');
|
|
@@ -26,7 +27,7 @@ const startServer = (port) => {
|
|
|
26
27
|
});
|
|
27
28
|
|
|
28
29
|
app.listen(port, () => {
|
|
29
|
-
|
|
30
|
+
logger.info(`Server running on port ${port}`);
|
|
30
31
|
});
|
|
31
32
|
};
|
|
32
33
|
|
|
@@ -2,6 +2,7 @@ const CreateUser = require('../../usecases/CreateUser');
|
|
|
2
2
|
const GetAllUsers = require('../../usecases/GetAllUsers');
|
|
3
3
|
const UserRepository = require('../../infrastructure/repositories/UserRepository');
|
|
4
4
|
const HTTP_STATUS = require('../../utils/httpCodes');
|
|
5
|
+
const logger = require('../../infrastructure/log/logger');
|
|
5
6
|
|
|
6
7
|
class UserController {
|
|
7
8
|
constructor() {
|
|
@@ -13,7 +14,10 @@ class UserController {
|
|
|
13
14
|
getUsers(req, res) {
|
|
14
15
|
this.getAllUsersUseCase.execute()
|
|
15
16
|
.then(users => res.json(users))
|
|
16
|
-
.catch(err =>
|
|
17
|
+
.catch(err => {
|
|
18
|
+
logger.error('Error getting users:', err);
|
|
19
|
+
res.status(HTTP_STATUS.INTERNAL_SERVER_ERROR).json({ error: err.message });
|
|
20
|
+
});
|
|
17
21
|
}
|
|
18
22
|
|
|
19
23
|
async createUser(req, res) {
|
|
@@ -22,6 +26,7 @@ class UserController {
|
|
|
22
26
|
const user = await this.createUserUseCase.execute(name, email);
|
|
23
27
|
res.status(HTTP_STATUS.CREATED).json(user);
|
|
24
28
|
} catch (error) {
|
|
29
|
+
logger.error('Error creating user:', error);
|
|
25
30
|
res.status(HTTP_STATUS.BAD_REQUEST).json({ error: error.message });
|
|
26
31
|
}
|
|
27
32
|
}
|
|
@@ -4,12 +4,12 @@ import helmet from 'helmet';
|
|
|
4
4
|
import hpp from 'hpp';
|
|
5
5
|
import rateLimit from 'express-rate-limit';
|
|
6
6
|
import dotenv from 'dotenv';
|
|
7
|
-
import logger from '
|
|
8
|
-
<% if (communication === 'REST APIs') { %>import userRoutes from '
|
|
7
|
+
import logger from '@/infrastructure/log/logger';
|
|
8
|
+
<% if (communication === 'REST APIs') { %>import userRoutes from '@/interfaces/routes/userRoutes';<% } -%>
|
|
9
9
|
<% if (communication === 'REST APIs') { -%>
|
|
10
10
|
import swaggerUi from 'swagger-ui-express';
|
|
11
|
-
import swaggerSpecs from '
|
|
12
|
-
<% if (communication === 'Kafka') { %>import { KafkaService } from '
|
|
11
|
+
import swaggerSpecs from '@/config/swagger';<% } -%>
|
|
12
|
+
<% if (communication === 'Kafka') { %>import { KafkaService } from '@/infrastructure/messaging/kafkaClient';<% } -%>
|
|
13
13
|
|
|
14
14
|
dotenv.config();
|
|
15
15
|
|
|
@@ -42,7 +42,7 @@ const syncDatabase = async () => {
|
|
|
42
42
|
let retries = 30;
|
|
43
43
|
while (retries) {
|
|
44
44
|
try {
|
|
45
|
-
const sequelize = (await import('
|
|
45
|
+
const sequelize = (await import('@/infrastructure/database/database')).default;
|
|
46
46
|
await sequelize.sync();
|
|
47
47
|
logger.info('Database synced');
|
|
48
48
|
|
|
@@ -61,10 +61,10 @@ const syncDatabase = async () => {
|
|
|
61
61
|
<%_ } -%>
|
|
62
62
|
});
|
|
63
63
|
break;
|
|
64
|
-
} catch (
|
|
65
|
-
logger.error('
|
|
64
|
+
} catch (error) {
|
|
65
|
+
logger.error('Error syncing database:', error);
|
|
66
66
|
retries -= 1;
|
|
67
|
-
logger.info(`Retries left: ${retries}
|
|
67
|
+
logger.info(`Retries left: ${retries}`);
|
|
68
68
|
await new Promise(res => setTimeout(res, 5000));
|
|
69
69
|
}
|
|
70
70
|
}
|
|
@@ -13,10 +13,10 @@ const logger = winston.createLogger({
|
|
|
13
13
|
],
|
|
14
14
|
});
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
}
|
|
16
|
+
logger.add(new winston.transports.Console({
|
|
17
|
+
format: process.env.NODE_ENV !== 'production'
|
|
18
|
+
? winston.format.simple()
|
|
19
|
+
: winston.format.json(),
|
|
20
|
+
}));
|
|
21
21
|
|
|
22
22
|
export default logger;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { User as UserEntity } from '
|
|
2
|
-
import UserModel from '
|
|
1
|
+
import { User as UserEntity } from '@/domain/user';
|
|
2
|
+
import UserModel from '@/infrastructure/database/models/User';
|
|
3
3
|
|
|
4
4
|
export class UserRepository {
|
|
5
5
|
async save(user: UserEntity): Promise<UserEntity> {
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { Request, Response } from 'express';
|
|
2
|
-
import { UserRepository } from '
|
|
3
|
-
import CreateUser from '
|
|
4
|
-
import GetAllUsers from '
|
|
5
|
-
import { HTTP_STATUS } from '
|
|
2
|
+
import { UserRepository } from '@/infrastructure/repositories/UserRepository';
|
|
3
|
+
import CreateUser from '@/usecases/createUser';
|
|
4
|
+
import GetAllUsers from '@/usecases/getAllUsers';
|
|
5
|
+
import { HTTP_STATUS } from '@/utils/httpCodes';
|
|
6
|
+
import logger from '@/infrastructure/log/logger';
|
|
6
7
|
|
|
7
8
|
export class UserController {
|
|
8
9
|
private createUserUseCase: CreateUser;
|
|
@@ -20,7 +21,7 @@ export class UserController {
|
|
|
20
21
|
const user = await this.createUserUseCase.execute(name, email);
|
|
21
22
|
res.status(HTTP_STATUS.CREATED).json(user);
|
|
22
23
|
} catch (error) {
|
|
23
|
-
|
|
24
|
+
logger.error('UserController Error:', error);
|
|
24
25
|
if (error instanceof Error) {
|
|
25
26
|
res.status(HTTP_STATUS.INTERNAL_SERVER_ERROR).json({ error: error.message });
|
|
26
27
|
} else {
|
|
@@ -34,7 +35,7 @@ export class UserController {
|
|
|
34
35
|
const users = await this.getAllUsersUseCase.execute();
|
|
35
36
|
res.json(users);
|
|
36
37
|
} catch (error) {
|
|
37
|
-
|
|
38
|
+
logger.error('UserController Error:', error);
|
|
38
39
|
if (error instanceof Error) {
|
|
39
40
|
res.status(HTTP_STATUS.INTERNAL_SERVER_ERROR).json({ error: error.message });
|
|
40
41
|
} else {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Router, Request, Response } from 'express';
|
|
2
|
-
import { UserController } from '
|
|
2
|
+
import { UserController } from '@/interfaces/controllers/userController';
|
|
3
3
|
|
|
4
4
|
const router = Router();
|
|
5
5
|
const userController = new UserController();
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { User } from '
|
|
1
|
+
import { User } from '@/domain/user';
|
|
2
2
|
|
|
3
|
-
import { UserRepository } from '
|
|
3
|
+
import { UserRepository } from '@/infrastructure/repositories/UserRepository';
|
|
4
4
|
|
|
5
5
|
export default class CreateUser {
|
|
6
6
|
constructor(private userRepository: UserRepository) {}
|
|
@@ -40,6 +40,7 @@ COPY --from=builder /app/src ./src
|
|
|
40
40
|
# Copy other necessary files (like views if MVC)
|
|
41
41
|
<% if (viewEngine && viewEngine !== 'None') { %>
|
|
42
42
|
COPY --from=builder /app/src/views ./dist/views
|
|
43
|
+
<% if (viewEngine && viewEngine !== 'None') { %>COPY --from=builder /app/public ./public<% } %>
|
|
43
44
|
<% } %>
|
|
44
45
|
|
|
45
46
|
EXPOSE 3000
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { DataTypes, Model } from 'sequelize';
|
|
2
|
-
<% if (architecture === 'MVC') { %>import sequelize from '
|
|
2
|
+
<% if (architecture === 'MVC') { %>import sequelize from '@/config/database';<% } else { %>import sequelize from '@/infrastructure/database/database';<% } %>
|
|
3
3
|
|
|
4
4
|
class User extends Model {
|
|
5
5
|
public id!: number;
|
|
@@ -3,7 +3,10 @@ module.exports = {
|
|
|
3
3
|
coverageDirectory: 'coverage',
|
|
4
4
|
collectCoverageFrom: ['src/**/*.{js,ts}'],
|
|
5
5
|
testMatch: ['**/*.test.ts', '**/*.test.js'],
|
|
6
|
-
<% if (language === 'TypeScript') { %>preset: 'ts-jest'
|
|
6
|
+
<% if (language === 'TypeScript') { %>preset: 'ts-jest',
|
|
7
|
+
moduleNameMapper: {
|
|
8
|
+
'^@/(.*)$': '<rootDir>/src/$1',
|
|
9
|
+
},<% } %>
|
|
7
10
|
coveragePathIgnorePatterns: [
|
|
8
11
|
"/node_modules/",
|
|
9
12
|
"/dist/"
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
const { kafka } = require('../config/kafka');
|
|
2
|
+
const logger = require('<%= loggerPath %>');
|
|
2
3
|
|
|
3
4
|
const producer = kafka.producer();
|
|
4
5
|
const consumer = kafka.consumer({ groupId: 'test-group' });
|
|
@@ -10,7 +11,7 @@ const connectKafka = async () => {
|
|
|
10
11
|
|
|
11
12
|
await consumer.run({
|
|
12
13
|
eachMessage: async ({ topic, partition, message }) => {
|
|
13
|
-
|
|
14
|
+
logger.info({
|
|
14
15
|
value: message.value.toString(),
|
|
15
16
|
});
|
|
16
17
|
},
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { kafka } from '
|
|
1
|
+
import { kafka } from '<%= configPath %>';
|
|
2
2
|
import { EachMessagePayload, Producer, Consumer } from 'kafkajs';
|
|
3
|
+
import logger from '<%= loggerPath %>';
|
|
3
4
|
|
|
4
5
|
export class KafkaService {
|
|
5
6
|
private producer: Producer;
|
|
@@ -17,7 +18,7 @@ export class KafkaService {
|
|
|
17
18
|
|
|
18
19
|
await this.consumer.run({
|
|
19
20
|
eachMessage: async ({ topic, partition, message }: EachMessagePayload) => {
|
|
20
|
-
|
|
21
|
+
logger.info({
|
|
21
22
|
value: message.value?.toString(),
|
|
22
23
|
});
|
|
23
24
|
},
|
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
"main": "<% if (language === 'TypeScript') { %>dist/index.js<% } else { %>src/index.js<% } %>",
|
|
6
6
|
"scripts": {
|
|
7
7
|
"start": "<% if (language === 'TypeScript') { %>node dist/index.js<% } else { %>node src/index.js<% } %>",
|
|
8
|
-
"dev": "<% if (language === 'TypeScript') { %>nodemon --exec ts-node src/index.ts<% } else { %>nodemon src/index.js<% } %>"<% if (language === 'TypeScript') { %>,
|
|
9
|
-
"build": "rimraf dist && tsc<% if (viewEngine && viewEngine !== 'None') { %> && copyfiles -u 1 \"src/views/**/*\" dist/<% } %>"<% } %>,
|
|
8
|
+
"dev": "<% if (language === 'TypeScript') { %>nodemon --exec ts-node -r tsconfig-paths/register src/index.ts<% } else { %>nodemon src/index.js<% } %>"<% if (language === 'TypeScript') { %>,
|
|
9
|
+
"build": "rimraf dist && tsc && tsc-alias<% if (viewEngine && viewEngine !== 'None') { %> && copyfiles -u 1 \"src/views/**/*\" dist/<% } %>"<% } %>,
|
|
10
10
|
"lint": "eslint . --ext .ts,.js",
|
|
11
11
|
"lint:fix": "eslint . --ext .ts,.js --fix",
|
|
12
12
|
"format": "prettier --write .",
|
|
@@ -65,6 +65,8 @@
|
|
|
65
65
|
"ts-jest": "^29.1.1",
|
|
66
66
|
"@types/jest": "^29.5.11",
|
|
67
67
|
"supertest": "^6.3.3",
|
|
68
|
+
"tsconfig-paths": "^4.2.0",
|
|
69
|
+
"tsc-alias": "^1.8.8",
|
|
68
70
|
"@types/supertest": "^6.0.2"<% } else { %>,
|
|
69
71
|
"jest": "^29.7.0",
|
|
70
72
|
"supertest": "^6.3.3"<% } %>
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
:root {
|
|
2
|
+
--primary-color: #4f46e5;
|
|
3
|
+
--primary-hover: #4338ca;
|
|
4
|
+
--bg-color: #f9fafb;
|
|
5
|
+
--card-bg: #ffffff;
|
|
6
|
+
--text-main: #111827;
|
|
7
|
+
--text-muted: #6b7280;
|
|
8
|
+
--success-color: #10b981;
|
|
9
|
+
--border-color: #e5e7eb;
|
|
10
|
+
--shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
|
|
11
|
+
--shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
body {
|
|
15
|
+
font-family: 'Inter', system-ui, -apple-system, sans-serif;
|
|
16
|
+
background-color: var(--bg-color);
|
|
17
|
+
color: var(--text-main);
|
|
18
|
+
margin: 0;
|
|
19
|
+
padding: 0;
|
|
20
|
+
line-height: 1.5;
|
|
21
|
+
display: flex;
|
|
22
|
+
justify-content: center;
|
|
23
|
+
min-height: 100vh;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.container {
|
|
27
|
+
width: 100%;
|
|
28
|
+
max-width: 800px;
|
|
29
|
+
padding: 40px 20px;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.header {
|
|
33
|
+
text-align: center;
|
|
34
|
+
margin-bottom: 40px;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.logo {
|
|
38
|
+
font-size: 3rem;
|
|
39
|
+
margin-bottom: 10px;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
h1 {
|
|
43
|
+
font-size: 2.5rem;
|
|
44
|
+
font-weight: 800;
|
|
45
|
+
color: var(--text-main);
|
|
46
|
+
margin: 0 0 10px 0;
|
|
47
|
+
letter-spacing: -0.025em;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
.subtitle {
|
|
51
|
+
color: var(--text-muted);
|
|
52
|
+
font-size: 1.125rem;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
.card-grid {
|
|
56
|
+
display: grid;
|
|
57
|
+
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
|
|
58
|
+
gap: 20px;
|
|
59
|
+
margin-bottom: 40px;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
.card {
|
|
63
|
+
background: var(--card-bg);
|
|
64
|
+
padding: 24px;
|
|
65
|
+
border-radius: 12px;
|
|
66
|
+
box-shadow: var(--shadow-sm);
|
|
67
|
+
border: 1px solid var(--border-color);
|
|
68
|
+
transition: transform 0.2s, box-shadow 0.2s;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
.card:hover {
|
|
72
|
+
transform: translateY(-2px);
|
|
73
|
+
box-shadow: var(--shadow-md);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
.card h3 {
|
|
77
|
+
margin-top: 0;
|
|
78
|
+
font-size: 0.875rem;
|
|
79
|
+
text-transform: uppercase;
|
|
80
|
+
letter-spacing: 0.05em;
|
|
81
|
+
color: var(--text-muted);
|
|
82
|
+
font-weight: 600;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
.card p {
|
|
86
|
+
font-size: 1.25rem;
|
|
87
|
+
font-weight: 600;
|
|
88
|
+
color: var(--text-main);
|
|
89
|
+
margin: 5px 0 0 0;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
.status-card {
|
|
93
|
+
background: var(--card-bg);
|
|
94
|
+
border-radius: 12px;
|
|
95
|
+
padding: 24px;
|
|
96
|
+
box-shadow: var(--shadow-md);
|
|
97
|
+
border-left: 6px solid var(--success-color);
|
|
98
|
+
display: flex;
|
|
99
|
+
align-items: center;
|
|
100
|
+
gap: 20px;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
.status-icon {
|
|
104
|
+
background: #d1fae5;
|
|
105
|
+
color: var(--success-color);
|
|
106
|
+
width: 48px;
|
|
107
|
+
height: 48px;
|
|
108
|
+
border-radius: 50%;
|
|
109
|
+
display: flex;
|
|
110
|
+
align-items: center;
|
|
111
|
+
justify-content: center;
|
|
112
|
+
font-size: 1.5rem;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
.status-content h3 {
|
|
116
|
+
margin: 0;
|
|
117
|
+
font-size: 1.25rem;
|
|
118
|
+
font-weight: 700;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
.status-content p {
|
|
122
|
+
margin: 5px 0 0 0;
|
|
123
|
+
color: var(--text-muted);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
.action-btn {
|
|
127
|
+
display: inline-block;
|
|
128
|
+
margin-top: 40px;
|
|
129
|
+
background-color: var(--primary-color);
|
|
130
|
+
color: white;
|
|
131
|
+
padding: 12px 24px;
|
|
132
|
+
border-radius: 8px;
|
|
133
|
+
text-decoration: none;
|
|
134
|
+
font-weight: 600;
|
|
135
|
+
transition: background-color 0.2s;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
.action-btn:hover {
|
|
139
|
+
background-color: var(--primary-hover);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
footer {
|
|
143
|
+
margin-top: 60px;
|
|
144
|
+
text-align: center;
|
|
145
|
+
color: var(--text-muted);
|
|
146
|
+
font-size: 0.875rem;
|
|
147
|
+
}
|
|
@@ -4,28 +4,52 @@
|
|
|
4
4
|
<meta charset="UTF-8">
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
6
|
<title><%= projectName %></title>
|
|
7
|
-
<style>
|
|
8
|
-
body { font-family: sans-serif; padding: 20px; }
|
|
9
|
-
h1 { color: #333; }
|
|
10
|
-
.status { margin-top: 20px; padding: 10px; background: #e0f7fa; border-left: 5px solid #00acc1; }
|
|
11
|
-
</style>
|
|
7
|
+
<link rel="stylesheet" href="/css/style.css">
|
|
12
8
|
</head>
|
|
13
9
|
<body>
|
|
14
|
-
<
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
<
|
|
22
|
-
|
|
10
|
+
<div class="container">
|
|
11
|
+
<header class="header">
|
|
12
|
+
<div class="logo">🚀</div>
|
|
13
|
+
<h1>Welcome to <%= projectName %></h1>
|
|
14
|
+
<p class="subtitle">A production-ready Node.js microservice starter.</p>
|
|
15
|
+
</header>
|
|
16
|
+
|
|
17
|
+
<div class="card-grid">
|
|
18
|
+
<div class="card">
|
|
19
|
+
<h3>Architecture</h3>
|
|
20
|
+
<p><%= architecture %></p>
|
|
21
|
+
</div>
|
|
22
|
+
<div class="card">
|
|
23
|
+
<h3>Database</h3>
|
|
24
|
+
<p><%= database %></p>
|
|
25
|
+
</div>
|
|
26
|
+
<div class="card">
|
|
27
|
+
<h3>Communication</h3>
|
|
28
|
+
<p><%= communication %></p>
|
|
29
|
+
</div>
|
|
30
|
+
</div>
|
|
31
|
+
|
|
32
|
+
<% if (communication === 'Kafka') { %>
|
|
33
|
+
<div class="status-card">
|
|
34
|
+
<div class="status-icon">🔄</div>
|
|
35
|
+
<div class="status-content">
|
|
36
|
+
<h3>Kafka Connected</h3>
|
|
37
|
+
<p>Connection to broker established successfully.</p>
|
|
38
|
+
</div>
|
|
39
|
+
</div>
|
|
40
|
+
<% } else { %>
|
|
41
|
+
<div class="status-card">
|
|
42
|
+
<div class="status-icon">✅</div>
|
|
43
|
+
<div class="status-content">
|
|
44
|
+
<h3>API Active</h3>
|
|
45
|
+
<p>REST API is running and ready to accept requests.</p>
|
|
46
|
+
</div>
|
|
47
|
+
</div>
|
|
48
|
+
<% } %>
|
|
49
|
+
|
|
50
|
+
<footer>
|
|
51
|
+
<p>Generated with ❤️ by Node.js Quickstart Generator</p>
|
|
52
|
+
</footer>
|
|
23
53
|
</div>
|
|
24
|
-
<% } else { %>
|
|
25
|
-
<div class="status">
|
|
26
|
-
<h3>API Status</h3>
|
|
27
|
-
<p>REST API is active.</p>
|
|
28
|
-
</div>
|
|
29
|
-
<% } %>
|
|
30
54
|
</body>
|
|
31
55
|
</html>
|
|
@@ -4,23 +4,37 @@ html(lang="en")
|
|
|
4
4
|
meta(charset="UTF-8")
|
|
5
5
|
meta(name="viewport", content="width=device-width, initial-scale=1.0")
|
|
6
6
|
title= projectName
|
|
7
|
-
style.
|
|
8
|
-
body { font-family: sans-serif; padding: 20px; }
|
|
9
|
-
h1 { color: #333; }
|
|
10
|
-
.status { margin-top: 20px; padding: 10px; background: #e0f7fa; border-left: 5px solid #00acc1; }
|
|
7
|
+
link(rel="stylesheet", href="/css/style.css")
|
|
11
8
|
body
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
9
|
+
.container
|
|
10
|
+
header.header
|
|
11
|
+
.logo 🚀
|
|
12
|
+
h1 Welcome to #{projectName}
|
|
13
|
+
p.subtitle A production-ready Node.js microservice starter.
|
|
17
14
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
15
|
+
.card-grid
|
|
16
|
+
.card
|
|
17
|
+
h3 Architecture
|
|
18
|
+
p #{architecture}
|
|
19
|
+
.card
|
|
20
|
+
h3 Database
|
|
21
|
+
p #{database}
|
|
22
|
+
.card
|
|
23
|
+
h3 Communication
|
|
24
|
+
p #{communication}
|
|
25
|
+
|
|
26
|
+
if communication === 'Kafka'
|
|
27
|
+
.status-card
|
|
28
|
+
.status-icon 🔄
|
|
29
|
+
.status-content
|
|
30
|
+
h3 Kafka Connected
|
|
31
|
+
p Connection to broker established successfully.
|
|
32
|
+
else
|
|
33
|
+
.status-card
|
|
34
|
+
.status-icon ✅
|
|
35
|
+
.status-content
|
|
36
|
+
h3 API Active
|
|
37
|
+
p REST API is running and ready to accept requests.
|
|
38
|
+
|
|
39
|
+
footer
|
|
40
|
+
p Generated with ❤️ by Node.js Quickstart Generator
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
const User = require('../models/User');
|
|
2
2
|
const HTTP_STATUS = require('../utils/httpCodes');
|
|
3
|
+
const logger = require('../utils/logger');
|
|
3
4
|
|
|
4
5
|
const getUsers = async (req, res) => {
|
|
5
6
|
try {
|
|
6
7
|
const users = await User.findAll();
|
|
7
8
|
res.json(users);
|
|
8
9
|
} catch (error) {
|
|
9
|
-
|
|
10
|
+
logger.error('Error fetching users:', error);
|
|
11
|
+
res.status(HTTP_STATUS.INTERNAL_SERVER_ERROR).json({ error: 'Internal Server Error' });
|
|
10
12
|
}
|
|
11
13
|
};
|
|
12
14
|
|
|
@@ -24,7 +24,7 @@ app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerSpecs));
|
|
|
24
24
|
const path = require('path');
|
|
25
25
|
app.set('views', path.join(__dirname, 'views'));
|
|
26
26
|
app.set('view engine', '<%= viewEngine.toLowerCase() %>');
|
|
27
|
-
|
|
27
|
+
app.use(express.static(path.join(__dirname, '../public')));<% } -%>
|
|
28
28
|
|
|
29
29
|
// Routes
|
|
30
30
|
<% if (communication === 'REST APIs' || (viewEngine && viewEngine !== 'None')) { -%>
|
|
@@ -21,10 +21,10 @@ const logger = winston.createLogger({
|
|
|
21
21
|
// If we're not in production then log to the `console` with the format:
|
|
22
22
|
// `${info.level}: ${info.message} JSON.stringify({ ...rest }) `
|
|
23
23
|
//
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
}
|
|
24
|
+
logger.add(new winston.transports.Console({
|
|
25
|
+
format: process.env.NODE_ENV !== 'production'
|
|
26
|
+
? winston.format.simple()
|
|
27
|
+
: winston.format.json(),
|
|
28
|
+
}));
|
|
29
29
|
|
|
30
30
|
module.exports = logger;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Request, Response } from 'express';
|
|
2
|
-
import User from '
|
|
3
|
-
import { HTTP_STATUS } from '
|
|
2
|
+
import User from '@/models/User';
|
|
3
|
+
import { HTTP_STATUS } from '@/utils/httpCodes';
|
|
4
|
+
import logger from '@/utils/logger';
|
|
4
5
|
|
|
5
6
|
export class UserController {
|
|
6
7
|
async getUsers(req: Request, res: Response) {
|
|
@@ -8,6 +9,7 @@ export class UserController {
|
|
|
8
9
|
const users = await User.findAll();
|
|
9
10
|
res.json(users);
|
|
10
11
|
} catch (error) {
|
|
12
|
+
logger.error('Error fetching users:', error);
|
|
11
13
|
if (error instanceof Error) {
|
|
12
14
|
res.status(HTTP_STATUS.INTERNAL_SERVER_ERROR).json({ error: error.message });
|
|
13
15
|
} else {
|
|
@@ -22,6 +24,7 @@ export class UserController {
|
|
|
22
24
|
const user = await User.create({ name, email });
|
|
23
25
|
res.status(HTTP_STATUS.CREATED).json(user);
|
|
24
26
|
} catch (error) {
|
|
27
|
+
logger.error('Error creating user:', error);
|
|
25
28
|
if (error instanceof Error) {
|
|
26
29
|
res.status(HTTP_STATUS.INTERNAL_SERVER_ERROR).json({ error: error.message });
|
|
27
30
|
} else {
|
|
@@ -4,12 +4,12 @@ import helmet from 'helmet';
|
|
|
4
4
|
import hpp from 'hpp';
|
|
5
5
|
import rateLimit from 'express-rate-limit';
|
|
6
6
|
import dotenv from 'dotenv';
|
|
7
|
-
import logger from '
|
|
8
|
-
<% if (communication === 'REST APIs' || (viewEngine && viewEngine !== 'None')) { %>import apiRoutes from '
|
|
7
|
+
import logger from '@/utils/logger';
|
|
8
|
+
<% if (communication === 'REST APIs' || (viewEngine && viewEngine !== 'None')) { %>import apiRoutes from '@/routes/api';<% } -%>
|
|
9
9
|
<% if (communication === 'REST APIs') { %>
|
|
10
10
|
import swaggerUi from 'swagger-ui-express';
|
|
11
|
-
import swaggerSpecs from '
|
|
12
|
-
<% if (communication === 'Kafka') { %>import { KafkaService } from '
|
|
11
|
+
import swaggerSpecs from '@/config/swagger';<% } -%>
|
|
12
|
+
<% if (communication === 'Kafka') { %>import { KafkaService } from '@/services/kafkaService';<% } -%>
|
|
13
13
|
|
|
14
14
|
dotenv.config();
|
|
15
15
|
|
|
@@ -34,7 +34,7 @@ app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerSpecs));
|
|
|
34
34
|
import path from 'path';
|
|
35
35
|
app.set('views', path.join(__dirname, 'views'));
|
|
36
36
|
app.set('view engine', '<%= viewEngine.toLowerCase() %>');
|
|
37
|
-
|
|
37
|
+
app.use(express.static(path.join(__dirname, '../public')));<% } -%>
|
|
38
38
|
|
|
39
39
|
// Routes
|
|
40
40
|
<% if (communication === 'REST APIs' || (viewEngine && viewEngine !== 'None')) { -%>
|
|
@@ -60,7 +60,7 @@ const syncDatabase = async () => {
|
|
|
60
60
|
let retries = 30;
|
|
61
61
|
while (retries) {
|
|
62
62
|
try {
|
|
63
|
-
const sequelize = (await import('
|
|
63
|
+
const sequelize = (await import('@/config/database')).default;
|
|
64
64
|
await sequelize.sync();
|
|
65
65
|
logger.info('Database synced');
|
|
66
66
|
|
|
@@ -80,10 +80,10 @@ const syncDatabase = async () => {
|
|
|
80
80
|
<%_ } -%>
|
|
81
81
|
});
|
|
82
82
|
break;
|
|
83
|
-
} catch (
|
|
84
|
-
logger.error('
|
|
83
|
+
} catch (error) {
|
|
84
|
+
logger.error('Error syncing database:', error);
|
|
85
85
|
retries -= 1;
|
|
86
|
-
logger.info(`Retries left: ${retries}
|
|
86
|
+
logger.info(`Retries left: ${retries}`);
|
|
87
87
|
await new Promise(res => setTimeout(res, 5000));
|
|
88
88
|
}
|
|
89
89
|
}
|
|
@@ -13,10 +13,10 @@ const logger = winston.createLogger({
|
|
|
13
13
|
],
|
|
14
14
|
});
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
}
|
|
16
|
+
logger.add(new winston.transports.Console({
|
|
17
|
+
format: process.env.NODE_ENV !== 'production'
|
|
18
|
+
? winston.format.simple()
|
|
19
|
+
: winston.format.json(),
|
|
20
|
+
}));
|
|
21
21
|
|
|
22
22
|
export default logger;
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
exports.getUsers = (req, res) => {
|
|
2
|
-
res.json([
|
|
3
|
-
{ id: 1, name: 'John Doe' },
|
|
4
|
-
{ id: 2, name: 'Jane Doe' }
|
|
5
|
-
]);
|
|
6
|
-
};
|
|
7
|
-
|
|
8
|
-
exports.createUser = (req, res) => {
|
|
9
|
-
const { name, email } = req.body;
|
|
10
|
-
res.status(201).json({
|
|
11
|
-
message: 'User created successfully',
|
|
12
|
-
user: { name, email }
|
|
13
|
-
});
|
|
14
|
-
};
|