nodejs-quickstart-structure 1.10.1 → 1.11.1

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.
Files changed (42) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/README.md +4 -0
  3. package/docs/generateCase.md +245 -165
  4. package/docs/generatorFlow.md +65 -23
  5. package/lib/generator.js +4 -1
  6. package/lib/modules/app-setup.js +56 -5
  7. package/package.json +1 -1
  8. package/templates/clean-architecture/js/src/errors/ApiError.js +14 -0
  9. package/templates/clean-architecture/js/src/errors/BadRequestError.js +10 -0
  10. package/templates/clean-architecture/js/src/errors/NotFoundError.js +10 -0
  11. package/templates/clean-architecture/js/src/infrastructure/webserver/middlewares/error.middleware.js +29 -0
  12. package/templates/clean-architecture/js/src/infrastructure/webserver/server.js.ejs +24 -0
  13. package/templates/clean-architecture/js/src/interfaces/controllers/userController.js.ejs +4 -7
  14. package/templates/clean-architecture/js/src/interfaces/graphql/resolvers/user.resolvers.js.ejs +2 -11
  15. package/templates/clean-architecture/js/src/interfaces/routes/api.js +2 -2
  16. package/templates/clean-architecture/ts/src/errors/ApiError.ts +15 -0
  17. package/templates/clean-architecture/ts/src/errors/BadRequestError.ts +8 -0
  18. package/templates/clean-architecture/ts/src/errors/NotFoundError.ts +8 -0
  19. package/templates/clean-architecture/ts/src/index.ts.ejs +23 -0
  20. package/templates/clean-architecture/ts/src/interfaces/controllers/userController.ts.ejs +13 -19
  21. package/templates/clean-architecture/ts/src/interfaces/graphql/resolvers/user.resolvers.ts.ejs +4 -13
  22. package/templates/clean-architecture/ts/src/interfaces/routes/userRoutes.ts +4 -3
  23. package/templates/clean-architecture/ts/src/utils/error.middleware.ts.ejs +27 -0
  24. package/templates/common/package.json.ejs +5 -6
  25. package/templates/mvc/js/src/controllers/userController.js.ejs +5 -4
  26. package/templates/mvc/js/src/errors/ApiError.js +14 -0
  27. package/templates/mvc/js/src/errors/BadRequestError.js +10 -0
  28. package/templates/mvc/js/src/errors/NotFoundError.js +10 -0
  29. package/templates/mvc/js/src/graphql/resolvers/user.resolvers.js.ejs +2 -11
  30. package/templates/mvc/js/src/index.js.ejs +23 -0
  31. package/templates/mvc/js/src/utils/error.middleware.js +28 -0
  32. package/templates/mvc/ts/src/controllers/userController.ts.ejs +6 -14
  33. package/templates/mvc/ts/src/errors/ApiError.ts +15 -0
  34. package/templates/mvc/ts/src/errors/BadRequestError.ts +8 -0
  35. package/templates/mvc/ts/src/errors/NotFoundError.ts +8 -0
  36. package/templates/mvc/ts/src/graphql/resolvers/user.resolvers.ts.ejs +4 -13
  37. package/templates/mvc/ts/src/index.ts.ejs +23 -0
  38. package/templates/mvc/ts/src/routes/api.ts +3 -3
  39. package/templates/mvc/ts/src/utils/error.middleware.ts.ejs +27 -0
  40. /package/templates/clean-architecture/js/src/infrastructure/webserver/{swagger.js → swagger.js.ejs} +0 -0
  41. /package/templates/clean-architecture/ts/src/infrastructure/repositories/{userRepository.ts.ejs → UserRepository.ts.ejs} +0 -0
  42. /package/templates/mvc/js/src/config/{swagger.js → swagger.js.ejs} +0 -0
@@ -1,10 +1,11 @@
1
- import { Router, Request, Response } from 'express';
1
+ import { Router, Request, Response, NextFunction } from 'express';
2
2
  import { UserController } from '@/interfaces/controllers/userController';
3
3
 
4
4
  const router = Router();
