express-generator-typescript 3.0.3 → 3.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.
Files changed (37) hide show
  1. package/README.md +7 -3
  2. package/bin/cli.js +1 -20
  3. package/lib/template/README.md +4 -3
  4. package/lib/template/package.json +8 -6
  5. package/lib/template/src/common/classes/route-errors.ts +4 -8
  6. package/lib/template/src/common/constants/HttpStatusCodes.ts +2 -7
  7. package/lib/template/src/common/constants/{Paths.ts → Paths/Paths.ts} +16 -9
  8. package/lib/template/src/common/constants/Paths/helpers.ts +29 -0
  9. package/lib/template/src/common/constants/Paths/index.ts +1 -0
  10. package/lib/template/src/common/constants/env.constants.ts +42 -0
  11. package/lib/template/src/common/utils/{date-utils.ts → date.utils.ts} +10 -12
  12. package/lib/template/src/entities/User/module.ts +77 -28
  13. package/lib/template/src/entities/User/types.ts +1 -1
  14. package/lib/template/src/entities/common/{types.ts → entity-base-types.ts} +1 -1
  15. package/lib/template/src/main.ts +3 -4
  16. package/lib/template/src/public/scripts/lib/bootstrap.bundle.min.js +0 -1
  17. package/lib/template/src/public/scripts/users.js +20 -11
  18. package/lib/template/src/public/stylesheets/lib/bootstrap.min.css +0 -1
  19. package/lib/template/src/public/stylesheets/users.css +24 -1
  20. package/lib/template/src/repos/MockOrm.ts +2 -2
  21. package/lib/template/src/repos/UserRepo.ts +20 -27
  22. package/lib/template/src/repos/common/database.json +3 -3
  23. package/lib/template/src/{routes/apiRouter.ts → routers/api.router.ts} +3 -11
  24. package/lib/template/src/{routes/UserRoutes.ts → routers/user.router.ts} +38 -47
  25. package/lib/template/src/server.ts +22 -11
  26. package/lib/template/src/services/UserService.ts +9 -14
  27. package/lib/template/src/views/users.html +8 -0
  28. package/lib/template/tests/support/utils.ts +23 -0
  29. package/lib/template/tests/users.test.ts +182 -140
  30. package/package.json +6 -5
  31. package/lib/template/src/common/constants/env-inv.ts +0 -32
  32. package/lib/template/src/entities/User/_internal/validators.ts +0 -47
  33. package/lib/template/src/routes/common/express-types.ts +0 -9
  34. package/lib/template/src/routes/common/parseReq.ts +0 -23
  35. package/lib/template/tests/common/comparators.ts +0 -38
  36. package/lib/template/tests/common/supertest-types.ts +0 -11
  37. /package/lib/template/src/common/{types/utility-types.ts → utils/validators.ts} +0 -0
@@ -1,7 +1,4 @@
1
- import jetid from 'jet-id';
2
-
3
- import { getISOString } from '@src/common/utils/date-utils';
4
- import type { UserEntity } from '@src/entities/User';
1
+ import { type UserEntity } from '@src/entities/User';
5
2
 
6
3
  import orm from './MockOrm';
7
4
 
@@ -15,9 +12,7 @@ import orm from './MockOrm';
15
12
  async function persists(id: string): Promise<boolean> {
16
13
  const db = await orm.openDb();
17
14
  for (const user of db.users) {
18
- if (user.id === id) {
19
- return true;
20
- }
15
+ if (user.id === id) return true;
21
16
  }
22
17
  return false;
23
18
  }
