@nest-arch/tui 0.2.8 → 0.4.3

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 (92) hide show
  1. package/README.md +20 -42
  2. package/dist/cli.js +1 -1
  3. package/dist/constants-B5MuV-iH.js +1 -0
  4. package/dist/index.js +365 -98
  5. package/dist/templates/addons/husky/pre-commit.hbs +11 -26
  6. package/dist/templates/base/.env.example.hbs +13 -2
  7. package/dist/templates/base/.env.hbs +11 -2
  8. package/dist/templates/base/.gitignore.hbs +2 -1
  9. package/dist/templates/base/.yarnrc.yml.hbs +0 -5
  10. package/dist/templates/base/README.md.hbs +49 -5
  11. package/dist/templates/base/nest-cli.json.hbs +2 -1
  12. package/dist/templates/base/pnpm-workspace.yaml.hbs +15 -0
  13. package/dist/templates/base/prisma.config.ts.hbs +32 -6
  14. package/dist/templates/base/src/app.controller.spec.ts.hbs +3 -11
  15. package/dist/templates/base/src/app.controller.ts.hbs +2 -2
  16. package/dist/templates/base/src/app.module.ts.hbs +76 -5
  17. package/dist/templates/base/src/app.router.ts.hbs +3 -4
  18. package/dist/templates/base/src/database/adapters/interfaces/db-adapter.interface.ts.hbs +7 -0
  19. package/dist/templates/base/src/database/adapters/mongo/prisma-mongo.adapter.ts.hbs +36 -4
  20. package/dist/templates/base/src/database/adapters/mongo/raw-mongo.adapter.ts.hbs +17 -12
  21. package/dist/templates/base/src/database/adapters/sql/drizzle-sql.adapter.ts.hbs +9 -0
  22. package/dist/templates/base/src/database/adapters/sql/prisma-sql.adapter.ts.hbs +8 -5
  23. package/dist/templates/base/src/database/adapters/sql/raw-sql.adapter.ts.hbs +37 -0
  24. package/dist/templates/base/src/database/adapters/sql/typeorm-sql.adapter.ts.hbs +8 -3
  25. package/dist/templates/base/src/database/db.module.ts.hbs +55 -42
  26. package/dist/templates/base/src/database/db.service.ts.hbs +60 -6
  27. package/dist/templates/base/src/database/entities/app.entity.ts.hbs +25 -0
  28. package/dist/templates/base/src/database/entities/todo.entity.ts.hbs +12 -7
  29. package/dist/templates/base/src/database/index.ts.hbs +20 -0
  30. package/dist/templates/base/src/database/orm/drizzle/schema.ts.hbs +80 -7
  31. package/dist/templates/base/src/database/orm/typeorm/data-source.ts.hbs +23 -4
  32. package/dist/templates/base/src/libs/middlewares/auth.trpc.middleware.ts.hbs +47 -0
  33. package/dist/templates/base/src/libs/middlewares/rate-limit.trpc.middleware.ts.hbs +43 -0
  34. package/dist/templates/base/src/libs/trpc/error.handler.ts.hbs +1 -1
  35. package/dist/templates/base/src/libs/trpc/inner.context.ts.hbs +6 -3
  36. package/dist/templates/base/src/main.ts.hbs +34 -10
  37. package/dist/templates/base/src/modules/users/entities/user.entity.ts.hbs +22 -0
  38. package/dist/templates/base/test/app.e2e-spec.ts.hbs +841 -6
  39. package/dist/templates/base/test/bootstrap.e2e-spec.ts.hbs +84 -0
  40. package/dist/templates/base/tsconfig.build.json.hbs +2 -0
  41. package/dist/templates/base/vitest.config.e2e.ts.hbs +4 -3
  42. package/dist/templates/base/vitest.config.ts.hbs +4 -3
  43. package/dist/templates/base/yarn.lock.hbs +1 -0
  44. package/dist/templates/extras/docker/.dockerignore.hbs +8 -1
  45. package/dist/templates/extras/docker/Dockerfile.hbs +11 -10
  46. package/dist/templates/extras/health/src/health/health.controller.ts.hbs +54 -0
  47. package/dist/templates/extras/health/src/health/health.module.ts.hbs +9 -0
  48. package/dist/templates/extras/swagger/create-docs.ts.hbs +13 -22
  49. package/dist/templates/feature/graphql/src/graphql/graphql.module.ts.hbs +6 -10
  50. package/dist/templates/feature/graphql/src/graphql/models/todo.model.ts.hbs +4 -0
  51. package/dist/templates/feature/graphql/src/graphql/orm/drizzle/drizzle-graphql.module.ts.hbs +6 -4
  52. package/dist/templates/feature/graphql/src/graphql/resolvers/health.resolver.ts.hbs +14 -0
  53. package/dist/templates/feature/graphql/src/graphql/resolvers/todo.resolver.ts.hbs +210 -47
  54. package/dist/templates/feature/rest/todo-example/src/api/todo/dto/create-todo.dto.ts.hbs +12 -0
  55. package/dist/templates/feature/rest/todo-example/src/api/todo/dto/update-todo.dto.ts.hbs +12 -0
  56. package/dist/templates/feature/rest/todo-example/src/api/todo/todo.controller.ts.hbs +21 -9
  57. package/dist/templates/feature/rest/todo-example/src/api/todo/todo.module.ts.hbs +1 -3
  58. package/dist/templates/feature/rest/todo-example/src/api/todo/todo.router.ts.hbs +41 -13
  59. package/dist/templates/feature/rest/todo-example/src/api/todo/todo.service.ts.hbs +157 -33
  60. package/dist/templates/monorepo/apps/api-gateway/package.json.hbs +2 -3
  61. package/dist/templates/monorepo/apps/api-gateway/src/app.controller.spec.ts.hbs +6 -1
  62. package/dist/templates/monorepo/apps/api-gateway/src/app.controller.ts.hbs +6 -0
  63. package/dist/templates/monorepo/apps/api-gateway/src/app.module.ts.hbs +32 -1
  64. package/dist/templates/monorepo/apps/api-gateway/src/app.service.ts.hbs +1 -1
  65. package/dist/templates/monorepo/apps/api-gateway/src/main.ts.hbs +13 -4
  66. package/dist/templates/monorepo/apps/api-gateway/test/app.e2e-spec.ts.hbs +45 -1
  67. package/dist/templates/monorepo/apps/api-gateway/tsconfig.build.json.hbs +2 -1
  68. package/dist/templates/monorepo/apps/api-gateway/vitest.config.e2e.ts.hbs +3 -2
  69. package/dist/templates/monorepo/apps/api-gateway/vitest.config.ts.hbs +3 -2
  70. package/dist/templates/monorepo/feature/graphql/src/graphql/graphql.module.ts.hbs +6 -10
  71. package/dist/templates/monorepo/feature/graphql/src/graphql/resolvers/health.resolver.ts.hbs +14 -0
  72. package/dist/templates/monorepo/feature/graphql/src/graphql/resolvers/todo.resolver.ts.hbs +151 -42
  73. package/dist/templates/monorepo/packages/env/src/api-gateway.ts.hbs +16 -3
  74. package/dist/templates/monorepo/packages/shared/package.json.hbs +1 -0
  75. package/dist/templates/monorepo/packages/shared/src/auth.ts.hbs +6 -0
  76. package/dist/templates/monorepo/root/.gitignore.hbs +1 -0
  77. package/dist/templates/monorepo/root/pnpm-workspace.yaml.hbs +19 -2
  78. package/dist/templates/monorepo/root/tsconfig.json.hbs +1 -1
  79. package/dist/templates/monorepo/root/turbo.json.hbs +2 -0
  80. package/dist/templates/single-project/architecture/nest-api/.env.hbs +9 -1
  81. package/dist/templates/single-project/architecture/nest-api/package.json.hbs +1 -2
  82. package/dist/templates/single-project/architecture/nest-api/src/main.ts.hbs +33 -9
  83. package/dist/templates/single-project/architecture/nest-gateway/src/main.ts.hbs +3 -3
  84. package/dist/templates/single-project/architecture/nest-microservice/.env.hbs +18 -0
  85. package/dist/templates/single-project/architecture/nest-microservice/proto/app.proto.hbs +26 -0
  86. package/dist/templates/single-project/architecture/nest-microservice/src/app.controller.ts.hbs +23 -4
  87. package/dist/templates/single-project/architecture/nest-microservice/src/app.service.ts.hbs +6 -2
  88. package/dist/templates/single-project/architecture/nest-microservice/src/main.ts.hbs +16 -5
  89. package/dist/templates/single-project/architecture/nest-microservice/test/microservice.e2e-spec.ts.hbs +70 -0
  90. package/dist/templates/single-project/shared/config/env.ts.hbs +38 -3
  91. package/package.json +5 -2
  92. package/dist/constants-Cgtoxhmd.js +0 -1