5
5
  const userController = new UserController();
6
6
 
7
- router.post('/', (req: Request, res: Response) => userController.createUser(req, res));
8
- router.get('/', (req: Request, res: Response) => userController.getUsers(req, res));
7
+ router.post('/', (req: Request, res: Response, next: NextFunction) => userController.createUser(req, res, next));
8
+ router.get('/', (req: Request, res: Response, next: NextFunction) => userController.getUsers(req, res, next));
9
9
 
10
10
  export default router;
11
+
@@ -0,0 +1,27 @@
1
+ import { Request, Response } from 'express';
2
+ import logger from '@/infrastructure/log/logger';
3
+ import { ApiError } from '@/errors/ApiError';
4
+ import { HTTP_STATUS } from '@/utils/httpCodes';
5
+
6
+ export const errorMiddleware = (err: Error, req: Request, res: Response, _: unknown) => {
7
+ let error = err;
8
+
9
+ if (!(error instanceof ApiError)) {
10
+ const statusCode = HTTP_STATUS.INTERNAL_SERVER_ERROR;
11
+ const message = error.message || 'Internal Server Error';
12
+ error = new ApiError(statusCode, message, false, err.stack);
13
+ }
14
+
15
+ const { statusCode, message } = error as ApiError;
16
+
17
+ if (statusCode === HTTP_STATUS.INTERNAL_SERVER_ERROR) {
18
+ logger.error(`${statusCode} - ${message} - ${req.originalUrl} - ${req.method} - ${req.ip}`);
19
+ logger.error(error.stack || 'No stack trace');
20
+ }
21
+
22
+ res.status(statusCode).json({
23
+ statusCode,
24
+ message,
25
+ ...(process.env.NODE_ENV === 'development' && { stack: error.stack }),
26
+ });
27
+ };
@@ -11,12 +11,11 @@
11
11
  "lint:fix": "eslint . --fix",
12
12
  "format": "prettier --write .",
13
13
  "prepare": "node -e \"try { require('child_process').execSync('husky install'); } catch (e) { console.log('Not a git repository, skipping husky install'); }\"",
14
+ <% if (database === 'MongoDB') { %> "migrate": "migrate-mongo up",
15
+ <% } -%>
14
16
  "test": "jest",
15
17
  "test:watch": "jest --watch",
16
18
  "test:coverage": "jest --coverage"
17
- <%_ if (database === 'MongoDB') { -%>,
18
- "migrate": "migrate-mongo up"
19
- <%_ } -%>
20
19
  },