@@ -27,13 +22,13 @@ async function persists(id: string): Promise<boolean> {
27
22
  */
28
23
  async function getAll(): Promise<UserEntity[]> {
29
24
  const db = await orm.openDb();
30
- return db.users;
25
+ return db.users.sort((a, b) => a.id.localeCompare(b.id));
31
26
  }
32
27
 
33
28
  /**
34
29
  * Add one user.
35
30
  */
36
- async function add(user: UserEntity): Promise<void> {
31
+ async function addOne(user: UserEntity): Promise<void> {
37
32
  const db = await orm.openDb();
38
33
  db.users.push(user);
39
34
  return orm.saveDb(db);
@@ -42,7 +37,7 @@ async function add(user: UserEntity): Promise<void> {
42
37
  /**
43
38
  * Update a user.
44
39
  */
45
- async function update(user: UserEntity): Promise<void> {
40
+ async function updateOne(user: UserEntity): Promise<void> {
46
41
  const db = await orm.openDb();
47
42
  for (let i = 0; i < db.users.length; i++) {
48
43
  if (db.users[i].id === user.id) {
@@ -60,7 +55,7 @@ async function update(user: UserEntity): Promise<void> {
60
55
  /**
61
56
  * Delete one user.
62
57
  */
63
- async function delete_(id: string): Promise<void> {
58
+ async function deleteOne(id: string): Promise<void> {
64
59
  const db = await orm.openDb();
65
60
  for (let i = 0; i < db.users.length; i++) {
66
61
  if (db.users[i].id === id) {
@@ -73,9 +68,9 @@ async function delete_(id: string): Promise<void> {
73
68
  // ============================ Unit-tests Only ============================ //
74
69
 
75
70
  /**
76
- * @testOnly
77
- *
78
71
  * Delete every user record.
72
+ *
73
+ * @testOnly
79
74
  */
80
75
  async function deleteAllUsers(): Promise<void> {
81
76
  const db = await orm.openDb();
@@ -84,34 +79,32 @@ async function deleteAllUsers(): Promise<void> {
84
79
  }
85
80
 
86
81
  /**
87
- * @testOnly
88
- *
89
82
  * Insert copies of `users` with fresh ids; the inputs are not modified.
83
+ *
84
+ * @testOnly
90
85
  */
91
86
  async function insertMultiple(
92
87
  users: UserEntity[] | readonly UserEntity[],
93
88
  ): Promise<UserEntity[]> {
94
89
  const db = await orm.openDb();
95
- const inserted = users.map((user) => ({
96
- ...user,
97
- id: jetid(),
98
- created: getISOString(),
99
- }));
100
- db.users = [...db.users, ...inserted];
90
+ db.users = [...db.users, ...users];
101
91
  await orm.saveDb(db);
102
- return inserted;
92
+ return [...users];
103
93
  }
104
94
 
105
95
  // ========================================================================= //
106
96
  // EXPORT //
107
97
  // ========================================================================= //
108
98
 
99
+ export const UserRepoTestOnly = {
100
+ deleteAllUsers,
101
+ insertMultiple,
102
+ } as const;
103
+
109
104
  export default {
110
105
  persists,
111
106
  getAll,
112
- add,
113
- update,
114
- delete: delete_,
115
- deleteAllUsers,
116
- insertMultiple,
107
+ addOne,
108
+ updateOne,
109
+ deleteOne,
117
110
  } as const;
@@ -1,19 +1,19 @@
1
1
  {
2
2
  "users": [
3
3
  {
4
- "id": "2HF7W6BZV-23RYN-0GY0E-80X19N",
4
+ "id": "01a12230-a18b-721e-bf21-0ee836fa989d",
5
5
  "name": "Sean Maxwell",
6
6
  "email": "smaxwell@example.com",
7
7
  "created": "2024-03-22T05:14:36.252Z"
8
8
  },
9
9
  {
10
- "id": "56N1CSRF5-SG3XG-M7CEY-TE5G45",
10
+ "id": "01a12230-a18c-7473-b1f0-845016f412d3",
11
11
  "name": "John Smith",
12
12
  "email": "john.smith@example.com",
13
13
  "created": "2024-03-22T05:20:55.079Z"
14
14
  },
15
15
  {
16
- "id": "Q78BTED9D-HBPJQ-GFEYQ-3EVFJF",
16
+ "id": "01a12230-a18c-7473-b1f1-88ed3ea1d5ae",
17
17
  "name": "Gordan Freeman",
18
18
  "email": "nova@prospect.com",
19
19
  "created": "2024-03-22T05:42:18.895Z"
@@ -2,7 +2,7 @@ import { Router } from 'express';
2
2
 
3
3
  import Paths from '@src/common/constants/Paths';
4
4
 
5
- import UserRoutes from './UserRoutes';
5
+ import userRouter from './user.router';
6
6
 
7
7
  // ========================================================================= //
8
8
  // EXEC //
@@ -10,16 +10,8 @@ import UserRoutes from './UserRoutes';
10
10
 
11
11
  const apiRouter = Router();
12
12
 
13
- // ============================ Add `userRouter` =========================== //
14
-
15
- const userRouter = Router();
16
-
17
- userRouter.get(Paths.Users.Get._, UserRoutes.getAll);
18
- userRouter.post(Paths.Users.Add._, UserRoutes.add);
19
- userRouter.put(Paths.Users.Update._, UserRoutes.update);
20
- userRouter.delete(Paths.Users.Delete._, UserRoutes.delete);
21
-
22
- apiRouter.use(Paths.Users._, userRouter);
13
+ // Add more routers as your app grows (e.g. `postsRouter`)
14
+ apiRouter.use(Paths.Users.PATH, userRouter);
23
15
 
24
16
  // ========================================================================= //
25
17
  // EXPORT //
@@ -1,74 +1,65 @@
1
+ import { Router } from 'express';
2
+ import validate from 'express-zod-safe';
3
+
1
4
  import HttpStatusCodes from '@src/common/constants/HttpStatusCodes';
5
+ import Paths from '@src/common/constants/Paths';
2
6
  import User from '@src/entities/User';
3
7
  import UserService from '@src/services/UserService';
4
8
 
5
- import type { Req, Res } from './common/express-types';
6
- import parseReq from './common/parseReq';
7
-
8
9
  // ========================================================================= //
9
- // EXEC //
10
+ // CONSTANTS //
10
11
  // ========================================================================= //
11
12
 
12
- const reqValidators = {
13
- add: parseReq({ user: User.isInput }),
14
- update: parseReq({ user: User.isComplete }),
15
- delete: parseReq({ id: User.isId }),
13
+ // Shorthand for the path strings
14
+ const P = {
15
+ GET: Paths.Users.Get.PATH,
16
+ ADD: Paths.Users.Add.PATH,
17
+ UPDATE: Paths.Users.Update.PATH,
18
+ DELETE: Paths.Users.Delete.PATH,
19
+ } as const;
20
+
21
+ // Middlware `validators`
22
+ const V = {
23
+ add: validate({ body: { user: User.Schemas.Input } }),
24
+ update: validate({ body: { user: User.Schemas.Main } }),
25
+ delete: validate({ params: { id: User.Schemas.Id } }),
16
26
  } as const;
17
27
 
18
28
  // ========================================================================= //
19
- // FUNCTIONS //
29
+ // EXEC //
20
30
  // ========================================================================= //
21
31
 
22
- /**
23
- * Get all users.
24
- *
25
- * @route GET /api/users/all
26
- */
27
- async function getAll(_: Req, res: Res) {
32
+ const router = Router();
33
+
34
+ // Get all users
35
+ router.get(P.GET, async (_, res) => {
28
36
  const users = await UserService.getAll();
29
37
  res.status(HttpStatusCodes.OK).json({ users });
30
- }
38
+ });
31
39
 
32
- /**
33
- * Add one user.
34
- *
35
- * @route POST /api/users/add
36
- */
37
- async function add(req: Req, res: Res) {
38
- const { user } = reqValidators.add(req.body);
40
+ // Add one user
41
+ router.post(P.ADD, V.add, async (req, res) => {
42
+ const { user } = req.body;
39
43
  const created = await UserService.addOne(user);
40
44
  res.status(HttpStatusCodes.CREATED).json({ user: created });
41
- }
45
+ });
42
46
 
43
- /**
44
- * Update one user.
45
- *
46
- * @route PUT /api/users/update
47
- */
48
- async function update(req: Req, res: Res) {
49
- const { user } = reqValidators.update(req.body);
47
+ // Update one user
48
+ router.put(P.UPDATE, V.update, async (req, res) => {
49
+ const { user } = req.body;
50
50
  await UserService.updateOne(user);
51
51
  res.status(HttpStatusCodes.OK).end();
52
- }
52
+ });
53
53
 
54
- /**
55
- * Delete one user.
56
- *
57
- * @route DELETE /api/users/delete?id=:id
58
- */
59
- async function delete_(req: Req, res: Res) {
60
- const { id } = reqValidators.delete(req.query);
61
- await UserService.delete(id);
54
+ // Delete one user
55
+ router.delete(P.DELETE, V.delete, async (req, res) => {
56
+ const { id } = req.params;
57
+ await UserService.deleteOne(id);
62
58
  res.status(HttpStatusCodes.OK).end();
63
- }
59
+ });
64
60
 
65
61
  // ========================================================================= //
66
62
  // EXPORT //
67
63
  // ========================================================================= //
68
64
 
69
- export default {
70
- getAll,
71
- add,
72
- update,
73
- delete: delete_,
74
- } as const;
65
+ export default router;
@@ -8,12 +8,12 @@ import logger from 'jet-logger';
8
8
  import morgan from 'morgan';
9
9
  import path from 'path';
10
10
 
11
+ import { RouteError } from '@src/common/classes/route-errors';
11
12
  import HttpStatusCodes from '@src/common/constants/HttpStatusCodes';
12
13
  import Paths from '@src/common/constants/Paths';
13
- import { RouteError } from '@src/common/classes/route-errors';
14
- import BaseRouter from '@src/routes/apiRouter';
14
+ import BaseRouter from '@src/routers/api.router';
15
15
 
16
- import { EnvVars, NodeEnvs } from './common/constants/env-inv';
16
+ import { Env, NodeEnvs } from './common/constants/env.constants';
17
17
 
18
18
  // ========================================================================= //
19
19
  // EXEC //
@@ -28,17 +28,17 @@ app.use(express.json());
28
28
  app.use(express.urlencoded({ extended: true }));
29
29
 
30
30
  // Show routes called in console during development
31
- if (EnvVars.NodeEnv === NodeEnvs.DEV) {
31
+ if (Env.NODE_ENV === NodeEnvs.DEVELOPMENT) {
32
32
  app.use(morgan('dev'));
33
33
  }
34
34
 
35
35
  // Security
36
- if (EnvVars.NodeEnv === NodeEnvs.PRODUCTION) {
36
+ if (Env.NODE_ENV === NodeEnvs.PRODUCTION) {
37
37
  app.use(helmet());
38
38
  }
39
39
 
40
40
  // Add APIs, must be after middleware
41
- app.use(Paths._, BaseRouter);
41
+ app.use(Paths.PATH, BaseRouter);
42
42
 
43
43
  // Add error handler. `_next` must stay: Express only treats 4-arg middleware
44
44
  // as an error handler.
@@ -48,7 +48,20 @@ app.use((err: Error, _: Request, res: Response, _next: NextFunction) => {
48
48
  .status(err.status)
49
49
  .json({ error: err.message, errors: err.errors });
50
50
  }
51
- if (EnvVars.NodeEnv !== NodeEnvs.TEST) {
51
+ // Body-parser errors are client errors; don't expose the rejected body.
52
+ if ('type' in err) {
53
+ if (err.type === 'entity.parse.failed') {
54
+ return res
55
+ .status(HttpStatusCodes.BAD_REQUEST)
56
+ .json({ error: 'Invalid JSON' });
57
+ }
58
+ if (err.type === 'entity.too.large') {
59
+ return res
60
+ .status(HttpStatusCodes.PAYLOAD_TOO_LARGE)
61
+ .json({ error: 'Request body too large' });
62
+ }
63
+ }
64
+ if (Env.NODE_ENV !== NodeEnvs.TEST) {
52
65
  logger.err(err, true);
53
66
  }
54
67
  return res
@@ -58,15 +71,13 @@ app.use((err: Error, _: Request, res: Response, _next: NextFunction) => {
58
71
 
59
72
  // =========================== Front-end Content =========================== //
60
73
 
61
- // Views directory (html)
74
+ // Views/HTML directory (html)
62
75
  const viewsDir = path.join(import.meta.dirname, 'views');
63
-
64
- // Set static directory (js and css).
65
76
  const staticDir = path.join(import.meta.dirname, 'public');
66
77
 
67
78
  // Refresh the browser on changes. Imported lazily because livereload is a
68
79
  // dev dependency and isn't installed in production.
69
- if (EnvVars.NodeEnv === NodeEnvs.DEV) {
80
+ if (Env.NODE_ENV === NodeEnvs.DEVELOPMENT) {
70
81
  const { setupLiveReload } = await import('@src/common/utils/dev-only');
71
82
  setupLiveReload(app, [staticDir, viewsDir]);
72
83
  }
@@ -1,5 +1,4 @@
1
- import HttpStatusCodes from '@src/common/constants/HttpStatusCodes';
2
- import { RouteError } from '@src/common/classes/route-errors';
1
+ import { NotFoundError } from '@src/common/classes/route-errors';
3
2
  import User, { type UserEntity, type UserInput } from '@src/entities/User';
4
3
  import UserRepo from '@src/repos/UserRepo';
5
4
 
@@ -7,7 +6,7 @@ import UserRepo from '@src/repos/UserRepo';
7
6
  // CONSTANTS //
8
7
  // ========================================================================= //
9
8
 
10
- const Errors = {
9
+ const UserServiceErrors = {
11
10
  USER_NOT_FOUND: 'User not found',
12
11
  } as const;
13
12
 
@@ -27,7 +26,7 @@ function getAll(): Promise<UserEntity[]> {
27
26
  */
28
27
  async function addOne(input: UserInput): Promise<UserEntity> {
29
28
  const user = User.create(input);
30
- await UserRepo.add(user);
29
+ await UserRepo.addOne(user);
31
30
  return user;
32
31
  }
33
32
 
@@ -36,10 +35,8 @@ async function addOne(input: UserInput): Promise<UserEntity> {
36
35
  */
37
36
  async function updateOne(user: UserEntity): Promise<void> {
38
37
  const persists = await UserRepo.persists(user.id);
39
- if (!persists) {
40
- throw new RouteError(HttpStatusCodes.NOT_FOUND, Errors.USER_NOT_FOUND);
41
- }
42
- return UserRepo.update(user);
38
+ if (!persists) throw new NotFoundError(UserServiceErrors.USER_NOT_FOUND);
39
+ return UserRepo.updateOne(user);
43
40
  }
44
41
 
45
42
  /**
@@ -47,10 +44,8 @@ async function updateOne(user: UserEntity): Promise<void> {
47
44
  */
48
45
  async function deleteOne(id: string): Promise<void> {
49
46
  const persists = await UserRepo.persists(id);
50
- if (!persists) {
51
- throw new RouteError(HttpStatusCodes.NOT_FOUND, Errors.USER_NOT_FOUND);
52
- }
53
- return UserRepo.delete(id);
47
+ if (!persists) throw new NotFoundError(UserServiceErrors.USER_NOT_FOUND);
48
+ return UserRepo.deleteOne(id);
54
49
  }
55
50
 
56
51
  // ========================================================================= //
@@ -58,9 +53,9 @@ async function deleteOne(id: string): Promise<void> {
58
53
  // ========================================================================= //
59
54
 
60
55
  export default {
61
- Errors,
56
+ Errors: UserServiceErrors,
62
57
  getAll,
63
58
  addOne,
64
59
  updateOne,
65
- delete: deleteOne,
60
+ deleteOne,
66
61
  } as const;
@@ -3,6 +3,7 @@
3
3
  <!-- Head -->
4
4
  <head>
5
5
  <meta charset="UTF-8" />
6
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
6
7
  <title>Users</title>
7
8
  <link rel="stylesheet" type="text/css" href="stylesheets/users.css" />
8
9
 
@@ -50,6 +51,13 @@
50
51
  <div id="all-users-anchor"></div>
51
52
  </div>
52
53
 
54
+ <!-- Error Display -->
55
+ <aside class="users-column errors-column" aria-labelledby="errors-heading">
56
+ <div id="errors-heading" class="column-header">Error Messages:</div>
57
+ <pre id="error-display" role="status" aria-live="polite">
58
+ No errors to display.</pre>
59
+ </aside>
60
+
53
61
  <script src="/scripts/HttpClient.js"></script>
54
62
  <script src="/scripts/renderUsers.js"></script>
55
63
  <script src="/scripts/users.js"></script>
@@ -0,0 +1,23 @@
1
+ import type { Response } from 'supertest';
2
+
3
+ // ========================================================================= //
4
+ // TYPES //
5
+ // ========================================================================= //
6
+
7
+ export type TypedRes<T> = Omit<Response, 'body'> & { body: T };
8
+ export type TypedErrRes<T = 'string'> = TypedRes<{ error: T }>;
9
+
10
+ interface ValidationError {
11
+ errors: { message: string }[];
12
+ }
13
+
14
+ // ========================================================================= //
15
+ // HELPERS //
16
+ // ========================================================================= //
17
+
18
+ /**
19
+ * Get the error message from a superTest Response object.
20
+ */
21
+ export function extractErrMsg(res: TypedRes<ValidationError[]>) {
22
+ return res.body[0].errors[0].message;
23
+ }