@@ -1,4 +1,42 @@
1
+ {{#if (and hasMongodb useRawSql)}}
2
+ import { BadRequestException, Injectable, NotFoundException } from "@nestjs/common";
3
+ import { ObjectId, type WithId } from "mongodb";
4
+ import { DbService } from "../../database/db.service.js";
5
+ import { CreateTodoDto } from "./dto/create-todo.dto.js";
6
+ import { UpdateTodoDto } from "./dto/update-todo.dto.js";
7
+ interface TodoDocument { title: string; completed: boolean; description?: string; }
8
+ const serialize = ({ _id, ...todo }: WithId<TodoDocument>) => ({ ...todo, id: _id.toHexString() });
9
+ function objectId(id: string): ObjectId {
10
+ if (!/^[a-f0-9]{24}$/iu.test(id)) throw new BadRequestException("Invalid todo identifier");
11
+ return new ObjectId(id);
12
+ }
13
+ @Injectable()
14
+ export class TodoService {
15
+ constructor(private readonly db: DbService) {}
16
+ private get todos() { return this.db.raw.mongo.collection<TodoDocument>("todos"); }
17
+ async findAll() { return (await this.todos.find().sort({ _id: 1 }).limit(100).toArray()).map(serialize); }
18
+ async create(input: CreateTodoDto) {
19
+ const todo = { title: input.title, completed: input.completed ?? false, ...(input.description === undefined ? {} : { description: input.description }) };
20
+ const result = await this.todos.insertOne(todo);
21
+ return { ...todo, id: result.insertedId.toHexString() };
22
+ }
23
+ async update(id: string, input: UpdateTodoDto) {
24
+ const todo = await this.todos.findOneAndUpdate({ _id: objectId(id) }, { $set: input }, { returnDocument: "after" });
25
+ if (!todo) throw new NotFoundException("Todo not found");
26
+ return serialize(todo);
27
+ }
28
+ async delete(id: string) {
29
+ const result = await this.todos.deleteOne({ _id: objectId(id) });
30
+ if (result.deletedCount === 0) throw new NotFoundException("Todo not found");
31
+ return { message: "Todo deleted successfully" };
32
+ }
33
+ }
34
+ {{else}}
35
+ {{#if (and hasDatabase hasAuth)}}
36
+ import { Injectable, NotFoundException } from "@nestjs/common";
37
+ {{else}}
1
38
  import { Injectable } from "@nestjs/common";
39
+ {{/if}}
2
40
  {{#if hasDatabase}}
3
41
  {{#if isMonorepo}}
4
42
  import { DbService } from "@repo/db";
@@ -7,8 +45,12 @@ import { DbService } from "../../database/db.service.js";
7
45
  {{/if}}
8
46
  {{#if useDrizzle}}
9
47
  import * as schema from "../../database/orm/drizzle/schema.js";
48
+ {{#if hasAuth}}
49
+ import { and, eq } from "drizzle-orm";
50
+ {{else}}
10
51
  import { eq } from "drizzle-orm";
11
52
  {{/if}}
53
+ {{/if}}
12
54
  {{#if useTypeorm}}
13
55
  import { Todo } from "../../database/entities/todo.entity.js";
14
56
  {{/if}}
@@ -33,83 +75,163 @@ export class TodoService {
33
75
  {{#if hasDatabase}}
34
76
  {{#if hasAuth}}
35
77
  findAll(authorId: string) {
36
- {{#if usePrisma}}
78
+ {{#if isPrisma8}}
79
+ return ((this.db.prisma.orm.public as any).Todo).where({ authorId }).all();
80
+ {{else if usePrisma}}
37
81
  return this.db.prisma.todo.findMany({ where: { authorId } });
38
82
  {{else if useTypeorm}}
39
- return this.db.getRepository(Todo).find({ where: { authorId } });
83
+ return this.db.typeorm.getRepository(Todo).find({ where: { authorId } });
40
84
  {{else if useDrizzle}}
41
- return this.db.select().from(schema.todos).where(eq(schema.todos.authorId, authorId));
85
+ return this.db.drizzle.select().from(schema.todos).where(eq(schema.todos.authorId, authorId));
42
86
  {{/if}}
43
87
  }
44
88
 
45
- create(input: CreateTodoDto, authorId: string) {
46
- {{#if usePrisma}}
47
- return this.db.prisma.todo.create({ data: { ...input, authorId } });
89
+ async create(input: CreateTodoDto, authorId: string) {
90
+ {{#if isPrisma8}}
91
+ return ((this.db.prisma.orm.public as any).Todo).create({ ...input, authorId });
92
+ {{else if usePrisma}}
93
+ const todo = await this.db.prisma.todo.create({ data: { ...input, authorId } });
94
+ return todo;
48
95
  {{else if useTypeorm}}
49
- return this.db.getRepository(Todo).save(this.db.getRepository(Todo).create({ ...input, authorId }));
96
+ const todo = await this.db.typeorm.getRepository(Todo).save(this.db.typeorm.getRepository(Todo).create({ ...input, authorId }));
97
+ return todo;
50
98
  {{else if useDrizzle}}
51
- return this.db.insert(schema.todos).values({ ...input, authorId }).returning();
99
+ {{#if hasMysql}}
100
+ const [inserted] = await this.db.drizzle.insert(schema.todos).values({ ...input, authorId }).$returningId();
101
+ if (!inserted) {
102
+ throw new NotFoundException("Todo not found");
103
+ }
104
+ const [todo] = await this.db.drizzle.select().from(schema.todos).where(and(eq(schema.todos.authorId, authorId), eq(schema.todos.id, inserted.id)));
105
+ return todo;
106
+ {{else}}
107
+ const [todo] = await this.db.drizzle.insert(schema.todos).values({ ...input, authorId }).returning();
108
+ return todo;
109
+ {{/if}}
52
110
  {{/if}}
53
111
  }
54
112
 
55
- update(id: string, input: UpdateTodoDto, authorId: string) {
56
- {{#if usePrisma}}
57
- return this.db.prisma.todo.update({ data: input, where: { authorId, id } });
113
+ async update(id: string, input: UpdateTodoDto, authorId: string) {
114
+ {{#if isPrisma8}}
115
+ const result = await ((this.db.prisma.orm.public as any).Todo).where({ authorId, id }).update(input);
116
+ if (!result) {
117
+ throw new NotFoundException("Todo not found");
118
+ }
119
+ return result;
120
+ {{else if usePrisma}}
121
+ const todo = await this.db.prisma.todo.findFirst({ where: { authorId, id } });
122
+ if (!todo) {
123
+ throw new NotFoundException("Todo not found");
124
+ }
125
+ return this.db.prisma.todo.update({ data: input, where: { id } });
58
126
  {{else if useTypeorm}}
59
- return this.db.getRepository(Todo).update({ authorId, id }, input);
127
+ const repository = this.db.typeorm.getRepository(Todo);
128
+ const result = await repository.update({ authorId, id }, input);
129
+ if (result.affected === 0) {
130
+ throw new NotFoundException("Todo not found");
131
+ }
132
+ const todo = await repository.findOneBy({ authorId, id });
133
+ if (!todo) {
134
+ throw new NotFoundException("Todo not found");
135
+ }
136
+ return todo;
60
137
  {{else if useDrizzle}}
61
- return this.db.update(schema.todos).set(input).where(eq(schema.todos.authorId, authorId) && eq(schema.todos.id, id)).returning();
138
+ {{#if hasMysql}}
139
+ await this.db.drizzle.update(schema.todos).set(input).where(and(eq(schema.todos.authorId, authorId), eq(schema.todos.id, Number(id))));
140
+ const [todo] = await this.db.drizzle.select().from(schema.todos).where(and(eq(schema.todos.authorId, authorId), eq(schema.todos.id, Number(id))));
141
+ {{else}}
142
+ const [todo] = await this.db.drizzle.update(schema.todos).set(input).where(and(eq(schema.todos.authorId, authorId), eq(schema.todos.id, Number(id)))).returning();
143
+ {{/if}}
144
+ if (!todo) {
145
+ throw new NotFoundException("Todo not found");
146
+ }
147
+ return todo;
62
148
  {{/if}}
63
149
  }
64
150
 
65
151
  async delete(id: string, authorId: string) {
66
- {{#if usePrisma}}
67
- await this.db.prisma.todo.delete({ where: { authorId, id } });
152
+ {{#if isPrisma8}}
153
+ const count = await ((this.db.prisma.orm.public as any).Todo).where({ authorId, id }).delete();
154
+ if (count === 0) {
155
+ throw new NotFoundException("Todo not found");
156
+ }
157
+ {{else if usePrisma}}
158
+ const result = await this.db.prisma.todo.deleteMany({ where: { authorId, id } });
159
+ if (result.count === 0) {
160
+ throw new NotFoundException("Todo not found");
161
+ }
68
162
  {{else if useTypeorm}}
69
- await this.db.getRepository(Todo).delete({ authorId, id });
163
+ const result = await this.db.typeorm.getRepository(Todo).delete({ authorId, id });
164
+ if (result.affected === 0) {
165
+ throw new NotFoundException("Todo not found");
166
+ }
70
167
  {{else if useDrizzle}}
71
- await this.db.delete(schema.todos).where(eq(schema.todos.authorId, authorId) && eq(schema.todos.id, id));
168
+ {{#if hasMysql}}
169
+ const [existing] = await this.db.drizzle.select().from(schema.todos).where(and(eq(schema.todos.authorId, authorId), eq(schema.todos.id, Number(id))));
170
+ if (!existing) {
171
+ throw new NotFoundException("Todo not found");
172
+ }
173
+ await this.db.drizzle.delete(schema.todos).where(and(eq(schema.todos.authorId, authorId), eq(schema.todos.id, Number(id))));
174
+ {{else}}
175
+ const [todo] = await this.db.drizzle.delete(schema.todos).where(and(eq(schema.todos.authorId, authorId), eq(schema.todos.id, Number(id)))).returning();
176
+ if (!todo) {
177
+ throw new NotFoundException("Todo not found");
178
+ }
179
+ {{/if}}
72
180
  {{/if}}
73
181
  return { message: "Todo deleted successfully" };
74
182
  }
75
183
  {{else}}
76
184
  findAll() {
77
- {{#if usePrisma}}
185
+ {{#if isPrisma8}}
186
+ return ((this.db.prisma.orm.public as any).Todo).all();
187
+ {{else if usePrisma}}
78
188
  return this.db.prisma.todo.findMany();
79
189
  {{else if useTypeorm}}
80
- return this.db.getRepository(Todo).find();
190
+ return this.db.typeorm.getRepository(Todo).find();
81
191
  {{else if useDrizzle}}
82
- return this.db.select().from(schema.todos);
192
+ return this.db.drizzle.select().from(schema.todos);
83
193
  {{/if}}
84
194
  }
85
195
 
86
- create(input: CreateTodoDto) {
87
- {{#if usePrisma}}
88
- return this.db.prisma.todo.create({ data: input });
196
+ async create(input: CreateTodoDto) {
197
+ {{#if isPrisma8}}
198
+ return ((this.db.prisma.orm.public as any).Todo).create(input);
199
+ {{else if usePrisma}}
200
+ const todo = await this.db.prisma.todo.create({ data: input });
201
+ return todo;
89
202
  {{else if useTypeorm}}
90
- return this.db.getRepository(Todo).save(this.db.getRepository(Todo).create(input));
203
+ const todo = await this.db.typeorm.getRepository(Todo).save(this.db.typeorm.getRepository(Todo).create(input));
204
+ return todo;
91
205
  {{else if useDrizzle}}
92
- return this.db.insert(schema.todos).values(input).returning();
206
+ const [todo] = await this.db.drizzle.insert(schema.todos).values(input).returning();
207
+ return todo;
93
208
  {{/if}}
94
209
  }
95
210
 
96
- update(id: string, input: UpdateTodoDto) {
97
- {{#if usePrisma}}
98
- return this.db.prisma.todo.update({ data: input, where: { id } });
211
+ async update(id: string, input: UpdateTodoDto) {
212
+ {{#if isPrisma8}}
213
+ return ((this.db.prisma.orm.public as any).Todo).where({ id }).update(input);
214
+ {{else if usePrisma}}
215
+ const result = await this.db.prisma.todo.update({ data: input, where: { id } });
216
+ return result;
99
217
  {{else if useTypeorm}}
100
- return this.db.getRepository(Todo).update(id, input);
218
+ const result = await this.db.typeorm.getRepository(Todo).update(id, input);
219
+ return result;
101
220
  {{else if useDrizzle}}
102
- return this.db.update(schema.todos).set(input).where(eq(schema.todos.id, id)).returning();
221
+ const [todo] = await this.db.drizzle.update(schema.todos).set(input).where(eq(schema.todos.id, Number(id))).returning();
222
+ return todo;
103
223
  {{/if}}
104
224
  }
105
225
 
106
226
  async delete(id: string) {
107
- {{#if usePrisma}}
227
+ {{#if isPrisma8}}
228
+ await ((this.db.prisma.orm.public as any).Todo).where({ id }).delete();
229
+ {{else if usePrisma}}
108
230
  await this.db.prisma.todo.delete({ where: { id } });
109
231
  {{else if useTypeorm}}
110
- await this.db.getRepository(Todo).delete(id);
232
+ await this.db.typeorm.getRepository(Todo).delete(id);
111
233
  {{else if useDrizzle}}
112
- await this.db.delete(schema.todos).where(eq(schema.todos.id, id));
234
+ await this.db.drizzle.delete(schema.todos).where(eq(schema.todos.id, Number(id)));
113
235
  {{/if}}
114
236
  return { message: "Todo deleted successfully" };
115
237
  }
@@ -143,3 +265,5 @@ export class TodoService {
143
265
  }
144
266
  {{/if}}
145
267
  }
268
+
269
+ {{/if}}
@@ -1,5 +1,5 @@
1
1
  {
2
- "name": "{{projectName}}",
2
+ "name": "@repo/api-gateway",
3
3
  "version": "0.0.1",
4
4
  "private": true,
5
5
  "description": "{{description}}",
@@ -20,7 +20,7 @@
20
20
  "test:debug": "vitest --inspect-brk --no-file-parallelism",
21
21
  "test:e2e": "vitest run --config ./vitest.config.e2e.ts"{{#if hasUltracite}},
22
22
  "check": "ultracite check",
23
- "fix": "ultracite fix"{{/if}}{{#if hasHusky}},
23
+ "fix": "ultracite fix --ignore-pattern migrations/**"{{/if}}{{#if hasHusky}},
24
24
  "prepare": "husky"{{/if}}
25
25
  },
26
26
  "dependencies": {
@@ -45,7 +45,6 @@
45
45
  "source-map-support": "^0.5.21",
46
46
  "supertest": "^7.0.0",
47
47
  "typescript": "catalog:",
48
- "vite-tsconfig-paths": "^5.1.4",
49
48
  {{#if hasUltracite}}"ultracite": "7.8.0",{{/if}}
50
49
  "vitest": "^4.1.2"
51
50
  }
@@ -1,14 +1,19 @@
1
1
  // oxlint-disable vitest/require-mock-type-parameters
2
2
  import type { TestingModule } from "@nestjs/testing";
3
3
  import { Test } from "@nestjs/testing";
4
- import { beforeEach, describe, expect, it, vi } from "vitest";
4
+ import { beforeEach, describe, expect, it{{#if (or hasDatabase hasOrm)}}, vi{{/if}} } from "vitest";
5
5
 
6
6
  import { AppController } from "./app.controller.js";
7
7
  import { AppService } from "./app.service.js";
8
8
  {{#if (or hasDatabase hasOrm)}}
9
+ {{#if isMonorepo}}
10
+ import type { ISqlAdapter } from "@repo/db";
11
+ import { DbService } from "@repo/db";
12
+ {{else}}
9
13
  import type { ISqlAdapter } from "./database/adapters/interfaces/db-adapter.interface.js";
10
14
  import { DbService } from "./database/db.service.js";
11
15
  {{/if}}
16
+ {{/if}}
12
17
 
13
18
  {{#if (or hasDatabase hasOrm)}}
14
19
  const mockSqlAdapter: ISqlAdapter = {
@@ -1,4 +1,7 @@
1
1
  import { Controller, Get } from "@nestjs/common";
2
+ {{#if (eq auth "better-auth")}}
3
+ import { AllowAnonymous } from "@thallesp/nestjs-better-auth";
4
+ {{/if}}
2
5
  {{#if swagger}}
3
6
  import { ApiOperation, ApiResponse, ApiTags } from "@nestjs/swagger";
4
7
  {{/if}}
@@ -16,6 +19,9 @@ export class AppController {
16
19
  @ApiOperation({ summary: "/api" })
17
20
  @ApiResponse({ status: 200, description: "Returns a hello world message" })
18
21
  {{/if}}
22
+ {{#if (eq auth "better-auth")}}
23
+ @AllowAnonymous()
24
+ {{/if}}
19
25
  @Get()
20
26
  getHello(): string {
21
27
  return this.appService.getHello();
@@ -1,7 +1,11 @@
1
1
  import { Module } from "@nestjs/common";
2
2
  {{#if (or hasDatabase hasOrm)}}
3
+ {{#if isMonorepo}}
4
+ import { DbModule } from "@repo/db";
5
+ {{else}}
3
6
  import { DbModule } from "./database/db.module.js";
4
- import { env } from "./config/env.js";
7
+ {{/if}}
8
+ import { env } from "@repo/env/api-gateway";
5
9
  {{/if}}
6
10
  {{#if (or (eq auth "better-auth") (eq auth "better-auth:stateless"))}}
7
11
  import { AuthModule } from "./auth/auth.module.js";
@@ -17,6 +21,13 @@ import { AppErrorHandler } from "./libs/trpc/error.handler.js";
17
21
  {{#if hasTrpcAndAuth}}
18
22
  import { AuthTRPCMiddleware } from "./libs/middlewares/auth.trpc.middleware.js";
19
23
  {{/if}}
24
+ {{#if (and hasTrpc hasRateLimiting)}}
25
+ import { RateLimitTRPCMiddleware } from "./libs/middlewares/rate-limit.trpc.middleware.js";
26
+ {{/if}}
27
+ {{/if}}
28
+ {{#if hasRateLimiting}}
29
+ import { APP_GUARD } from "@nestjs/core";
30
+ import { ThrottlerGuard, ThrottlerModule } from "@nestjs/throttler";
20
31
  {{/if}}
21
32
 
22
33
  import { AppController } from "./app.controller.js";
@@ -51,8 +62,19 @@ import { AppService } from "./app.service.js";
51
62
  TRPCModule.forRoot({
52
63
  context: AppContext,
53
64
  onError: AppErrorHandler,
65
+ {{#if hasRateLimiting}}
66
+ globalMiddlewares: [RateLimitTRPCMiddleware],
67
+ {{/if}}
54
68
  }),
55
69
  {{/if}}
70
+ {{#if hasRateLimiting}}
71
+ ThrottlerModule.forRoot([
72
+ {
73
+ ttl: 60_000,
74
+ limit: 60,
75
+ },
76
+ ]),
77
+ {{/if}}
56
78
  {{#if (and hasTodoExample (or hasRest hasTrpc))}}
57
79
  TodoModule,
58
80
  {{/if}}
@@ -63,6 +85,9 @@ import { AppService } from "./app.service.js";
63
85
  {{#if hasTrpcAndAuth}}
64
86
  AuthTRPCMiddleware,
65
87
  {{/if}}
88
+ {{#if hasRateLimiting}}
89
+ RateLimitTRPCMiddleware,
90
+ {{/if}}
66
91
  AppContext,
67
92
  AppErrorHandler,
68
93
  AppRouter,
@@ -70,6 +95,12 @@ import { AppService } from "./app.service.js";
70
95
  TodoRouter,
71
96
  {{/if}}
72
97
  {{/if}}
98
+ {{#if hasRateLimiting}}
99
+ {
100
+ provide: APP_GUARD,
101
+ useClass: ThrottlerGuard,
102
+ },
103
+ {{/if}}
73
104
  ],
74
105
  })
75
106
  export class AppModule {}
@@ -1,6 +1,6 @@
1
1
  import { Injectable } from "@nestjs/common";
2
2
  {{#if (or hasDatabase hasOrm)}}
3
- import { DbService } from "./database/db.service.js";
3
+ import { DbService } from "@repo/db";
4
4
  {{/if}}
5
5
 
6
6
  @Injectable()
@@ -16,11 +16,20 @@ import { apiReference } from "@scalar/nestjs-api-reference";
16
16
  import { SwaggerModule } from "@nestjs/swagger";
17
17
  {{/if}}
18
18
  {{#if (and swagger (or (eq auth "better-auth") (eq auth "better-auth:stateless")))}}
19
- import { auth } from "./auth/auth.js";
19
+ import { AuthService } from "@thallesp/nestjs-better-auth";
20
20
  {{/if}}
21
21
 
22
22
  async function bootstrap() {
23
- const app = await NestFactory.create{{#if (eq httpProvider "fastify")}}<NestFastifyApplication>(AppModule, new FastifyAdapter()){{else}}(AppModule){{/if}};
23
+ const trustedProxyIps = env.TRUSTED_PROXY_IPS
24
+ .split(",")
25
+ .map((ip) => ip.trim())
26
+ .filter(Boolean);
27
+ const app = await NestFactory.create{{#if (eq httpProvider "fastify")}}<NestFastifyApplication>(AppModule, new FastifyAdapter({ trustProxy: trustedProxyIps })){{else}}(AppModule){{/if}};
28
+ {{#unless (eq httpProvider "fastify")}}
29
+ if (trustedProxyIps.length > 0) {
30
+ app.getHttpAdapter().getInstance().set("trust proxy", trustedProxyIps);
31
+ }
32
+ {{/unless}}
24
33
  app.enableCors({
25
34
  origin: env.CORS_ORIGIN,
26
35
  });
@@ -38,7 +47,7 @@ async function bootstrap() {
38
47
  integrations: {
39
48
  {{#if (or (eq auth "better-auth") (eq auth "better-auth:stateless"))}}
40
49
  betterAuth: {
41
- auth,
50
+ auth: app.get(AuthService).instance,
42
51
  pathPrefix: "/api/auth",
43
52
  },
44
53
  {{/if}}
@@ -65,7 +74,7 @@ async function bootstrap() {
65
74
  integrations: {
66
75
  {{#if (or (eq auth "better-auth") (eq auth "better-auth:stateless"))}}
67
76
  betterAuth: {
68
- auth,
77
+ auth: app.get(AuthService).instance,
69
78
  pathPrefix: "/api/auth",
70
79
  },
71
80
  {{/if}}
@@ -1,6 +1,10 @@
1
1
  import { INestApplication } from "@nestjs/common";
2
2
  import { Test, TestingModule } from "@nestjs/testing";
3
+ {{#if (eq httpProvider "fastify")}}
4
+ import { FastifyAdapter } from "@nestjs/platform-fastify";
5
+ {{/if}}
3
6
  import request from "supertest";
7
+ import { afterEach, beforeEach, describe, expect, it } from "vitest";
4
8
  import type { App } from "supertest/types";
5
9
 
6
10
  import { AppModule } from "./../src/app.module.js";
@@ -13,15 +17,55 @@ describe("AppController (e2e)", () => {
13
17
  imports: [AppModule],
14
18
  }).compile();
15
19
 
20
+ {{#if (eq httpProvider "fastify")}}
21
+ app = moduleFixture.createNestApplication(new FastifyAdapter());
22
+ {{else}}
16
23
  app = moduleFixture.createNestApplication();
24
+ {{/if}}
17
25
  await app.init();
26
+ {{#if (eq httpProvider "fastify")}}
27
+ await app.getHttpAdapter().getInstance().ready();
28
+ {{/if}}
18
29
  });
19
30
 
20
31
  it("/ (GET)", () => {
21
32
  return request(app.getHttpServer()).get("/").expect(200).expect("Hello World!");
22
33
  });
23
34
 
35
+ it("returns 404 for an unknown route", async () => {
36
+ await request(app.getHttpServer()).get("/__missing_route__").expect(404);
37
+ });
38
+
39
+ it("rejects unsupported methods", async () => {
40
+ await request(app.getHttpServer()).post("/").send({ unexpected: true }).expect(404);
41
+ });
42
+
43
+ {{#if (eq auth "better-auth")}}
44
+ it("creates a user and resolves the authenticated session", async () => {
45
+ const email = `monorepo-${Date.now()}@example.com`;
46
+ const signup = await request(app.getHttpServer())
47
+ .post("/api/auth/sign-up/email")
48
+ .set("origin", "http://localhost:3000")
49
+ .send({ email, name: "Monorepo Scenario", password: "Scenario-password-123!" })
50
+ .expect(200);
51
+ const cookies = (signup.headers["set-cookie"] ?? []).map((cookie) => cookie.split(";")[0]);
52
+ expect(cookies.length).toBeGreaterThan(0);
53
+
54
+ const session = await request(app.getHttpServer())
55
+ .get("/api/auth/get-session")
56
+ .set("cookie", cookies.join("; "))
57
+ .expect(200);
58
+ expect(session.body.user.email).toBe(email);
59
+
60
+ const profile = await request(app.getHttpServer())
61
+ .get("/auth-profile")
62
+ .set("cookie", cookies.join("; "))
63
+ .expect(200);
64
+ expect(profile.body).toEqual({ id: session.body.user.id, email });
65
+ });
66
+ {{/if}}
67
+
24
68
  afterEach(async () => {
25
- await app.close();
69
+ await app?.close();
26
70
  });
27
71
  });
@@ -2,7 +2,8 @@
2
2
  "$schema": "https://json.schemastore.org/tsconfig",
3
3
  "extends": "./tsconfig.json",
4
4
  "compilerOptions": {
5
- "outDir": "./dist"
5
+ "outDir": "./dist",
6
+ "rootDir": "./src"
6
7
  },
7
8
  "include": ["src/**/*"],
8
9
  "exclude": ["node_modules", "test", "dist", "**/*spec.ts"]
@@ -1,8 +1,9 @@
1
1
  import { defineConfig } from "vitest/config";
2
- import tsconfigPaths from "vite-tsconfig-paths";
3
2
 
4
3
  export default defineConfig({
5
- plugins: [tsconfigPaths()],
4
+ resolve: {
5
+ tsconfigPaths: true,
6
+ },
6
7
  test: {
7
8
  globals: true,
8
9
  root: "./",
@@ -1,8 +1,9 @@
1
1
  import { defineConfig } from "vitest/config";
2
- import tsconfigPaths from "vite-tsconfig-paths";
3
2
 
4
3
  export default defineConfig({
5
- plugins: [tsconfigPaths()],
4
+ resolve: {
5
+ tsconfigPaths: true,
6
+ },
6
7
  test: {
7
8
  globals: true,
8
9
  root: "./",
@@ -1,13 +1,10 @@
1
1
  import { Module } from "@nestjs/common";
2
2
  import { GraphQLModule } from "@nestjs/graphql";
3
3
  import { ApolloDriver, ApolloDriverConfig } from "@nestjs/apollo";
4
- import { join } from "path";
4
+ import type { IncomingMessage } from "node:http";
5
+ import path from "node:path";
5
6
  import { env } from "@repo/env/api-gateway";
6
- {{#if (eq httpProvider "fastify")}}
7
- // WARNING: If you use @as-integrations/fastify with multiple GraphQL endpoints
8
- // in a single application, make sure to enable the disableHealthCheck setting
9
- // in the GraphQLModule configuration below.
10
- {{/if}}
7
+ import { HealthResolver } from "./resolvers/health.resolver.js";
11
8
  {{#if hasTodoExample}}
12
9
  import { TodoResolver } from "./resolvers/todo.resolver.js";
13
10
  {{/if}}
@@ -16,15 +13,14 @@ import { TodoResolver } from "./resolvers/todo.resolver.js";
16
13
  imports: [
17
14
  GraphQLModule.forRoot<ApolloDriverConfig>({
18
15
  driver: ApolloDriver,
19
- autoSchemaFile: join(process.cwd(), "apps/api-gateway/src/schema.gql"),
16
+ autoSchemaFile: path.join(process.cwd(), "apps/api-gateway/src/schema.gql"),
20
17
  graphiql: env.GRAPHQL_PLAYGROUND,
18
+ context: ({ req, res }: { req: IncomingMessage; res: unknown }) => ({ req, res }),
21
19
  sortSchema: true,
22
- {{#if (eq httpProvider "fastify")}}
23
- disableHealthCheck: true,
24
- {{/if}}
25
20
  }),
26
21
  ],
27
22
  providers: [
23
+ HealthResolver,
28
24
  {{#if hasTodoExample}}
29
25
  TodoResolver,
30
26
  {{/if}}
@@ -0,0 +1,14 @@
1
+ import { Query, Resolver } from "@nestjs/graphql";
2
+ {{#if isBetterAuth}}
3
+ import { AllowAnonymous } from "@thallesp/nestjs-better-auth";
4
+ {{/if}}
5
+ {{#if isBetterAuth}}
6
+ @AllowAnonymous()
7
+ {{/if}}
8
+ @Resolver()
9
+ export class HealthResolver {
10
+ @Query(() => String)
11
+ hello(): string {
12
+ return "Hello GraphQL!";
13
+ }
14
+ }