21
20
  "dependencies": {
22
21
  "express": "^4.18.2",
@@ -80,9 +79,9 @@
80
79
  "eslint-config-prettier": "^10.0.1",
81
80
  "husky": "^8.0.3",
82
81
  "lint-staged": "^15.4.3"<% if (language === 'TypeScript') { %>,
83
- "typescript-eslint": "^8.24.1",
84
- <% if (communication === 'REST APIs') { %> "@types/swagger-ui-express": "^4.1.6",
85
- "@types/yamljs": "^0.2.34",<% } %>
82
+ "typescript-eslint": "^8.24.1",<%_ if (communication === 'REST APIs') { %>
83
+ "@types/swagger-ui-express": "^4.1.6",
84
+ "@types/yamljs": "^0.2.34",<%_ } -%>
86
85
  "jest": "^29.7.0",
87
86
  "ts-jest": "^29.2.5",
88
87
  "@types/jest": "^29.5.14",
@@ -61,7 +61,7 @@ const createUser = async (data) => {
61
61
  }
62
62
  };
63
63
  <% } else { -%>
64
- const getUsers = async (req, res) => {
64
+ const getUsers = async (req, res, next) => {
65
65
  try {
66
66
  <%_ if (caching === 'Redis' || caching === 'Memory Cache') { -%>
67
67
  const users = await cacheService.getOrSet('users:all', async () => {
@@ -85,11 +85,11 @@ const getUsers = async (req, res) => {
85
85
  res.json(users);
86
86
  } catch (error) {
87
87
  logger.error('Error fetching users:', error);
88
- res.status(HTTP_STATUS.INTERNAL_SERVER_ERROR).json({ error: 'Internal Server Error' });
88
+ next(error);
89
89
  }
90
90
  };
91
91
 
92
- const createUser = async (req, res) => {
92
+ const createUser = async (req, res, next) => {
93
93
  try {
94
94
  const { name, email } = req.body;
95
95
  <%_ if (database === 'None') { -%>
@@ -107,7 +107,8 @@ const createUser = async (req, res) => {
107
107
  res.status(HTTP_STATUS.CREATED).json(user);
108
108
  <%_ } -%>
109
109
  } catch (error) {
110
- res.status(HTTP_STATUS.INTERNAL_SERVER_ERROR).json({ error: error.message });
110
+ logger.error('Error creating user:', error);
111
+ next(error);
111
112
  }
112
113
  };
113
114
  <% } -%>
@@ -0,0 +1,14 @@
1
+ class ApiError extends Error {
2
+ constructor(statusCode, message, isOperational = true, stack = '') {
3
+ super(message);
4
+ this.statusCode = statusCode;
5
+ this.isOperational = isOperational;
6
+ if (stack) {
7
+ this.stack = stack;
8
+ } else {
9
+ Error.captureStackTrace(this, this.constructor);
10
+ }
11
+ }
12
+ }
13
+
14
+ module.exports = { ApiError };
@@ -0,0 +1,10 @@
1
+ const { ApiError } = require('./ApiError');
2
+ const { HTTP_STATUS } = require('../utils/httpCodes');
3
+
4
+ class BadRequestError extends ApiError {
5
+ constructor(message = 'Bad request') {
6
+ super(HTTP_STATUS.BAD_REQUEST, message);
7
+ }
8
+ }
9
+
10
+ module.exports = { BadRequestError };
@@ -0,0 +1,10 @@
1
+ const { ApiError } = require('./ApiError');
2
+ const { HTTP_STATUS } = require('../utils/httpCodes');
3
+
4
+ class NotFoundError extends ApiError {
5
+ constructor(message = 'Resource not found') {
6
+ super(HTTP_STATUS.NOT_FOUND, message);
7
+ }
8
+ }
9
+
10
+ module.exports = { NotFoundError };
@@ -1,23 +1,14 @@
1
- const { GraphQLError } = require('graphql');
2
1
  const userController = require('../../controllers/userController');
3
2
 
4
3
  const userResolvers = {
5
4
  Query: {
6
5
  getAllUsers: async () => {
7
- try {
8
- return await userController.getUsers();
9
- } catch (error) {
10
- throw new GraphQLError(error.message || 'Internal server error', { extensions: { code: 'INTERNAL_SERVER_ERROR' } });
11
- }
6
+ return await userController.getUsers();
12
7
  }
13
8
  },
14
9
  Mutation: {
15
10
  createUser: async (_, { name, email }) => {
16
- try {
17
- return await userController.createUser({ name, email });
18
- } catch (error) {
19
- throw new GraphQLError(error.message || 'Internal server error', { extensions: { code: 'INTERNAL_SERVER_ERROR' } });
20
- }
11
+ return await userController.createUser({ name, email });
21
12
  }
22
13
  }
23
14
  };
@@ -7,6 +7,8 @@ require('dotenv').config();
7
7
  const { ApolloServer } = require('@apollo/server');
8
8
  const { expressMiddleware } = require('@apollo/server/express4');
9
9
  const { ApolloServerPluginLandingPageLocalDefault } = require('@apollo/server/plugin/landingPage/default');
10
+ const { unwrapResolverError } = require('@apollo/server/errors');
11
+ const { ApiError } = require('./errors/ApiError');
10
12
  const { typeDefs, resolvers } = require('./graphql');
11
13
  const { gqlContext } = require('./graphql/context');
12
14
  <% } -%>
@@ -19,6 +21,7 @@ const app = express();
19
21
  const PORT = process.env.PORT || 3000;
20
22
  const logger = require('./utils/logger');
21
23
  const morgan = require('morgan');
24
+ const { errorMiddleware } = require('./utils/error.middleware');
22
25
 
23
26
  app.use(cors());
24
27
  app.use(express.json());
@@ -57,10 +60,30 @@ const startServer = async () => {
57
60
  typeDefs,
58
61
  resolvers,
59
62
  plugins: [ApolloServerPluginLandingPageLocalDefault({ embed: true })],
63
+ formatError: (formattedError, error) => {
64
+ const originalError = unwrapResolverError(error);
65
+ if (originalError instanceof ApiError) {
66
+ return {
67
+ ...formattedError,
68
+ message: originalError.message,
69
+ extensions: {
70
+ ...formattedError.extensions,
71
+ code: originalError.statusCode.toString(),
72
+ }
73
+ };
74
+ }
75
+
76
+ logger.error(`GraphQL Error: ${formattedError.message}`);
77
+ if (originalError && originalError.stack && process.env.NODE_ENV === 'development') {
78
+ logger.error(originalError.stack);
79
+ }
80
+ return formattedError;
81
+ },
60
82
  });
61
83
  await server.start();
62
84
  app.use('/graphql', expressMiddleware(server, { context: gqlContext }));
63
85
  <%_ } -%>
86
+ app.use(errorMiddleware);
64
87
  app.listen(PORT, () => {
65
88
  logger.info(`Server running on port ${PORT}`);
66
89
  <%_ if (communication === 'Kafka') { -%>
@@ -0,0 +1,28 @@
1
+ const logger = require('./logger');
2
+ const { ApiError } = require('../errors/ApiError');
3
+ const HTTP_STATUS = require('./httpCodes');
4
+
5
+ const errorMiddleware = (err, req, res, _) => {
6
+ let error = err;
7
+
8
+ if (!(error instanceof ApiError)) {
9
+ const statusCode = err.statusCode || HTTP_STATUS.INTERNAL_SERVER_ERROR;
10
+ const message = error.message || 'Internal Server Error';
11
+ error = new ApiError(statusCode, message, false, err.stack);
12
+ }
13
+
14
+ const { statusCode, message } = error;
15
+
16
+ if (statusCode === HTTP_STATUS.INTERNAL_SERVER_ERROR) {
17
+ logger.error(`${statusCode} - ${message} - ${req.originalUrl} - ${req.method} - ${req.ip}`);
18
+ logger.error(error.stack || 'No stack trace');
19
+ }
20
+
21
+ res.status(statusCode).json({
22
+ statusCode,
23
+ message,
24
+ ...(process.env.NODE_ENV === 'development' && { stack: error.stack }),
25
+ });
26
+ };
27
+
28
+ module.exports = { errorMiddleware };
@@ -1,5 +1,5 @@
1
1
  <% if (communication !== 'GraphQL') { -%>
2
- import { Request, Response } from 'express';
2
+ import { Request, Response, NextFunction } from 'express';
3
3
  import { HTTP_STATUS } from '@/utils/httpCodes';
4
4
  <% } -%>
5
5
  import User from '@/models/User';
@@ -63,7 +63,7 @@ export class UserController {
63
63
  }
64
64
  }
65
65
  <% } else { -%>
66
- async getUsers(req: Request, res: Response) {
66
+ async getUsers(req: Request, res: Response, next: NextFunction) {
67
67
  try {
68
68
  <%_ if (caching === 'Redis' || caching === 'Memory Cache') { -%>
69
69
  const users = await cacheService.getOrSet('users:all', async () => {
@@ -86,16 +86,12 @@ export class UserController {
86
86
  <%_ } -%>
87
87
  res.json(users);
88
88
  } catch (error) {
89
- logger.error('Error fetching users:', error);
90
- if (error instanceof Error) {
91
- res.status(HTTP_STATUS.INTERNAL_SERVER_ERROR).json({ error: error.message });
92
- } else {
93
- res.status(HTTP_STATUS.INTERNAL_SERVER_ERROR).json({ error: 'Unknown error occurred' });
94
- }
89
+ logger.error('Error fetching user:', error);
90
+ next(error);
95
91
  }
96
92
  }
97
93
 
98
- async createUser(req: Request, res: Response) {
94
+ async createUser(req: Request, res: Response, next: NextFunction) {
99
95
  try {
100
96
  const { name, email } = req.body;
101
97
  <%_ if (database === 'None') { -%>
@@ -114,11 +110,7 @@ export class UserController {
114
110
  <%_ } -%>
115
111
  } catch (error) {
116
112
  logger.error('Error creating user:', error);
117
- if (error instanceof Error) {
118
- res.status(HTTP_STATUS.INTERNAL_SERVER_ERROR).json({ error: error.message });
119
- } else {
120
- res.status(HTTP_STATUS.INTERNAL_SERVER_ERROR).json({ error: 'Unknown error occurred' });
121
- }
113
+ next(error);
122
114
  }
123
115
  }
124
116
  <% } -%>
@@ -0,0 +1,15 @@
1
+ export class ApiError extends Error {
2
+ statusCode: number;
3
+ isOperational: boolean;
4
+
5
+ constructor(statusCode: number, message: string, isOperational = true, stack = '') {
6
+ super(message);
7
+ this.statusCode = statusCode;
8
+ this.isOperational = isOperational;
9
+ if (stack) {
10
+ this.stack = stack;
11
+ } else {
12
+ Error.captureStackTrace(this, this.constructor);
13
+ }
14
+ }
15
+ }
@@ -0,0 +1,8 @@
1
+ import { ApiError } from './ApiError';
2
+ import { HTTP_STATUS } from '@/utils/httpCodes';
3
+
4
+ export class BadRequestError extends ApiError {
5
+ constructor(message = 'Bad request') {
6
+ super(HTTP_STATUS.BAD_REQUEST, message);
7
+ }
8
+ }
@@ -0,0 +1,8 @@
1
+ import { ApiError } from './ApiError';
2
+ import { HTTP_STATUS } from '@/utils/httpCodes';
3
+
4
+ export class NotFoundError extends ApiError {
5
+ constructor(message = 'Resource not found') {
6
+ super(HTTP_STATUS.NOT_FOUND, message);
7
+ }
8
+ }
@@ -1,4 +1,3 @@
1
- import { GraphQLError } from 'graphql';
2
1
  import { UserController } from '@/controllers/userController';
3
2
 
4
3
  const userController = new UserController();
@@ -6,22 +5,14 @@ const userController = new UserController();
6
5
  export const userResolvers = {
7
6
  Query: {
8
7
  getAllUsers: async () => {
9
- try {
10
- return await userController.getUsers();
11
- } catch (error: unknown) {
12
- const message = error instanceof Error ? error.message : 'Internal server error';
13
- throw new GraphQLError(message, { extensions: { code: 'INTERNAL_SERVER_ERROR' } });
14
- }
8
+ const users = await userController.getUsers();
9
+ return users;
15
10
  }
16
11
  },
17
12
  Mutation: {
18
13
  createUser: async (_: unknown, { name, email }: { name: string, email: string }) => {
19
- try {
20
- return await userController.createUser({ name, email });
21
- } catch (error: unknown) {
22
- const message = error instanceof Error ? error.message : 'Internal server error';
23
- throw new GraphQLError(message, { extensions: { code: 'INTERNAL_SERVER_ERROR' } });
24
- }
14
+ const user = await userController.createUser({ name, email });
15
+ return user;
25
16
  }
26
17
  }
27
18
  };
@@ -6,6 +6,7 @@ import rateLimit from 'express-rate-limit';
6
6
  import dotenv from 'dotenv';
7
7
  import logger from '@/utils/logger';
8
8
  import morgan from 'morgan';
9
+ import { errorMiddleware } from '@/utils/error.middleware';
9
10
  <%_ if (communication === 'REST APIs') { -%>
10
11
  import apiRoutes from '@/routes/api';<%_ } -%>
11
12
  <% if (communication === 'REST APIs') { %>
@@ -16,6 +17,8 @@ import swaggerSpecs from '@/config/swagger';<% } -%>
16
17
  import { ApolloServer } from '@apollo/server';
17
18
  import { expressMiddleware } from '@apollo/server/express4';
18
19
  import { ApolloServerPluginLandingPageLocalDefault } from '@apollo/server/plugin/landingPage/default';
20
+ import { unwrapResolverError } from '@apollo/server/errors';
21
+ import { ApiError } from '@/errors/ApiError';
19
22
  import { typeDefs, resolvers } from '@/graphql';
20
23
  import { gqlContext, MyContext } from '@/graphql/context';
21
24
  <% } -%>
@@ -81,10 +84,30 @@ const startServer = async () => {
81
84
  typeDefs,
82
85
  resolvers,
83
86
  plugins: [ApolloServerPluginLandingPageLocalDefault({ embed: true })],
87
+ formatError: (formattedError, error) => {
88
+ const originalError = unwrapResolverError(error);
89
+ if (originalError instanceof ApiError) {
90
+ return {
91
+ ...formattedError,
92
+ message: originalError.message,
93
+ extensions: {
94
+ ...formattedError.extensions,
95
+ code: originalError.statusCode.toString(),
96
+ }
97
+ };
98
+ }
99
+
100
+ logger.error(`GraphQL Error: ${formattedError.message}`);
101
+ if (originalError instanceof Error && originalError.stack && process.env.NODE_ENV === 'development') {
102
+ logger.error(originalError.stack);
103
+ }
104
+ return formattedError;
105
+ },
84
106
  });
85
107
  await server.start();
86
108
  app.use('/graphql', expressMiddleware(server, { context: gqlContext }));
87
109
  <%_ } -%>
110
+ app.use(errorMiddleware);
88
111
  app.listen(port, () => {
89
112
  logger.info(`Server running on port ${port}`);
90
113
  <%_ if (communication === 'Kafka') { -%>
@@ -1,10 +1,10 @@
1
- import { Router, Request, Response } from 'express';
1
+ import { Router, Request, Response, NextFunction } from 'express';
2
2
  import { UserController } from '@/controllers/userController';
3
3
 
4
4
  const router = Router();
5
5
  const userController = new UserController();
6
6
 
7
- router.get('/users', (req: Request, res: Response) => userController.getUsers(req, res));
8
- router.post('/users', (req: Request, res: Response) => userController.createUser(req, res));
7
+ router.get('/users', (req: Request, res: Response, next: NextFunction) => userController.getUsers(req, res, next));
8
+ router.post('/users', (req: Request, res: Response, next: NextFunction) => userController.createUser(req, res, next));
9
9
 
10
10
  export default router;
@@ -0,0 +1,27 @@
1
+ import { Request, Response } from 'express';
2
+ import logger from '@/utils/logger';
3
+ import { ApiError } from '@/errors/ApiError';
4
+ import { HTTP_STATUS } from '@/utils/httpCodes';
5
+
6
+ export const errorMiddleware = (err: Error, req: Request, res: Response, _: unknown) => {
7
+ let error = err;
8
+
9
+ if (!(error instanceof ApiError)) {
10
+ const statusCode = HTTP_STATUS.INTERNAL_SERVER_ERROR;
11
+ const message = error.message || 'Internal Server Error';
12
+ error = new ApiError(statusCode, message, false, err.stack);
13
+ }
14
+
15
+ const { statusCode, message } = error as ApiError;
16
+
17
+ if (statusCode === HTTP_STATUS.INTERNAL_SERVER_ERROR) {
18
+ logger.error(`${statusCode} - ${message} - ${req.originalUrl} - ${req.method} - ${req.ip}`);
19
+ logger.error(error.stack || 'No stack trace');
20
+ }
21
+
22
+ res.status(statusCode).json({
23
+ statusCode,
24
+ message,
25
+ ...(process.env.NODE_ENV === 'development' && { stack: error.stack }),
26
+ });
27
+ };