stackkit 0.2.3 → 0.2.7

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 (99) hide show
  1. package/dist/cli/add.js +2 -16
  2. package/dist/cli/create.js +2 -5
  3. package/dist/cli/doctor.js +0 -4
  4. package/dist/cli/list.js +73 -83
  5. package/dist/index.js +25 -44
  6. package/dist/lib/constants.d.ts +110 -0
  7. package/dist/lib/constants.js +112 -0
  8. package/dist/lib/conversion/js-conversion.js +27 -11
  9. package/dist/lib/discovery/installed-detection.js +3 -3
  10. package/dist/lib/discovery/module-discovery.d.ts +1 -1
  11. package/dist/lib/discovery/module-discovery.js +22 -6
  12. package/dist/lib/env/env-editor.js +41 -47
  13. package/dist/lib/fs/files.d.ts +0 -1
  14. package/dist/lib/fs/files.js +12 -40
  15. package/dist/lib/generation/code-generator.d.ts +4 -1
  16. package/dist/lib/generation/code-generator.js +39 -13
  17. package/dist/lib/pm/package-manager.d.ts +1 -1
  18. package/dist/lib/pm/package-manager.js +130 -14
  19. package/dist/lib/ui/logger.d.ts +8 -1
  20. package/dist/lib/ui/logger.js +60 -3
  21. package/dist/lib/utils/fs-helpers.d.ts +12 -0
  22. package/dist/lib/utils/fs-helpers.js +61 -0
  23. package/dist/lib/utils/json-loader.d.ts +6 -0
  24. package/dist/lib/utils/json-loader.js +34 -0
  25. package/dist/lib/utils/module-loader.d.ts +9 -0
  26. package/dist/lib/utils/module-loader.js +98 -0
  27. package/dist/lib/utils/package-root.d.ts +1 -0
  28. package/dist/lib/utils/package-root.js +75 -2
  29. package/dist/lib/utils/path-resolver.d.ts +9 -0
  30. package/dist/lib/utils/path-resolver.js +44 -0
  31. package/modules/auth/authjs/files/nextjs/api/auth/[...nextauth]/route.ts +3 -0
  32. package/modules/auth/authjs/files/nextjs/proxy.ts +1 -0
  33. package/modules/auth/authjs/files/shared/lib/auth.ts +119 -0
  34. package/modules/auth/authjs/files/{prisma → shared/prisma}/schema.prisma +11 -1
  35. package/modules/auth/authjs/generator.json +18 -8
  36. package/modules/auth/better-auth/files/express/middlewares/authorize.ts +54 -0
  37. package/modules/auth/better-auth/files/express/types/express.d.ts +16 -0
  38. package/modules/auth/better-auth/files/nextjs/lib/auth/auth-guards.ts +31 -0
  39. package/modules/auth/better-auth/files/nextjs/proxy.ts +34 -0
  40. package/modules/auth/better-auth/files/{lib → shared/lib}/auth-client.ts +1 -1
  41. package/modules/auth/better-auth/files/{lib → shared/lib}/auth.ts +46 -20
  42. package/modules/auth/better-auth/files/{prisma → shared/prisma}/schema.prisma +11 -2
  43. package/modules/auth/better-auth/generator.json +74 -19
  44. package/modules/database/mongoose/generator.json +16 -2
  45. package/modules/database/prisma/files/lib/prisma.ts +1 -1
  46. package/modules/database/prisma/files/prisma/schema.prisma +1 -2
  47. package/modules/database/prisma/generator.json +8 -1
  48. package/package.json +7 -7
  49. package/templates/express/env.example +2 -1
  50. package/templates/express/package.json +3 -4
  51. package/templates/express/src/app.ts +18 -25
  52. package/templates/express/src/config/cors.ts +12 -0
  53. package/templates/express/src/config/helmet.ts +5 -0
  54. package/templates/express/src/config/logger.ts +6 -0
  55. package/templates/express/src/config/rate-limit.ts +11 -0
  56. package/templates/express/src/{features → modules}/health/health.route.ts +1 -1
  57. package/templates/express/src/routes/index.ts +12 -0
  58. package/templates/express/src/shared/errors/api-error.ts +14 -0
  59. package/templates/express/src/shared/errors/error-codes.ts +9 -0
  60. package/templates/express/src/shared/logger/logger.ts +20 -0
  61. package/templates/express/src/{middlewares → shared/middlewares}/error.middleware.ts +1 -1
  62. package/templates/express/src/shared/middlewares/not-found.middleware.ts +9 -0
  63. package/templates/express/src/shared/utils/async-handler.ts +9 -0
  64. package/templates/express/src/shared/utils/pagination.ts +6 -0
  65. package/templates/express/src/shared/utils/response.ts +9 -0
  66. package/templates/express/tsconfig.json +9 -3
  67. package/templates/nextjs/next-env.d.ts +6 -0
  68. package/templates/react/dist/assets/index-D4AHT4dU.js +193 -0
  69. package/templates/react/dist/assets/index-rpwj5ZOX.css +1 -0
  70. package/templates/react/dist/index.html +14 -0
  71. package/templates/react/dist/vite.svg +1 -0
  72. package/templates/react/src/app/layouts/dashboard-layout.tsx +8 -0
  73. package/templates/react/src/app/layouts/public-layout.tsx +5 -0
  74. package/templates/react/src/app/providers.tsx +20 -0
  75. package/templates/react/src/app/router.tsx +21 -0
  76. package/templates/react/src/{pages/About.tsx → features/about/pages/about.tsx} +1 -1
  77. package/templates/react/src/{pages/Home.tsx → features/home/pages/home.tsx} +1 -1
  78. package/templates/react/src/main.tsx +2 -2
  79. package/templates/react/src/{api/client.ts → shared/api/http.ts} +1 -1
  80. package/templates/react/src/{pages/NotFound.tsx → shared/pages/not-found.tsx} +1 -1
  81. package/dist/lib/git-utils.d.ts +0 -1
  82. package/dist/lib/git-utils.js +0 -29
  83. package/modules/auth/authjs/files/api/auth/[...nextauth]/route.ts +0 -2
  84. package/modules/auth/authjs/files/lib/auth.ts +0 -22
  85. package/templates/express/.env.example +0 -2
  86. package/templates/nextjs/.env.example +0 -1
  87. package/templates/react/.env.example +0 -1
  88. package/templates/react/.prettierignore +0 -4
  89. package/templates/react/.prettierrc +0 -9
  90. /package/modules/auth/better-auth/files/{api → nextjs/api}/auth/[...all]/route.ts +0 -0
  91. /package/modules/auth/better-auth/files/{lib → shared/lib/email}/email-service.ts +0 -0
  92. /package/modules/auth/better-auth/files/{lib → shared/lib/email}/email-templates.ts +0 -0
  93. /package/templates/express/src/{features → modules}/health/health.controller.ts +0 -0
  94. /package/templates/express/src/{features → modules}/health/health.service.ts +0 -0
  95. /package/templates/react/src/{components/ErrorBoundary.tsx → shared/components/error-boundary.tsx} +0 -0
  96. /package/templates/react/src/{components/Layout.tsx → shared/components/layout.tsx} +0 -0
  97. /package/templates/react/src/{components/Loading.tsx → shared/components/loading.tsx} +0 -0
  98. /package/templates/react/src/{components/SEO.tsx → shared/components/seo.tsx} +0 -0
  99. /package/templates/react/src/{lib/queryClient.ts → shared/lib/query-client.ts} +0 -0
