nodejs-express-starter 1.7.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.
Files changed (70) hide show
  1. package/.dockerignore +3 -0
  2. package/.editorconfig +9 -0
  3. package/.env.example +22 -0
  4. package/.eslintignore +2 -0
  5. package/.eslintrc.json +32 -0
  6. package/.gitignore +14 -0
  7. package/.prettierignore +3 -0
  8. package/.prettierrc.json +4 -0
  9. package/Dockerfile +15 -0
  10. package/LICENSE +21 -0
  11. package/README.md +440 -0
  12. package/bin/createNodejsApp.js +105 -0
  13. package/docker-compose.dev.yml +4 -0
  14. package/docker-compose.prod.yml +4 -0
  15. package/docker-compose.test.yml +4 -0
  16. package/docker-compose.yml +30 -0
  17. package/jest.config.js +9 -0
  18. package/package.json +117 -0
  19. package/src/app.js +82 -0
  20. package/src/config/config.js +64 -0
  21. package/src/config/logger.js +26 -0
  22. package/src/config/morgan.js +25 -0
  23. package/src/config/passport.js +30 -0
  24. package/src/config/roles.js +12 -0
  25. package/src/config/tokens.js +10 -0
  26. package/src/controllers/auth.controller.js +59 -0
  27. package/src/controllers/index.js +2 -0
  28. package/src/controllers/user.controller.js +43 -0
  29. package/src/docs/components.yml +92 -0
  30. package/src/docs/swaggerDef.js +21 -0
  31. package/src/index.js +57 -0
  32. package/src/middlewares/auth.js +33 -0
  33. package/src/middlewares/error.js +47 -0
  34. package/src/middlewares/rateLimiter.js +11 -0
  35. package/src/middlewares/requestId.js +14 -0
  36. package/src/middlewares/validate.js +21 -0
  37. package/src/models/index.js +2 -0
  38. package/src/models/plugins/index.js +2 -0
  39. package/src/models/plugins/paginate.plugin.js +70 -0
  40. package/src/models/plugins/toJSON.plugin.js +43 -0
  41. package/src/models/token.model.js +44 -0
  42. package/src/models/user.model.js +91 -0
  43. package/src/routes/v1/auth.route.js +291 -0
  44. package/src/routes/v1/docs.route.js +21 -0
  45. package/src/routes/v1/health.route.js +43 -0
  46. package/src/routes/v1/index.js +39 -0
  47. package/src/routes/v1/user.route.js +252 -0
  48. package/src/services/auth.service.js +99 -0
  49. package/src/services/email.service.js +63 -0
  50. package/src/services/index.js +4 -0
  51. package/src/services/token.service.js +123 -0
  52. package/src/services/user.service.js +89 -0
  53. package/src/utils/ApiError.js +14 -0
  54. package/src/utils/catchAsync.js +5 -0
  55. package/src/utils/pick.js +17 -0
  56. package/src/validations/auth.validation.js +60 -0
  57. package/src/validations/custom.validation.js +21 -0
  58. package/src/validations/index.js +2 -0
  59. package/src/validations/user.validation.js +54 -0
  60. package/tests/fixtures/token.fixture.js +14 -0
  61. package/tests/fixtures/user.fixture.js +46 -0
  62. package/tests/integration/auth.test.js +587 -0
  63. package/tests/integration/docs.test.js +14 -0
  64. package/tests/integration/health.test.js +32 -0
  65. package/tests/integration/user.test.js +625 -0
  66. package/tests/unit/middlewares/error.test.js +168 -0
  67. package/tests/unit/models/plugins/paginate.plugin.test.js +61 -0
  68. package/tests/unit/models/plugins/toJSON.plugin.test.js +89 -0
  69. package/tests/unit/models/user.model.test.js +57 -0
  70. package/tests/utils/setupTestDB.js +18 -0