@@ -1,35 +1,54 @@
1
1
  import { betterAuth } from "better-auth";
2
- import { sendEmail } from "./email/email-service";
3
- import { getVerificationEmailTemplate, getPasswordResetEmailTemplate } from "./email/email-templates";
4
- {{#switch database}}
5
- {{#case prisma}}
6
- import { prisma } from "./prisma";
2
+ {{#if combo == "prisma:express"}}
3
+ import { sendEmail } from "../../shared/email/email-service";
4
+ import {
5
+ getPasswordResetEmailTemplate,
6
+ getVerificationEmailTemplate,
7
+ } from "../../shared/email/email-templates";
8
+ import { prisma } from "../../database/prisma";
7
9
  import { prismaAdapter } from "better-auth/adapters/prisma";
8
- {{/case}}
9
- {{#case mongoose}}
10
- import { mongoose } from "./mongoose";
10
+ {{/if}}
11
+
12
+ {{#if combo == "prisma:nextjs"}}
13
+ import { getPasswordResetEmailTemplate, getVerificationEmailTemplate } from "../service/email/email-templates";
14
+ import { sendEmail } from "../service/email/email-service";
15
+ import { prisma } from "../database/prisma";
16
+ import { prismaAdapter } from "better-auth/adapters/prisma";
17
+ {{/if}}
18
+
19
+ {{#if combo == "mongoose:express"}}
20
+ import { sendEmail } from "../../shared/email/email-service";
21
+ import {
22
+ getPasswordResetEmailTemplate,
23
+ getVerificationEmailTemplate,
24
+ } from "../../shared/email/email-templates";
25
+ import { mongoose } from "../../database/mongoose";
26
+ import { mongodbAdapter } from "better-auth/adapters/mongodb";
27
+ {{/if}}
28
+
29
+ {{#if combo == "mongoose:nextjs"}}
30
+ import { getPasswordResetEmailTemplate, getVerificationEmailTemplate } from "../service/email/email-templates";
31
+ import { sendEmail } from "../service/email/email-service";
32
+ import { mongoose } from "../database/mongoose";
11
33
  import { mongodbAdapter } from "better-auth/adapters/mongodb";
12
- {{/case}}
13
- {{/switch}}
34
+ {{/if}}
14
35
 
15
36
  export async function initAuth() {
16
- {{#if database == 'mongoose'}}
37
+ {{#if database == "mongoose"}}
17
38
  const mongooseInstance = await mongoose();
18
39
  const client = mongooseInstance.connection.getClient();
19
40
  const db = client.db();
20
41
  {{/if}}
21
42
 
22
43
  return betterAuth({
23
- {{#switch database}}
24
- {{#case prisma}}
44
+ {{#if database == "prisma"}}
25
45
  database: prismaAdapter(prisma, {
26
46
  provider: "{{prismaProvider}}",
27
47
  }),
28
- {{/case}}
29
- {{#case mongoose}}
48
+ {{/if}}
49
+ {{#if database == "mongoose"}}
30
50
  database: mongodbAdapter(db, { client }),
31
- {{/case}}
32
- {{/switch}}
51
+ {{/if}}
33
52
  baseURL: process.env.BETTER_AUTH_URL,
34
53
  secret: process.env.BETTER_AUTH_SECRET,
35
54
  trustedOrigins: [process.env.APP_URL!],
@@ -57,8 +76,10 @@ return betterAuth({
57
76
  },
58
77
  socialProviders: {
59
78
  google: {
60
- clientId: process.env.GOOGLE_CLIENT_ID!,
61
- clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
79
+ accessType: "offline",
80
+ prompt: "select_account consent",
81
+ clientId: process.env.GOOGLE_CLIENT_ID as string,
82
+ clientSecret: process.env.GOOGLE_CLIENT_SECRET as string,
62
83
  },
63
84
  },
64
85
  emailVerification: {
@@ -95,4 +116,9 @@ return betterAuth({
95
116
  })
96
117
  };
97
118
 
98
- export const auth = await initAuth();
119
+ export let auth: any = undefined;
120
+
121
+ export async function setupAuth() {
122
+ auth = await initAuth();
123
+ return auth;
124
+ }
@@ -1,4 +1,8 @@
1
- {{#var defaultId = {{#if prismaProvider == "mongodb"}}@default(auto()) @map("_id") @db.ObjectId{{else}}@default(cuid()){{/if}}}}
1
+ {{#if prismaProvider == "mongodb"}}
2
+ {{#var defaultId = @default(auto()) @map("_id") @db.ObjectId}}
3
+ {{else}}
4
+ {{#var defaultId = @default(cuid())}}
5
+ {{/if}}
2
6
  model User {
3
7
  id String @id {{defaultId}}
4
8
  name String
@@ -9,7 +13,7 @@ model User {
9
13
  updatedAt DateTime @updatedAt
10
14
  sessions Session[]
11
15
  accounts Account[]
12
- role String @default("USER")
16
+ role Role @default(USER)
13
17
 
14
18
  @@unique([email])
15
19
  @@map("user")
@@ -61,3 +65,8 @@ model Verification {
61
65
  @@index([identifier])
62
66
  @@map("verification")
63
67
  }
68
+
69
+ enum Role {
70
+ USER
71
+ ADMIN
72
+ }
@@ -5,33 +5,81 @@
5
5
  "operations": [
6
6
  {
7
7
  "type": "create-file",
8
- "source": "lib/auth.ts",
9
- "destination": "lib/auth.ts",
10
- "condition": { "framework": ["nextjs", "express"] }
8
+ "source": "shared/lib/auth.ts",
9
+ "destination": "server/auth/auth.ts",
10
+ "condition": { "framework": ["nextjs"] }
11
11
  },
12
12
  {
13
13
  "type": "create-file",
14
- "source": "lib/email-service.ts",
15
- "destination": "lib/email/email-service.ts",
16
- "condition": { "framework": ["nextjs", "express"] }
14
+ "source": "shared/lib/auth.ts",
15
+ "destination": "src/modules/auth/auth.ts",
16
+ "condition": { "framework": ["express"] }
17
17
  },
18
18
  {
19
19
  "type": "create-file",
20
- "source": "lib/email-templates.ts",
21
- "destination": "lib/email/email-templates.ts",
22
- "condition": { "framework": ["nextjs", "express"] }
20
+ "source": "shared/lib/email/email-service.ts",
21
+ "destination": "server/service/email/email-service.ts",
22
+ "condition": { "framework": ["nextjs"] }
23
23
  },
24
24
  {
25
25
  "type": "create-file",
26
- "source": "api/auth/[...all]/route.ts",
26
+ "source": "shared/lib/email/email-service.ts",
27
+ "destination": "src/shared/email/email-service.ts",
28
+ "condition": { "framework": ["express"] }
29
+ },
30
+ {
31
+ "type": "create-file",
32
+ "source": "shared/lib/email/email-templates.ts",
33
+ "destination": "server/service/email/email-templates.ts",
34
+ "condition": { "framework": ["nextjs"] }
35
+ },
36
+ {
37
+ "type": "create-file",
38
+ "source": "shared/lib/email/email-templates.ts",
39
+ "destination": "src/shared/email/email-templates.ts",
40
+ "condition": { "framework": ["express"] }
41
+ },
42
+ {
43
+ "type": "create-file",
44
+ "source": "nextjs/api/auth/[...all]/route.ts",
27
45
  "destination": "app/api/auth/[...all]/route.ts",
28
46
  "condition": { "framework": "nextjs" }
29
47
  },
30
48
  {
31
49
  "type": "create-file",
32
- "source": "lib/auth-client.ts",
33
- "destination": "lib/auth-client.ts",
34
- "condition": { "framework": ["nextjs", "react"] }
50
+ "source": "nextjs/proxy.ts",
51
+ "destination": "proxy.ts",
52
+ "condition": { "framework": "nextjs" }
53
+ },
54
+ {
55
+ "type": "create-file",
56
+ "source": "nextjs/lib/auth/auth-guards.ts",
57
+ "destination": "server/auth/guards.ts",
58
+ "condition": { "framework": "nextjs" }
59
+ },
60
+ {
61
+ "type": "create-file",
62
+ "source": "express/middlewares/authorize.ts",
63
+ "destination": "src/shared/middlewares/authorize.middleware.ts",
64
+ "condition": { "framework": "express" }
65
+ },
66
+ {
67
+ "type": "create-file",
68
+ "source": "express/types/express.d.ts",
69
+ "destination": "src/types/express.d.ts",
70
+ "condition": { "framework": "express" }
71
+ },
72
+ {
73
+ "type": "create-file",
74
+ "source": "shared/lib/auth-client.ts",
75
+ "destination": "lib/auth/auth-client.ts",
76
+ "condition": { "framework": ["nextjs"] }
77
+ },
78
+ {
79
+ "type": "create-file",
80
+ "source": "shared/lib/auth-client.ts",
81
+ "destination": "src/shared/lib/auth-client.ts",
82
+ "condition": { "framework": ["react"] }
35
83
  },
36
84
  {
37
85
  "type": "patch-file",
@@ -40,7 +88,7 @@
40
88
  "operations": [
41
89
  {
42
90
  "type": "add-to-bottom",
43
- "source": "prisma/schema.prisma"
91
+ "source": "shared/prisma/schema.prisma"
44
92
  }
45
93
  ]
46
94
  },
@@ -51,12 +99,12 @@
51
99
  "operations": [
52
100
  {
53
101
  "type": "add-import",
54
- "imports": ["import { initAuth } from \"../lib/auth\";"]
102
+ "imports": ["import { setupAuth } from \"./modules/auth/auth\";"]
55
103
  },
56
104
  {
57
105
  "type": "add-code",
58
106
  "before": "const port = env.port;",
59
- "code": "await initAuth();"
107
+ "code": "await setupAuth();"
60
108
  }
61
109
  ]
62
110
  },
@@ -67,13 +115,20 @@
67
115
  "operations": [
68
116
  {
69
117
  "type": "add-import",
70
- "imports": ["import { auth } from \"../lib/auth\";",
118
+ "imports": ["import { auth } from \"./modules/auth/auth\";",
71
119
  "import { toNodeHandler } from \"better-auth/node\";"]
72
120
  },
73
121
  {
74
122
  "type": "add-code",
75
- "after": "// routes",
76
- "code": "app.all(\"/api/auth/*splat\", toNodeHandler(auth));"
123
+ "after": "// API routes",
124
+ "code": [
125
+ "app.all(\"/api/auth/*splat\", (req: Request, res: Response) => {",
126
+ " if (!auth) {",
127
+ " return res.status(503).json({ success: false, message: \"Auth not ready\" });",
128
+ " }",
129
+ " return toNodeHandler(auth)(req, res);",
130
+ "});"
131
+ ]
77
132
  }
78
133
  ]
79
134
  },
@@ -6,12 +6,26 @@
6
6
  {
7
7
  "type": "create-file",
8
8
  "source": "lib/mongoose.ts",
9
- "destination": "lib/mongoose.ts"
9
+ "destination": "server/database/mongoose.ts",
10
+ "condition": { "framework": ["nextjs"] }
11
+ },
12
+ {
13
+ "type": "create-file",
14
+ "source": "lib/mongoose.ts",
15
+ "destination": "src/database/mongoose.ts",
16
+ "condition": { "framework": ["express"] }
17
+ },
18
+ {
19
+ "type": "create-file",
20
+ "source": "models/health.ts",
21
+ "destination": "src/modules/health/health.model.ts",
22
+ "condition": { "framework": ["express"] }
10
23
  },
11
24
  {
12
25
  "type": "create-file",
13
26
  "source": "models/health.ts",
14
- "destination": "models/health.ts"
27
+ "destination": "server/database/models/health.model.ts",
28
+ "condition": { "framework": ["nextjs"] }
15
29
  },
16
30
  {
17
31
  "type": "patch-file",
@@ -1,5 +1,5 @@
1
+ import { PrismaClient } from "@prisma/client";
1
2
  import 'dotenv/config';
2
- import { PrismaClient } from './generated/prisma/client'
3
3
 
4
4
  const globalForPrisma = globalThis as unknown as {
5
5
  prisma: PrismaClient | undefined
@@ -1,6 +1,5 @@
1
1
  generator client {
2
- provider = "prisma-client"
3
- output = "../lib/generated/prisma"
2
+ provider = "prisma-client-js"
4
3
  }
5
4
 
6
5
  datasource db {
@@ -16,7 +16,14 @@
16
16
  {
17
17
  "type": "create-file",
18
18
  "source": "lib/prisma.ts",
19
- "destination": "lib/prisma.ts"
19
+ "destination": "server/database/prisma.ts",
20
+ "condition": { "framework": ["nextjs"] }
21
+ },
22
+ {
23
+ "type": "create-file",
24
+ "source": "lib/prisma.ts",
25
+ "destination": "src/database/prisma.ts",
26
+ "condition": { "framework": ["express"] }
20
27
  },
21
28
  {
22
29
  "type": "add-dependency",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "stackkit",
3
- "version": "0.2.3",
3
+ "version": "0.2.7",
4
4
  "description": "Production-ready CLI to create and extend JavaScript or TypeScript apps with modular stacks.",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
@@ -67,17 +67,17 @@
67
67
  "@babel/core": "^7.28.5",
68
68
  "@babel/preset-env": "^7.28.5",
69
69
  "@babel/preset-typescript": "^7.28.5",
70
- "@babel/preset-react": "^7.28.5"
70
+ "@babel/preset-react": "^7.28.5",
71
+ "recast": "^0.20.5",
72
+ "@babel/parser": "^7.28.5",
73
+ "@babel/plugin-transform-typescript": "^7.28.5",
74
+ "@babel/plugin-transform-react-jsx": "^7.27.1"
71
75
  },
72
76
  "devDependencies": {
73
77
  "@types/fs-extra": "^11.0.4",
74
78
  "@types/inquirer": "^9.0.7",
75
79
  "@types/node": "^25.0.8",
76
80
  "@types/validate-npm-package-name": "^4.0.2",
77
- "typescript": "^5.3.3",
78
- "recast": "^0.20.5",
79
- "@babel/plugin-transform-typescript": "^7.28.5",
80
- "@babel/parser": "^7.28.5",
81
- "@babel/plugin-transform-react-jsx": "^7.27.1"
81
+ "typescript": "^5.3.3"
82
82
  }
83
83
  }
@@ -1,2 +1,3 @@
1
1
  PORT=3000
2
- NODE_ENV=development
2
+ NODE_ENV=development
3
+ CORS_ORIGIN="http://localhost:3000"
@@ -8,15 +8,15 @@
8
8
  "build": "tsc",
9
9
  "lint": "eslint src --ext .ts",
10
10
  "lint:fix": "eslint src --ext .ts --fix",
11
- "start": "node dist/server.js",
12
- "start:prod": "cross-env NODE_ENV=production node dist/server.js"
11
+ "start": "node dist/server.js"
13
12
  },
14
13
  "dependencies": {
15
14
  "cors": "^2.8.5",
16
15
  "dotenv": "^17.2.3",
17
16
  "express": "^5.2.1",
18
17
  "helmet": "^8.1.0",
19
- "morgan": "^1.10.1"
18
+ "morgan": "^1.10.1",
19
+ "express-rate-limit": "^6.10.0"
20
20
  },
21
21
  "devDependencies": {
22
22
  "@types/cors": "^2.8.19",
@@ -25,7 +25,6 @@
25
25
  "@types/node": "^25.0.8",
26
26
  "@typescript-eslint/eslint-plugin": "^8.53.0",
27
27
  "@typescript-eslint/parser": "^8.53.0",
28
- "cross-env": "^10.1.0",
29
28
  "eslint": "^9.39.2",
30
29
  "tsx": "^4.21.0",
31
30
  "typescript": "^5.9.3"
@@ -1,28 +1,26 @@
1
- import cors from "cors";
2
- import express, { Application, NextFunction, Request, Response } from "express";
3
- import helmet from "helmet";
4
- import morgan from "morgan";
5
- import { env } from "./config/env";
6
- import { authRoutes } from "./features/health/health.route";
7
- import { errorHandler } from "./middlewares/error.middleware";
1
+ import express, { Application, Request, Response } from "express";
2
+ import { cors } from "./config/cors";
3
+ import { helmet } from "./config/helmet";
4
+ import { logger } from "./config/logger";
5
+ import { limiter } from "./config/rate-limit";
6
+ import { apiRoutes } from "./routes";
7
+ import { errorHandler } from "./shared/middlewares/error.middleware";
8
+ import { notFound } from "./shared/middlewares/not-found.middleware";
8
9
 
9
10
  // app initialization
10
11
  const app: Application = express();
11
12
  app.use(express.json());
12
13
 
13
- // security headers
14
- app.use(helmet());
14
+ app.use(helmet);
15
+ app.use(logger);
16
+ app.use(cors);
17
+ app.use(limiter);
15
18
 
16
- // logging
17
- if (env.isProduction) {
18
- app.use(morgan("combined"));
19
- } else {
20
- app.use(morgan("dev"));
19
+ // trust proxy when behind proxies (load balancers)
20
+ if (process.env.NODE_ENV === "production") {
21
+ app.set("trust proxy", 1);
21
22
  }
22
23
 
23
- // cors configuration
24
- app.use(cors());
25
-
26
24
  // Home page route
27
25
  app.get("/", (_req: Request, res: Response) => {
28
26
  res.status(200).json({
@@ -34,16 +32,11 @@ app.get("/", (_req: Request, res: Response) => {
34
32
  });
35
33
  });
36
34
 
37
- // routes
38
- app.use("/api/health", authRoutes);
35
+ // API routes
36
+ app.use("/api", apiRoutes);
39
37
 
40
38
  // unhandled routes
41
- app.use((req: Request, _res: Response, next: NextFunction) => {
42
- const error: any = new Error(`Can't find ${req.originalUrl} on this server!`);
43
- error.status = 404;
44
-
45
- next(error);
46
- });
39
+ app.use(notFound);
47
40
 
48
41
  // Global error handler
49
42
  app.use(errorHandler);
@@ -0,0 +1,12 @@
1
+ import createCors from "cors";
2
+ import { env } from "./env";
3
+
4
+ const origin = env.isProduction
5
+ ? process.env.CORS_ORIGIN
6
+ ? process.env.CORS_ORIGIN.split(",").map((s) => s.trim())
7
+ : false
8
+ : true;
9
+
10
+ const cors = createCors({ origin });
11
+
12
+ export { cors };
@@ -0,0 +1,5 @@
1
+ import createHelmet from "helmet";
2
+
3
+ const helmet = createHelmet();
4
+
5
+ export { helmet };
@@ -0,0 +1,6 @@
1
+ import morgan from "morgan";
2
+ import { env } from "./env";
3
+
4
+ const logger = env.isProduction ? morgan("combined") : morgan("dev");
5
+
6
+ export { logger };
@@ -0,0 +1,11 @@
1
+ import rateLimit from "express-rate-limit";
2
+ import { env } from "./env";
3
+
4
+ const limiter = rateLimit({
5
+ windowMs: 15 * 60 * 1000, // 15 minutes
6
+ max: env.isProduction ? 100 : 1000, // limit each IP
7
+ standardHeaders: true,
8
+ legacyHeaders: false,
9
+ });
10
+
11
+ export { limiter };
@@ -6,4 +6,4 @@ const router = Router();
6
6
  // demo route
7
7
  router.get("/", healthController.health);
8
8
 
9
- export const authRoutes = router;
9
+ export const healthRoutes = router;
@@ -0,0 +1,12 @@
1
+ import { Router } from "express";
2
+ import { healthRoutes } from "../modules/health/health.route";
3
+ const router = Router();
4
+
5
+ // versioned API
6
+ const v1 = Router();
7
+
8
+ v1.use("/health", healthRoutes);
9
+
10
+ router.use("/v1", v1);
11
+
12
+ export const apiRoutes = router;
@@ -0,0 +1,14 @@
1
+ export default class ApiError extends Error {
2
+ statusCode: number;
3
+ isOperational: boolean;
4
+
5
+ constructor(statusCode: number, message: string, isOperational = true) {
6
+ super(message);
7
+ this.statusCode = statusCode;
8
+ this.isOperational = isOperational;
9
+ Object.setPrototypeOf(this, ApiError.prototype);
10
+ if (Error.captureStackTrace) {
11
+ Error.captureStackTrace(this, this.constructor);
12
+ }
13
+ }
14
+ }
@@ -0,0 +1,9 @@
1
+ const ERROR_CODES = {
2
+ VALIDATION: "VALIDATION_ERROR",
3
+ NOT_FOUND: "NOT_FOUND",
4
+ UNAUTHORIZED: "UNAUTHORIZED",
5
+ FORBIDDEN: "FORBIDDEN",
6
+ INTERNAL: "INTERNAL_ERROR",
7
+ } as const;
8
+
9
+ export default ERROR_CODES;
@@ -0,0 +1,20 @@
1
+ function ts() {
2
+ return new Date().toISOString();
3
+ }
4
+
5
+ export const info = (msg: string, meta?: any) => {
6
+ console.log(`${ts()} INFO: ${msg}` + (meta ? ` ${JSON.stringify(meta)}` : ""));
7
+ };
8
+ export const warn = (msg: string, meta?: any) => {
9
+ console.warn(`${ts()} WARN: ${msg}` + (meta ? ` ${JSON.stringify(meta)}` : ""));
10
+ };
11
+ export const error = (msg: string, meta?: any) => {
12
+ console.error(`${ts()} ERROR: ${msg}` + (meta ? ` ${JSON.stringify(meta)}` : ""));
13
+ };
14
+ export const debug = (msg: string, meta?: any) => {
15
+ if (process.env.NODE_ENV !== "production") {
16
+ console.debug(`${ts()} DEBUG: ${msg}` + (meta ? ` ${JSON.stringify(meta)}` : ""));
17
+ }
18
+ };
19
+
20
+ export default { info, warn, error, debug };
@@ -1,5 +1,5 @@
1
1
  import { NextFunction, Request, Response } from "express";
2
- import { env } from "../config/env";
2
+ import { env } from "../../config/env";
3
3
 
4
4
  export const errorHandler = (err: any, _req: Request, res: Response, _: NextFunction) => {
5
5
  const statusCode = err.status || 500;
@@ -0,0 +1,9 @@
1
+ import { Request, Response } from "express";
2
+
3
+ export function notFound(req: Request, res: Response) {
4
+ res.status(404).json({
5
+ message: `Can't find ${req.originalUrl} on this server!`,
6
+ path: req.originalUrl,
7
+ date: Date(),
8
+ });
9
+ }
@@ -0,0 +1,9 @@
1
+ import { NextFunction, Request, Response } from "express";
2
+
3
+ export default function asyncHandler(
4
+ fn: (req: Request, res: Response, next: NextFunction) => Promise<any>,
5
+ ) {
6
+ return (req: Request, res: Response, next: NextFunction) => {
7
+ fn(req, res, next).catch(next);
8
+ };
9
+ }
@@ -0,0 +1,6 @@
1
+ export function getPagination(query: any) {
2
+ const page = Math.max(1, parseInt(String(query.page || "1"), 10) || 1);
3
+ const limit = Math.min(100, parseInt(String(query.limit || "10"), 10) || 10);
4
+ const skip = (page - 1) * limit;
5
+ return { page, limit, skip, take: limit };
6
+ }
@@ -0,0 +1,9 @@
1
+ import { Response } from "express";
2
+
3
+ export function success(res: Response, data: any, message = "Success", meta?: any) {
4
+ return res.json({ success: true, message, data, meta });
5
+ }
6
+
7
+ export function fail(res: Response, status = 500, message = "Error", details?: any) {
8
+ return res.status(status).json({ success: false, message, details });
9
+ }
@@ -1,9 +1,15 @@
1
1
  {
2
2
  "compilerOptions": {
3
+ "sourceMap": true,
4
+ "outDir": "dist",
5
+ "strict": true,
6
+ "target": "ES2020",
7
+ "lib": ["ES2020", "DOM"],
8
+ "skipLibCheck": true,
9
+ "types": ["node"],
10
+ "esModuleInterop": true,
3
11
  "module": "ESNext",
4
12
  "moduleResolution": "node",
5
- "target": "ES2023",
6
- "strict": true,
7
- "esModuleInterop": true
13
+ "resolveJsonModule": true
8
14
  }
9
15
  }
@@ -0,0 +1,6 @@
1
+ /// <reference types="next" />
2
+ /// <reference types="next/image-types/global" />
3
+ import "./.next/types/routes.d.ts";
4
+
5
+ // NOTE: This file should not be edited
6
+ // see https://nextjs.org/docs/app/api-reference/config/typescript for more information.