@@ -0,0 +1,291 @@
1
+ const express = require('express');
2
+ const validate = require('../../middlewares/validate');
3
+ const authValidation = require('../../validations/auth.validation');
4
+ const authController = require('../../controllers/auth.controller');
5
+ const auth = require('../../middlewares/auth');
6
+
7
+ const router = express.Router();
8
+
9
+ router.post('/register', validate(authValidation.register), authController.register);
10
+ router.post('/login', validate(authValidation.login), authController.login);
11
+ router.post('/logout', validate(authValidation.logout), authController.logout);
12
+ router.post('/refresh-tokens', validate(authValidation.refreshTokens), authController.refreshTokens);
13
+ router.post('/forgot-password', validate(authValidation.forgotPassword), authController.forgotPassword);
14
+ router.post('/reset-password', validate(authValidation.resetPassword), authController.resetPassword);
15
+ router.post('/send-verification-email', auth(), authController.sendVerificationEmail);
16
+ router.post('/verify-email', validate(authValidation.verifyEmail), authController.verifyEmail);
17
+
18
+ module.exports = router;
19
+
20
+ /**
21
+ * @swagger
22
+ * tags:
23
+ * name: Auth
24
+ * description: Authentication
25
+ */
26
+
27
+ /**
28
+ * @swagger
29
+ * /auth/register:
30
+ * post:
31
+ * summary: Register as user
32
+ * tags: [Auth]
33
+ * requestBody:
34
+ * required: true
35
+ * content:
36
+ * application/json:
37
+ * schema:
38
+ * type: object
39
+ * required:
40
+ * - name
41
+ * - email
42
+ * - password
43
+ * properties:
44
+ * name:
45
+ * type: string
46
+ * email:
47
+ * type: string
48
+ * format: email
49
+ * description: must be unique
50
+ * password:
51
+ * type: string
52
+ * format: password
53
+ * minLength: 8
54
+ * description: At least one number and one letter
55
+ * example:
56
+ * name: fake name
57
+ * email: fake@example.com
58
+ * password: password1
59
+ * responses:
60
+ * "201":
61
+ * description: Created
62
+ * content:
63
+ * application/json:
64
+ * schema:
65
+ * type: object
66
+ * properties:
67
+ * user:
68
+ * $ref: '#/components/schemas/User'
69
+ * tokens:
70
+ * $ref: '#/components/schemas/AuthTokens'
71
+ * "400":
72
+ * $ref: '#/components/responses/DuplicateEmail'
73
+ */
74
+
75
+ /**
76
+ * @swagger
77
+ * /auth/login:
78
+ * post:
79
+ * summary: Login
80
+ * tags: [Auth]
81
+ * requestBody:
82
+ * required: true
83
+ * content:
84
+ * application/json:
85
+ * schema:
86
+ * type: object
87
+ * required:
88
+ * - email
89
+ * - password
90
+ * properties:
91
+ * email:
92
+ * type: string
93
+ * format: email
94
+ * password:
95
+ * type: string
96
+ * format: password
97
+ * example:
98
+ * email: fake@example.com
99
+ * password: password1
100
+ * responses:
101
+ * "200":
102
+ * description: OK
103
+ * content:
104
+ * application/json:
105
+ * schema:
106
+ * type: object
107
+ * properties:
108
+ * user:
109
+ * $ref: '#/components/schemas/User'
110
+ * tokens:
111
+ * $ref: '#/components/schemas/AuthTokens'
112
+ * "401":
113
+ * description: Invalid email or password
114
+ * content:
115
+ * application/json:
116
+ * schema:
117
+ * $ref: '#/components/schemas/Error'
118
+ * example:
119
+ * code: 401
120
+ * message: Invalid email or password
121
+ */
122
+
123
+ /**
124
+ * @swagger
125
+ * /auth/logout:
126
+ * post:
127
+ * summary: Logout
128
+ * tags: [Auth]
129
+ * requestBody:
130
+ * required: true
131
+ * content:
132
+ * application/json:
133
+ * schema:
134
+ * type: object
135
+ * required:
136
+ * - refreshToken
137
+ * properties:
138
+ * refreshToken:
139
+ * type: string
140
+ * example:
141
+ * refreshToken: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI1ZWJhYzUzNDk1NGI1NDEzOTgwNmMxMTIiLCJpYXQiOjE1ODkyOTg0ODQsImV4cCI6MTU4OTMwMDI4NH0.m1U63blB0MLej_WfB7yC2FTMnCziif9X8yzwDEfJXAg
142
+ * responses:
143
+ * "204":
144
+ * description: No content
145
+ * "404":
146
+ * $ref: '#/components/responses/NotFound'
147
+ */
148
+
149
+ /**
150
+ * @swagger
151
+ * /auth/refresh-tokens:
152
+ * post:
153
+ * summary: Refresh auth tokens
154
+ * tags: [Auth]
155
+ * requestBody:
156
+ * required: true
157
+ * content:
158
+ * application/json:
159
+ * schema:
160
+ * type: object
161
+ * required:
162
+ * - refreshToken
163
+ * properties:
164
+ * refreshToken:
165
+ * type: string
166
+ * example:
167
+ * refreshToken: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI1ZWJhYzUzNDk1NGI1NDEzOTgwNmMxMTIiLCJpYXQiOjE1ODkyOTg0ODQsImV4cCI6MTU4OTMwMDI4NH0.m1U63blB0MLej_WfB7yC2FTMnCziif9X8yzwDEfJXAg
168
+ * responses:
169
+ * "200":
170
+ * description: OK
171
+ * content:
172
+ * application/json:
173
+ * schema:
174
+ * $ref: '#/components/schemas/AuthTokens'
175
+ * "401":
176
+ * $ref: '#/components/responses/Unauthorized'
177
+ */
178
+
179
+ /**
180
+ * @swagger
181
+ * /auth/forgot-password:
182
+ * post:
183
+ * summary: Forgot password
184
+ * description: An email will be sent to reset password.
185
+ * tags: [Auth]
186
+ * requestBody:
187
+ * required: true
188
+ * content:
189
+ * application/json:
190
+ * schema:
191
+ * type: object
192
+ * required:
193
+ * - email
194
+ * properties:
195
+ * email:
196
+ * type: string
197
+ * format: email
198
+ * example:
199
+ * email: fake@example.com
200
+ * responses:
201
+ * "204":
202
+ * description: No content
203
+ * "404":
204
+ * $ref: '#/components/responses/NotFound'
205
+ */
206
+
207
+ /**
208
+ * @swagger
209
+ * /auth/reset-password:
210
+ * post:
211
+ * summary: Reset password
212
+ * tags: [Auth]
213
+ * parameters:
214
+ * - in: query
215
+ * name: token
216
+ * required: true
217
+ * schema:
218
+ * type: string
219
+ * description: The reset password token
220
+ * requestBody:
221
+ * required: true
222
+ * content:
223
+ * application/json:
224
+ * schema:
225
+ * type: object
226
+ * required:
227
+ * - password
228
+ * properties:
229
+ * password:
230
+ * type: string
231
+ * format: password
232
+ * minLength: 8
233
+ * description: At least one number and one letter
234
+ * example:
235
+ * password: password1
236
+ * responses:
237
+ * "204":
238
+ * description: No content
239
+ * "401":
240
+ * description: Password reset failed
241
+ * content:
242
+ * application/json:
243
+ * schema:
244
+ * $ref: '#/components/schemas/Error'
245
+ * example:
246
+ * code: 401
247
+ * message: Password reset failed
248
+ */
249
+
250
+ /**
251
+ * @swagger
252
+ * /auth/send-verification-email:
253
+ * post:
254
+ * summary: Send verification email
255
+ * description: An email will be sent to verify email.
256
+ * tags: [Auth]
257
+ * security:
258
+ * - bearerAuth: []
259
+ * responses:
260
+ * "204":
261
+ * description: No content
262
+ * "401":
263
+ * $ref: '#/components/responses/Unauthorized'
264
+ */
265
+
266
+ /**
267
+ * @swagger
268
+ * /auth/verify-email:
269
+ * post:
270
+ * summary: verify email
271
+ * tags: [Auth]
272
+ * parameters:
273
+ * - in: query
274
+ * name: token
275
+ * required: true
276
+ * schema:
277
+ * type: string
278
+ * description: The verify email token
279
+ * responses:
280
+ * "204":
281
+ * description: No content
282
+ * "401":
283
+ * description: verify email failed
284
+ * content:
285
+ * application/json:
286
+ * schema:
287
+ * $ref: '#/components/schemas/Error'
288
+ * example:
289
+ * code: 401
290
+ * message: verify email failed
291
+ */
@@ -0,0 +1,21 @@
1
+ const express = require('express');
2
+ const swaggerJsdoc = require('swagger-jsdoc');
3
+ const swaggerUi = require('swagger-ui-express');
4
+ const swaggerDefinition = require('../../docs/swaggerDef');
5
+
6
+ const router = express.Router();
7
+
8
+ const specs = swaggerJsdoc({
9
+ swaggerDefinition,
10
+ apis: ['src/docs/*.yml', 'src/routes/v1/*.js'],
11
+ });
12
+
13
+ router.use('/', swaggerUi.serve);
14
+ router.get(
15
+ '/',
16
+ swaggerUi.setup(specs, {
17
+ explorer: true,
18
+ }),
19
+ );
20
+
21
+ module.exports = router;
@@ -0,0 +1,43 @@
1
+ const express = require('express');
2
+ const mongoose = require('mongoose');
3
+ const httpStatus = require('http-status');
4
+
5
+ const router = express.Router();
6
+
7
+ /**
8
+ * Health check - returns 200 if server is running
9
+ * GET /health
10
+ */
11
+ router.get('/', (req, res) => {
12
+ res.status(httpStatus.OK).json({
13
+ status: 'ok',
14
+ timestamp: new Date().toISOString(),
15
+ uptime: process.uptime(),
16
+ });
17
+ });
18
+
19
+ /**
20
+ * Readiness check - returns 200 if app is ready to accept traffic (DB connected)
21
+ * GET /health/ready
22
+ */
23
+ router.get('/ready', async (req, res) => {
24
+ try {
25
+ if (mongoose.connection.readyState !== 1) {
26
+ return res.status(httpStatus.SERVICE_UNAVAILABLE).json({
27
+ status: 'unavailable',
28
+ reason: 'Database not connected',
29
+ });
30
+ }
31
+ res.status(httpStatus.OK).json({
32
+ status: 'ready',
33
+ database: 'connected',
34
+ });
35
+ } catch (err) {
36
+ res.status(httpStatus.SERVICE_UNAVAILABLE).json({
37
+ status: 'unavailable',
38
+ reason: err.message,
39
+ });
40
+ }
41
+ });
42
+
43
+ module.exports = router;
@@ -0,0 +1,39 @@
1
+ const express = require('express');
2
+ const authRoute = require('./auth.route');
3
+ const userRoute = require('./user.route');
4
+ const docsRoute = require('./docs.route');
5
+ const config = require('../../config/config');
6
+
7
+ const router = express.Router();
8
+
9
+ const defaultRoutes = [
10
+ {
11
+ path: '/auth',
12
+ route: authRoute,
13
+ },
14
+ {
15
+ path: '/users',
16
+ route: userRoute,
17
+ },
18
+ ];
19
+
20
+ const devRoutes = [
21
+ // routes available only in development mode
22
+ {
23
+ path: '/docs',
24
+ route: docsRoute,
25
+ },
26
+ ];
27
+
28
+ defaultRoutes.forEach((route) => {
29
+ router.use(route.path, route.route);
30
+ });
31
+
32
+ /* istanbul ignore next */
33
+ if (config.env === 'development') {
34
+ devRoutes.forEach((route) => {
35
+ router.use(route.path, route.route);
36
+ });
37
+ }
38
+
39
+ module.exports = router;
@@ -0,0 +1,252 @@
1
+ const express = require('express');
2
+ const auth = require('../../middlewares/auth');
3
+ const validate = require('../../middlewares/validate');
4
+ const userValidation = require('../../validations/user.validation');
5
+ const userController = require('../../controllers/user.controller');
6
+
7
+ const router = express.Router();
8
+
9
+ router
10
+ .route('/')
11
+ .post(auth('manageUsers'), validate(userValidation.createUser), userController.createUser)
12
+ .get(auth('getUsers'), validate(userValidation.getUsers), userController.getUsers);
13
+
14
+ router
15
+ .route('/:userId')
16
+ .get(auth('getUsers'), validate(userValidation.getUser), userController.getUser)
17
+ .patch(auth('manageUsers'), validate(userValidation.updateUser), userController.updateUser)
18
+ .delete(auth('manageUsers'), validate(userValidation.deleteUser), userController.deleteUser);
19
+
20
+ module.exports = router;
21
+
22
+ /**
23
+ * @swagger
24
+ * tags:
25
+ * name: Users
26
+ * description: User management and retrieval
27
+ */
28
+
29
+ /**
30
+ * @swagger
31
+ * /users:
32
+ * post:
33
+ * summary: Create a user
34
+ * description: Only admins can create other users.
35
+ * tags: [Users]
36
+ * security:
37
+ * - bearerAuth: []
38
+ * requestBody:
39
+ * required: true
40
+ * content:
41
+ * application/json:
42
+ * schema:
43
+ * type: object
44
+ * required:
45
+ * - name
46
+ * - email
47
+ * - password
48
+ * - role
49
+ * properties:
50
+ * name:
51
+ * type: string
52
+ * email:
53
+ * type: string
54
+ * format: email
55
+ * description: must be unique
56
+ * password:
57
+ * type: string
58
+ * format: password
59
+ * minLength: 8
60
+ * description: At least one number and one letter
61
+ * role:
62
+ * type: string
63
+ * enum: [user, admin]
64
+ * example:
65
+ * name: fake name
66
+ * email: fake@example.com
67
+ * password: password1
68
+ * role: user
69
+ * responses:
70
+ * "201":
71
+ * description: Created
72
+ * content:
73
+ * application/json:
74
+ * schema:
75
+ * $ref: '#/components/schemas/User'
76
+ * "400":
77
+ * $ref: '#/components/responses/DuplicateEmail'
78
+ * "401":
79
+ * $ref: '#/components/responses/Unauthorized'
80
+ * "403":
81
+ * $ref: '#/components/responses/Forbidden'
82
+ *
83
+ * get:
84
+ * summary: Get all users
85
+ * description: Only admins can retrieve all users.
86
+ * tags: [Users]
87
+ * security:
88
+ * - bearerAuth: []
89
+ * parameters:
90
+ * - in: query
91
+ * name: name
92
+ * schema:
93
+ * type: string
94
+ * description: User name
95
+ * - in: query
96
+ * name: role
97
+ * schema:
98
+ * type: string
99
+ * description: User role
100
+ * - in: query
101
+ * name: sortBy
102
+ * schema:
103
+ * type: string
104
+ * description: sort by query in the form of field:desc/asc (ex. name:asc)
105
+ * - in: query
106
+ * name: limit
107
+ * schema:
108
+ * type: integer
109
+ * minimum: 1
110
+ * default: 10
111
+ * description: Maximum number of users
112
+ * - in: query
113
+ * name: page
114
+ * schema:
115
+ * type: integer
116
+ * minimum: 1
117
+ * default: 1
118
+ * description: Page number
119
+ * responses:
120
+ * "200":
121
+ * description: OK
122
+ * content:
123
+ * application/json:
124
+ * schema:
125
+ * type: object
126
+ * properties:
127
+ * results:
128
+ * type: array
129
+ * items:
130
+ * $ref: '#/components/schemas/User'
131
+ * page:
132
+ * type: integer
133
+ * example: 1
134
+ * limit:
135
+ * type: integer
136
+ * example: 10
137
+ * totalPages:
138
+ * type: integer
139
+ * example: 1
140
+ * totalResults:
141
+ * type: integer
142
+ * example: 1
143
+ * "401":
144
+ * $ref: '#/components/responses/Unauthorized'
145
+ * "403":
146
+ * $ref: '#/components/responses/Forbidden'
147
+ */
148
+
149
+ /**
150
+ * @swagger
151
+ * /users/{id}:
152
+ * get:
153
+ * summary: Get a user
154
+ * description: Logged in users can fetch only their own user information. Only admins can fetch other users.
155
+ * tags: [Users]
156
+ * security:
157
+ * - bearerAuth: []
158
+ * parameters:
159
+ * - in: path
160
+ * name: id
161
+ * required: true
162
+ * schema:
163
+ * type: string
164
+ * description: User id
165
+ * responses:
166
+ * "200":
167
+ * description: OK
168
+ * content:
169
+ * application/json:
170
+ * schema:
171
+ * $ref: '#/components/schemas/User'
172
+ * "401":
173
+ * $ref: '#/components/responses/Unauthorized'
174
+ * "403":
175
+ * $ref: '#/components/responses/Forbidden'
176
+ * "404":
177
+ * $ref: '#/components/responses/NotFound'
178
+ *
179
+ * patch:
180
+ * summary: Update a user
181
+ * description: Logged in users can only update their own information. Only admins can update other users.
182
+ * tags: [Users]
183
+ * security:
184
+ * - bearerAuth: []
185
+ * parameters:
186
+ * - in: path
187
+ * name: id
188
+ * required: true
189
+ * schema:
190
+ * type: string
191
+ * description: User id
192
+ * requestBody:
193
+ * required: true
194
+ * content:
195
+ * application/json:
196
+ * schema:
197
+ * type: object
198
+ * properties:
199
+ * name:
200
+ * type: string
201
+ * email:
202
+ * type: string
203
+ * format: email
204
+ * description: must be unique
205
+ * password:
206
+ * type: string
207
+ * format: password
208
+ * minLength: 8
209
+ * description: At least one number and one letter
210
+ * example:
211
+ * name: fake name
212
+ * email: fake@example.com
213
+ * password: password1
214
+ * responses:
215
+ * "200":
216
+ * description: OK
217
+ * content:
218
+ * application/json:
219
+ * schema:
220
+ * $ref: '#/components/schemas/User'
221
+ * "400":
222
+ * $ref: '#/components/responses/DuplicateEmail'
223
+ * "401":
224
+ * $ref: '#/components/responses/Unauthorized'
225
+ * "403":
226
+ * $ref: '#/components/responses/Forbidden'
227
+ * "404":
228
+ * $ref: '#/components/responses/NotFound'
229
+ *
230
+ * delete:
231
+ * summary: Delete a user
232
+ * description: Logged in users can delete only themselves. Only admins can delete other users.
233
+ * tags: [Users]
234
+ * security:
235
+ * - bearerAuth: []
236
+ * parameters:
237
+ * - in: path
238
+ * name: id
239
+ * required: true
240
+ * schema:
241
+ * type: string
242
+ * description: User id
243
+ * responses:
244
+ * "200":
245
+ * description: No content
246
+ * "401":
247
+ * $ref: '#/components/responses/Unauthorized'
248
+ * "403":
249
+ * $ref: '#/components/responses/Forbidden'
250
+ * "404":
251
+ * $ref: '#/components/responses/NotFound'
252
+ */