create-ej-app 16.0.0 → 18.0.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 (104) hide show
  1. package/README.md +108 -13
  2. package/cli.js +32 -19
  3. package/cli.ts +45 -27
  4. package/package.json +2 -2
  5. package/template/api/.env.example +3 -0
  6. package/template/api/.github/copilot-instructions.md +129 -0
  7. package/template/api/.vscode/extensions.json +9 -0
  8. package/template/api/.vscode/settings.json +3 -0
  9. package/template/api/Dockerfile +23 -0
  10. package/template/api/package.json +31 -0
  11. package/template/api/prettier.config.mjs +11 -0
  12. package/template/api/src/data-access/todo.ts +142 -0
  13. package/template/api/src/db/fields.ts +46 -0
  14. package/template/api/src/db/index.ts +21 -0
  15. package/template/api/src/db/schema.ts +19 -0
  16. package/template/api/src/env.ts +24 -0
  17. package/template/api/src/index.ts +39 -0
  18. package/template/api/src/lib/util.ts +55 -0
  19. package/template/api/src/routes/index.ts +18 -0
  20. package/template/api/src/routes/todo/index.ts +241 -0
  21. package/template/api/src/scripts/seed.ts +25 -0
  22. package/template/api/tsconfig.json +33 -0
  23. package/template/nextjs/drizzle.config.ts +10 -0
  24. package/template/{base → nextjs}/package.json +7 -4
  25. package/template/{base → nextjs}/src/app/not-found.tsx +8 -2
  26. package/template/nextjs/src/instrumentation.ts +26 -0
  27. package/template/nextjs/src/lib/otel.ts +27 -0
  28. package/template/{base → nextjs}/src/lib/safe-action.ts +8 -0
  29. /package/template/{base → api}/drizzle.config.ts +0 -0
  30. /package/template/{base → nextjs}/.dockerignore +0 -0
  31. /package/template/{base → nextjs}/.env.example +0 -0
  32. /package/template/{base → nextjs}/Dockerfile +0 -0
  33. /package/template/{base → nextjs}/components.json +0 -0
  34. /package/template/{base → nextjs}/eslint.config.mjs +0 -0
  35. /package/template/{base → nextjs}/next.config.ts +0 -0
  36. /package/template/{base → nextjs}/postcss.config.mjs +0 -0
  37. /package/template/{base → nextjs}/prettier.config.mjs +0 -0
  38. /package/template/{base → nextjs}/public/placeholder.svg +0 -0
  39. /package/template/{base → nextjs}/src/app/(admin)/admin/page.tsx +0 -0
  40. /package/template/{base → nextjs}/src/app/(admin)/layout.tsx +0 -0
  41. /package/template/{base → nextjs}/src/app/(app)/layout.tsx +0 -0
  42. /package/template/{base → nextjs}/src/app/(auth)/login/email-login-form.tsx +0 -0
  43. /package/template/{base → nextjs}/src/app/(auth)/login/page.tsx +0 -0
  44. /package/template/{base → nextjs}/src/app/(auth)/login/social-login-buttons.tsx +0 -0
  45. /package/template/{base → nextjs}/src/app/(marketing)/layout.tsx +0 -0
  46. /package/template/{base → nextjs}/src/app/(marketing)/page.tsx +0 -0
  47. /package/template/{base → nextjs}/src/app/(marketing)/privacy/page.tsx +0 -0
  48. /package/template/{base → nextjs}/src/app/(marketing)/terms/page.tsx +0 -0
  49. /package/template/{base → nextjs}/src/app/api/[[...slugs]]/route.ts +0 -0
  50. /package/template/{base → nextjs}/src/app/api/auth/[...all]/route.ts +0 -0
  51. /package/template/{base → nextjs}/src/app/favicon.ico +0 -0
  52. /package/template/{base → nextjs}/src/app/globals.css +0 -0
  53. /package/template/{base → nextjs}/src/app/layout.tsx +0 -0
  54. /package/template/{base → nextjs}/src/components/loader.tsx +0 -0
  55. /package/template/{base → nextjs}/src/components/react-query-provider.tsx +0 -0
  56. /package/template/{base → nextjs}/src/components/screen-size.tsx +0 -0
  57. /package/template/{base → nextjs}/src/components/site-footer.tsx +0 -0
  58. /package/template/{base → nextjs}/src/components/theme-toggle.tsx +0 -0
  59. /package/template/{base → nextjs}/src/components/ui/button.tsx +0 -0
  60. /package/template/{base → nextjs}/src/components/ui/card.tsx +0 -0
  61. /package/template/{base → nextjs}/src/components/ui/form.tsx +0 -0
  62. /package/template/{base → nextjs}/src/components/ui/input.tsx +0 -0
  63. /package/template/{base → nextjs}/src/components/ui/label.tsx +0 -0
  64. /package/template/{base → nextjs}/src/components/ui/separator.tsx +0 -0
  65. /package/template/{base → nextjs}/src/components/ui/sonner.tsx +0 -0
  66. /package/template/{base → nextjs}/src/db/index.ts +0 -0
  67. /package/template/{base → nextjs}/src/db/schema.ts +0 -0
  68. /package/template/{base → nextjs}/src/emails/components/email-layout.tsx +0 -0
  69. /package/template/{base → nextjs}/src/emails/magic-link.tsx +0 -0
  70. /package/template/{base → nextjs}/src/emails/test.tsx +0 -0
  71. /package/template/{base → nextjs}/src/emails/welcome.tsx +0 -0
  72. /package/template/{base → nextjs}/src/env.ts +0 -0
  73. /package/template/{base → nextjs}/src/lib/auth-client.ts +0 -0
  74. /package/template/{base → nextjs}/src/lib/auth.ts +0 -0
  75. /package/template/{base → nextjs}/src/lib/cn.ts +0 -0
  76. /package/template/{base → nextjs}/src/lib/delay.ts +0 -0
  77. /package/template/{base → nextjs}/src/lib/eden.ts +0 -0
  78. /package/template/{base → nextjs}/src/lib/email-service.ts +0 -0
  79. /package/template/{base → nextjs}/src/lib/nodemailer.ts +0 -0
  80. /package/template/{base → nextjs}/src/lib/safe-action-client.ts +0 -0
  81. /package/template/{base → nextjs}/src/lib/siteConfig.ts +0 -0
  82. /package/template/{base → nextjs}/src/lib/toast-message.ts +0 -0
  83. /package/template/{base → nextjs}/src/scripts/test-mail.ts +0 -0
  84. /package/template/{base → nextjs}/terraform/artifact-registry.tf +0 -0
  85. /package/template/{base → nextjs}/terraform/backend-service.tf +0 -0
  86. /package/template/{base → nextjs}/terraform/cleanup.sh +0 -0
  87. /package/template/{base → nextjs}/terraform/cloudbuild.tf +0 -0
  88. /package/template/{base → nextjs}/terraform/cloudrun.tf +0 -0
  89. /package/template/{base → nextjs}/terraform/database.tf +0 -0
  90. /package/template/{base → nextjs}/terraform/forwarding-rule.tf +0 -0
  91. /package/template/{base → nextjs}/terraform/ip-address.tf +0 -0
  92. /package/template/{base → nextjs}/terraform/network-endpoint-group.tf +0 -0
  93. /package/template/{base → nextjs}/terraform/network.tf +0 -0
  94. /package/template/{base → nextjs}/terraform/pre.sh +0 -0
  95. /package/template/{base → nextjs}/terraform/providers.tf +0 -0
  96. /package/template/{base → nextjs}/terraform/secret.txt +0 -0
  97. /package/template/{base → nextjs}/terraform/setup.sh +0 -0
  98. /package/template/{base → nextjs}/terraform/ssl-certs.tf +0 -0
  99. /package/template/{base → nextjs}/terraform/target-https-proxy copy.tf +0 -0
  100. /package/template/{base → nextjs}/terraform/target-https-proxy.tf +0 -0
  101. /package/template/{base → nextjs}/terraform/terraform.tfvars +0 -0
  102. /package/template/{base → nextjs}/terraform/url-map.tf +0 -0
  103. /package/template/{base → nextjs}/terraform/variables.tf +0 -0
  104. /package/template/{base → nextjs}/tsconfig.json +0 -0
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # create-ej-app
2
2
 
3
- A no-bullshit CLI to bootstrap a production-ready **fucking beast** of a app with Next.js, Better Auth, Drizzle ORM, Elysia, Workflow, shadcn/ui, React Email, and deployment wired up so you don’t screw your shit up in production.
3
+ A no-bullshit CLI to bootstrap production-ready **fucking beasts** of apps with Next.js, Better Auth, Drizzle ORM, Elysia, Workflow, shadcn/ui, React Email, OTEL, and deployment wired up so you don’t screw your shit up in production.
4
4
 
5
5
  ```bash
6
6
  bunx create-ej-app@latest
@@ -11,6 +11,7 @@ bunx create-ej-app@latest
11
11
  ## ✨ Features (aka the good shit)
12
12
 
13
13
  - **Next.js 16** – Powered by React 19, Server Components, App Router. Fast as hell.
14
+
14
15
  - **Better Auth** – Auth that doesn’t suck:
15
16
  - Email/Password
16
17
  - Magic link (because passwords are annoying as fuck)
@@ -19,19 +20,49 @@ bunx create-ej-app@latest
19
20
  - Admin powers
20
21
 
21
22
  - **Drizzle ORM** – Type-safe SQL over PostgreSQL without the usual ORM garbage.
23
+
22
24
  - **Elysia** – Built for Bun. Minimal, fast, no nonsense.
25
+
23
26
  - **Workflow** – Durable jobs so your background tasks don’t randomly die like weak shit.
27
+
24
28
  - **React Email** – Build transactional emails using React components instead of ugly HTML nightmares.
29
+
30
+ - **OTEL (OpenTelemetry)** – Production-grade observability:
31
+ - Distributed tracing
32
+ - Metrics
33
+ - Structured logs
34
+ - Works with GCP, Datadog, Honeycomb, Grafana, etc.
35
+
25
36
  - **shadcn/ui** – Clean, accessible components built on Radix UI.
37
+
26
38
  - **TailwindCSS 4** – Utility-first styling with zero CSS drama.
39
+
27
40
  - **TypeScript** – Strict types so you don’t ship dumb bugs.
41
+
28
42
  - **TanStack Query** – Server state that doesn’t implode.
43
+
29
44
  - **Lucide React** – Icons that don’t look like 2009 garbage.
45
+
30
46
  - **Docker + Terraform** – Ship this shit properly.
47
+
31
48
  - Terraform setup for Google Cloud Platform so you don’t YOLO infra.
32
49
 
33
50
  ---
34
51
 
52
+ ## 📋 Templates (Pick your poison)
53
+
54
+ Choose your template based on what you’re building:
55
+
56
+ ### Next.js Full-Stack App
57
+
58
+ Full-stack setup with Next.js 16, Better Auth, shadcn/ui, and Drizzle ORM. Perfect for web apps that need auth, UI, and backend APIs.
59
+
60
+ ### API Server
61
+
62
+ Production-ready REST API built with Elysia on Bun, Drizzle ORM, PostgreSQL, and Zod validation. Perfect for headless APIs, microservices, internal tools, or that backend you actually want to scale without it exploding like fragile shit.
63
+
64
+ ---
65
+
35
66
  ## 🚀 Quick Start (Let’s fucking go)
36
67
 
37
68
  ```bash
@@ -42,7 +73,7 @@ You’ll be prompted for:
42
73
 
43
74
  - Project name
44
75
  - Description
45
- - Template
76
+ - Template (nextjs or api)
46
77
  - Git init (because not using git in 2026 is wild)
47
78
 
48
79
  Example:
@@ -50,7 +81,7 @@ Example:
50
81
  ```bash
51
82
  ? Enter the project name: badass-app
52
83
  ? Enter a description: My savage Next.js monster
53
- ? Select a template: base
84
+ ? Select a template: nextjs
54
85
  ? Initialize a git repository? yes
55
86
  ```
56
87
 
@@ -60,7 +91,9 @@ Boom. Done.
60
91
 
61
92
  ## 📦 What You Actually Get
62
93
 
63
- ### Tech Stack (The real weapons)
94
+ ### Next.js Full-Stack Template
95
+
96
+ **Tech Stack (The real weapons)**
64
97
 
65
98
  - **Framework**: Next.js
66
99
  - **Runtime**: Bun (fast as shit)
@@ -68,6 +101,7 @@ Boom. Done.
68
101
  - **Database**: PostgreSQL + Drizzle
69
102
  - **Auth**: Better Auth
70
103
  - **Emails**: React Email (transactional + auth templates ready)
104
+ - **Observability**: OpenTelemetry
71
105
  - **Background Jobs**: Workflow
72
106
  - **UI**: shadcn/ui
73
107
  - **Styling**: TailwindCSS
@@ -75,6 +109,20 @@ Boom. Done.
75
109
  - **Forms**: react-hook-form + Zod
76
110
  - **Theme**: Dark mode out of the box
77
111
 
112
+ ### API Server Template
113
+
114
+ **Tech Stack (The real weapons)**
115
+
116
+ - **Runtime**: Bun (blazing fast as fuck)
117
+ - **Framework**: Elysia
118
+ - **Database**: PostgreSQL + Drizzle ORM
119
+ - **Validation**: Zod v4
120
+ - **API Docs**: Auto-generated OpenAPI docs
121
+ - **Containerisation**: Docker with distroless images
122
+ - **Type Checking**: TypeScript with strict mode
123
+ - **Formatting**: Prettier
124
+ - **Environment**: Validated env vars with @t3-oss/env-core
125
+
78
126
  ---
79
127
 
80
128
  ## 🛠 Setup & Development
@@ -83,8 +131,8 @@ Boom. Done.
83
131
 
84
132
  - Node 24+ or Bun
85
133
  - PostgreSQL running
86
- - OAuth creds if you want GitHub/Google login
87
- - Email provider (Resend, SES, Postmark, etc.)
134
+ - OAuth creds if you want GitHub/Google login (Next.js template)
135
+ - Email provider (Resend, SES, Postmark, etc.) (Next.js template)
88
136
 
89
137
  ### Install
90
138
 
@@ -106,26 +154,38 @@ Don’t commit this crap. Seriously.
106
154
 
107
155
  ## 💻 Development
108
156
 
157
+ ### Next.js Template
158
+
109
159
  ```bash
110
160
  bun run dev
111
161
  ```
112
162
 
113
163
  Open `http://localhost:3000` and admire your badass setup.
114
164
 
165
+ ### API Template
166
+
167
+ ```bash
168
+ bun run dev
169
+ ```
170
+
171
+ API runs on `http://localhost:3000`. OpenAPI docs at `http://localhost:3000/docs`.
172
+
173
+ Fire it up, hit the endpoints, break things on purpose, and watch it handle your shit properly.
174
+
115
175
  ---
116
176
 
117
177
  ## 🐳 Docker (Ship it like a pro)
118
178
 
119
179
  ```bash
120
- docker build -t my-app .
121
- docker run -p 3000:3000 my-app
180
+ bun run build:docker
181
+ docker run -p 3000:3000 my-awesome-project
122
182
  ```
123
183
 
124
- No “works on my machine” bullshit anymore.
184
+ No "works on my machine" bullshit anymore.
125
185
 
126
186
  ---
127
187
 
128
- ## ☁️ Infrastructure (The Cloud Shit)
188
+ ## ☁️ Infrastructure (The Cloud Shit) - Next.js Template
129
189
 
130
190
  Terraform config included for:
131
191
 
@@ -149,7 +209,7 @@ And now you’re running production infra like a grown-ass engineer.
149
209
 
150
210
  ---
151
211
 
152
- ## 🔐 Authentication
212
+ ## 🔐 Authentication - Next.js Template
153
213
 
154
214
  Better Auth gives you:
155
215
 
@@ -163,7 +223,7 @@ Secure, extensible, no duct-taped auth spaghetti.
163
223
 
164
224
  ---
165
225
 
166
- ## 🎨 UI Components
226
+ ## 🎨 UI Components - Next.js Template
167
227
 
168
228
  Preinstalled:
169
229
 
@@ -183,7 +243,7 @@ Ship beautiful shit without reinventing buttons for the 900th time.
183
243
 
184
244
  ---
185
245
 
186
- ## 🎯 Route Groups
246
+ ## 🎯 Route Groups - Next.js Template
187
247
 
188
248
  Organized like a sane human:
189
249
 
@@ -198,6 +258,8 @@ No messy folder hell.
198
258
 
199
259
  ## 🔧 Customization
200
260
 
261
+ ### Next.js Template
262
+
201
263
  Edit:
202
264
 
203
265
  - `siteConfig.ts` – change name/description
@@ -206,6 +268,38 @@ Edit:
206
268
 
207
269
  Then push schema and move on with your life.
208
270
 
271
+ ### API Template
272
+
273
+ Edit:
274
+
275
+ - `src/db/schema.ts` – change DB schema and model your data like a boss
276
+ - `src/routes/` – add new API endpoints and wire your logic properly
277
+ - `src/env.ts` – add new env vars and validate that shit
278
+
279
+ Push schema with `bun run db:push` and type-check with `bun run type-check`.
280
+
281
+ ---
282
+
283
+ ## 🏗️ API Template Architecture
284
+
285
+ Built with a clean layered architecture (no spaghetti garbage):
286
+
287
+ - **Routes** (`src/routes/`) – HTTP endpoints, validation, call data-access. Keep it sharp.
288
+ - **Data-Access** (`src/data-access/`) – Direct DB interactions per resource. No random queries scattered like WTF.
289
+ - **Database** (`src/db/`) – Drizzle client, schemas, reusable fields. Your source of truth.
290
+ - **Library** (`src/lib/`) – Shared utilities and Zod schemas so you don’t duplicate crap everywhere.
291
+
292
+ Includes a full CRUD API for todos with pagination, sorting, search, and OpenAPI docs so you can see your shit documented without extra effort.
293
+
294
+ ### API Commands
295
+
296
+ - `bun run dev` – Start dev server with hot-reload
297
+ - `bun run build` – Type-check and compile to native binary
298
+ - `bun run type-check` – Run TypeScript checker
299
+ - `bun run db:push` – Push schema to DB
300
+ - `bun run db:studio` – Open Drizzle Studio
301
+ - `bun run db:seed` – Seed database
302
+
209
303
  ---
210
304
 
211
305
  ## 🤝 Contributing
@@ -232,6 +326,7 @@ MIT. Do whatever the hell you want, just don’t blame us if you screw it up.
232
326
  - [Workflow](https://useworkflow.dev)
233
327
  - [shadcn/ui](https://ui.shadcn.com)
234
328
  - [React Email](https://react.email)
329
+ - [Logging](https://loggingsucks.com)
235
330
 
236
331
  ---
237
332
 
package/cli.js CHANGED
@@ -62,13 +62,13 @@ commander_1.program.action(() => __awaiter(void 0, void 0, void 0, function* ()
62
62
  type: 'input',
63
63
  name: 'projectName',
64
64
  message: 'Enter the project name:',
65
- initial: 'my-app'
65
+ initial: 'my-project'
66
66
  },
67
67
  {
68
68
  type: 'input',
69
69
  name: 'description',
70
70
  message: 'Enter a description for the project:',
71
- initial: 'A Legendary Next.js App'
71
+ initial: 'A Legendary Project'
72
72
  },
73
73
  {
74
74
  type: 'select',
@@ -76,8 +76,12 @@ commander_1.program.action(() => __awaiter(void 0, void 0, void 0, function* ()
76
76
  message: 'Select a template:',
77
77
  choices: [
78
78
  {
79
- name: 'base',
80
- message: 'Base setup with Next 16, Better Auth, shadcn and Drizzle'
79
+ name: 'nextjs',
80
+ message: 'Full Stack setup with Next 16, Better Auth, shadcn and Drizzle'
81
+ },
82
+ {
83
+ name: 'api',
84
+ message: 'API Server setup with Elysia, Drizzle, Typescript'
81
85
  }
82
86
  ]
83
87
  },
@@ -102,27 +106,36 @@ commander_1.program.action(() => __awaiter(void 0, void 0, void 0, function* ()
102
106
  packageJson.name = projectName;
103
107
  packageJson.description = description;
104
108
  fs_1.default.writeFileSync(packageJsonFile, JSON.stringify(packageJson, null, 2));
105
- const siteConfigFile = path_1.default.join(projectDir, 'src', 'lib', 'siteConfig.ts');
106
- let siteConfig = fs_1.default.readFileSync(siteConfigFile, 'utf-8');
107
- siteConfig = siteConfig.replace('Project Name', projectName);
108
- siteConfig = siteConfig.replace('Project Description', description);
109
- fs_1.default.writeFileSync(siteConfigFile, siteConfig);
110
109
  const envExampleFile = path_1.default.join(projectDir, '.env.example');
111
110
  const envExample = fs_1.default.readFileSync(envExampleFile, 'utf-8');
112
111
  fs_1.default.writeFileSync(path_1.default.join(projectDir, '.env'), envExample);
113
- fs_1.default.writeFileSync(path_1.default.join(projectDir, '.gitignore'), `node_modules
114
- .next
115
- out
116
- build
117
- next-env.d.ts
112
+ if (template === 'nextjs') {
113
+ const siteConfigFile = path_1.default.join(projectDir, 'src', 'lib', 'siteConfig.ts');
114
+ let siteConfig = fs_1.default.readFileSync(siteConfigFile, 'utf-8');
115
+ siteConfig = siteConfig.replace('Project Name', projectName);
116
+ siteConfig = siteConfig.replace('Project Description', description);
117
+ fs_1.default.writeFileSync(siteConfigFile, siteConfig);
118
+ fs_1.default.writeFileSync(path_1.default.join(projectDir, '.gitignore'), `node_modules
119
+ .next
120
+ out
121
+ build
122
+ next-env.d.ts
123
+ .DS_Store
124
+ .env
125
+ `);
126
+ fs_1.default.writeFileSync(path_1.default.join(projectDir, 'terraform', '.gitignore'), `.terraform
127
+ *.tfstate
128
+ *.tfstate.*
129
+ .terraform.lock.hcl
130
+ `);
131
+ }
132
+ if (template === 'api') {
133
+ fs_1.default.writeFileSync(path_1.default.join(projectDir, '.gitignore'), `node_modules
118
134
  .DS_Store
119
135
  .env
136
+ server
120
137
  `);
121
- fs_1.default.writeFileSync(path_1.default.join(projectDir, 'terraform', '.gitignore'), `.terraform
122
- *.tfstate
123
- *.tfstate.*
124
- .terraform.lock.hcl
125
- `);
138
+ }
126
139
  fs_1.default.writeFileSync(path_1.default.join(projectDir, 'README.md'), `# ${projectName}
127
140
 
128
141
  ${description}
package/cli.ts CHANGED
@@ -59,13 +59,13 @@ program.action(async () => {
59
59
  type: 'input',
60
60
  name: 'projectName',
61
61
  message: 'Enter the project name:',
62
- initial: 'my-app'
62
+ initial: 'my-project'
63
63
  },
64
64
  {
65
65
  type: 'input',
66
66
  name: 'description',
67
67
  message: 'Enter a description for the project:',
68
- initial: 'A Legendary Next.js App'
68
+ initial: 'A Legendary Project'
69
69
  },
70
70
  {
71
71
  type: 'select',
@@ -73,8 +73,13 @@ program.action(async () => {
73
73
  message: 'Select a template:',
74
74
  choices: [
75
75
  {
76
- name: 'base',
77
- message: 'Base setup with Next 16, Better Auth, shadcn and Drizzle'
76
+ name: 'nextjs',
77
+ message:
78
+ 'Full Stack setup with Next 16, Better Auth, shadcn and Drizzle'
79
+ },
80
+ {
81
+ name: 'api',
82
+ message: 'API Server setup with Elysia, Drizzle, Typescript'
78
83
  }
79
84
  ]
80
85
  },
@@ -106,36 +111,49 @@ program.action(async () => {
106
111
  packageJson.description = description
107
112
  fs.writeFileSync(packageJsonFile, JSON.stringify(packageJson, null, 2))
108
113
 
109
- const siteConfigFile = path.join(projectDir, 'src', 'lib', 'siteConfig.ts')
110
- let siteConfig = fs.readFileSync(siteConfigFile, 'utf-8')
111
- siteConfig = siteConfig.replace('Project Name', projectName)
112
- siteConfig = siteConfig.replace('Project Description', description)
113
- fs.writeFileSync(siteConfigFile, siteConfig)
114
-
115
114
  const envExampleFile = path.join(projectDir, '.env.example')
116
115
  const envExample = fs.readFileSync(envExampleFile, 'utf-8')
117
116
  fs.writeFileSync(path.join(projectDir, '.env'), envExample)
118
117
 
119
- fs.writeFileSync(
120
- path.join(projectDir, '.gitignore'),
121
- `node_modules
122
- .next
123
- out
124
- build
125
- next-env.d.ts
126
- .DS_Store
127
- .env
118
+ if (template === 'nextjs') {
119
+ const siteConfigFile = path.join(projectDir, 'src', 'lib', 'siteConfig.ts')
120
+ let siteConfig = fs.readFileSync(siteConfigFile, 'utf-8')
121
+ siteConfig = siteConfig.replace('Project Name', projectName)
122
+ siteConfig = siteConfig.replace('Project Description', description)
123
+ fs.writeFileSync(siteConfigFile, siteConfig)
124
+
125
+ fs.writeFileSync(
126
+ path.join(projectDir, '.gitignore'),
127
+ `node_modules
128
+ .next
129
+ out
130
+ build
131
+ next-env.d.ts
132
+ .DS_Store
133
+ .env
128
134
  `
129
- )
135
+ )
136
+
137
+ fs.writeFileSync(
138
+ path.join(projectDir, 'terraform', '.gitignore'),
139
+ `.terraform
140
+ *.tfstate
141
+ *.tfstate.*
142
+ .terraform.lock.hcl
143
+ `
144
+ )
145
+ }
130
146
 
131
- fs.writeFileSync(
132
- path.join(projectDir, 'terraform', '.gitignore'),
133
- `.terraform
134
- *.tfstate
135
- *.tfstate.*
136
- .terraform.lock.hcl
147
+ if (template === 'api') {
148
+ fs.writeFileSync(
149
+ path.join(projectDir, '.gitignore'),
150
+ `node_modules
151
+ .DS_Store
152
+ .env
153
+ server
137
154
  `
138
- )
155
+ )
156
+ }
139
157
 
140
158
  fs.writeFileSync(
141
159
  path.join(projectDir, 'README.md'),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-ej-app",
3
- "version": "16.0.0",
3
+ "version": "18.0.0",
4
4
  "description": "Bootstrap a next.js app with my preferred setup",
5
5
  "main": "cli.js",
6
6
  "scripts": {
@@ -35,7 +35,7 @@
35
35
  "enquirer": "^2.4.1"
36
36
  },
37
37
  "devDependencies": {
38
- "@types/node": "^25.2.3",
38
+ "@types/node": "^25.3.0",
39
39
  "typescript": "^5.9.3"
40
40
  }
41
41
  }
@@ -0,0 +1,3 @@
1
+ PORT="3000"
2
+ APP_ENV="production"
3
+ DATABASE_URL="postgresql://postgres:postgres@localhost:5432/postgres"
@@ -0,0 +1,129 @@
1
+ # Copilot Instructions
2
+
3
+ ## Project Overview
4
+
5
+ This project is a production-ready REST API built with **Elysia** on the **Bun** runtime. It uses **Drizzle ORM** with **PostgreSQL**, **Zod v4** for validation, and ships with auto-generated OpenAPI docs.
6
+
7
+ ## Tech Stack
8
+
9
+ - **Runtime:** Bun (not Node.js) — use `bun` for all CLI commands
10
+ - **HTTP Framework:** Elysia
11
+ - **Database:** PostgreSQL via Drizzle ORM (using the `bun-sql` driver)
12
+ - **Validation:** Zod v4
13
+ - **Environment Validation:** @t3-oss/env-core + Zod
14
+ - **API Docs:** @elysiajs/openapi (served at `/docs`)
15
+ - **Type Checker:** @typescript/native-preview (`tsgo`) — NOT `tsc`
16
+ - **Formatter:** Prettier (with `prettier-plugin-organize-imports`)
17
+ - **Containerisation:** Docker with multi-stage build producing a distroless image
18
+
19
+ ## Project Structure
20
+
21
+ ```
22
+ src/
23
+ ├── index.ts # App entry point – boots the Elysia server
24
+ ├── env.ts # Validated env vars (Zod + t3-env)
25
+ ├── data-access/ # Database query functions (one file per resource)
26
+ │ └── todo.ts
27
+ ├── db/
28
+ │ ├── fields.ts # Reusable Drizzle column definitions
29
+ │ ├── index.ts # Drizzle client singleton
30
+ │ └── schema.ts # All Drizzle table definitions
31
+ ├── lib/
32
+ │ └── util.ts # Shared Zod schemas (pagination, sorting)
33
+ └── routes/
34
+ ├── index.ts # Top-level /api router
35
+ └── todo/
36
+ └── index.ts # CRUD routes for /api/todos
37
+ ```
38
+
39
+ ## Architecture & Conventions
40
+
41
+ ### Layered Architecture
42
+
43
+ The project follows a strict separation of concerns:
44
+
45
+ 1. **Routes** (`src/routes/`) — Define HTTP endpoints, request/response validation, and call data-access functions. Route handlers never import `db` directly.
46
+ 2. **Data-Access** (`src/data-access/`) — All direct database interactions. One file per resource. Route handlers call these functions exclusively.
47
+ 3. **Database** (`src/db/`) — Drizzle client singleton, table schema definitions, and reusable column helpers.
48
+ 4. **Library** (`src/lib/`) — Shared utilities, reusable Zod schema fragments.
49
+
50
+ ### Adding a New Resource
51
+
52
+ 1. Create the Drizzle table in `src/db/schema.ts` (use `commonFieldDefs` from `src/db/fields.ts` for id, `createdAt`, `updatedAt`).
53
+ 2. Create `src/data-access/<resource>.ts` with all DB query functions.
54
+ 3. Create `src/routes/<resource>/index.ts` with the Elysia router.
55
+ 4. Register the new router in `src/routes/index.ts` via `.use()`.
56
+ 5. Push Schema changes with `bun run db:push`.
57
+ 6. Run `bun type-check` to ensure types are correct across layers.
58
+
59
+ ### Route Conventions
60
+
61
+ - All API routes live under the `/api` prefix.
62
+ - Each resource router uses `new Elysia({ prefix: '/<resource>' })`.
63
+ - Every route handler validates input using Zod schemas in the third argument to route methods (`query`, `params`, `body`).
64
+ - UUID path params are validated with `z.uuid()`.
65
+ - Each route includes a `detail` object with `description` and `tags` for OpenAPI documentation.
66
+ - Handlers use try/catch, returning `{ success: true, data: { ... } }` on success and `status(statusCode, { success: false, error: '...' })` on failure.
67
+ - Use `status()` from `elysia` (not `.set.status`) for error responses.
68
+
69
+ ### Database & Schema Conventions
70
+
71
+ - All tables use **UUID** primary keys generated by the database (`defaultRandom()`).
72
+ - All tables include `created_at` and `updated_at` timestamp columns via the `commonFieldDefs.dates` spread.
73
+ - Use `commonFieldDefs.id` for the primary key column.
74
+ - The Drizzle client is a singleton exported from `src/db/index.ts`.
75
+ - Column names in the database use **snake_case** (`created_at`), while TypeScript properties use **camelCase** (`createdAt`).
76
+ - Schema definitions are in `src/db/schema.ts` — the single source of truth shared by both the app and Drizzle Kit.
77
+
78
+ ### Validation & Environment
79
+
80
+ - Zod v4 is used for all request validation (NOT Elysia's built-in typebox schemas).
81
+ - Environment variables are validated at startup via `src/env.ts`. If a variable is missing or invalid the app refuses to start.
82
+ - Import env as `import { env } from '~/env'`.
83
+
84
+ ### Path Aliases
85
+
86
+ - `~/*` maps to `./src/*` (configured in `tsconfig.json`). Always use `~/` imports for project files.
87
+
88
+ ### Code Style
89
+
90
+ - **No semicolons** — the project uses Prettier with `semi: false`.
91
+ - **Single quotes** for strings.
92
+ - **No trailing commas** (`trailingComma: 'none'`).
93
+ - **Arrow parens:** avoid when possible (`arrowParens: 'avoid'`).
94
+ - **Tab width:** 2 spaces.
95
+ - Imports are auto-organised by `prettier-plugin-organize-imports`.
96
+ - Use `const` arrow functions for exported functions (not `function` declarations).
97
+ - Add JSDoc comments on all exports describing their purpose.
98
+
99
+ ### TypeScript
100
+
101
+ - Strict mode is enabled with all strict checks (`noImplicitAny`, `strictNullChecks`, `noUnusedLocals`, `noUnusedParameters`, `noImplicitReturns`, `noUncheckedIndexedAccess`, etc.).
102
+ - Target is `ESNext` with `module: ESNext` and `moduleResolution: bundler`.
103
+ - Type checking is done with `tsgo` (`bun run type-check`), not `tsc`.
104
+
105
+ ### Pagination & Sorting
106
+
107
+ - Use the `paginationSchema` spread from `src/lib/util.ts` for paginated list endpoints (offset-based, 1-indexed pages, default limit 10, max 100).
108
+ - Use `getSortSchema(['field1', 'field2', ...])` for sortable endpoints.
109
+ - List responses follow the shape: `{ success, data: { page, limit, hasMore, <items>, count } }`.
110
+
111
+ ### Error Handling
112
+
113
+ - Route handlers wrap logic in try/catch.
114
+ - Log errors with `console.error('Error in METHOD /path', err)`.
115
+ - Return `status(500, { success: false, error: 'Something went wrong.' })` for unexpected errors.
116
+ - Return `status(404, { success: false, error: '<Resource> not found' })` for missing resources.
117
+
118
+ ## Commands
119
+
120
+ | Command | Purpose |
121
+ | ---------------------- | ------------------------------------- |
122
+ | `bun dev` | Start dev server with hot-reload |
123
+ | `bun run build` | Type-check + compile to native binary |
124
+ | `bun run type-check` | Run tsgo type checker |
125
+ | `bun run format` | Format code with Prettier |
126
+ | `bun run db:push` | Push Drizzle schema to the database |
127
+ | `bun run db:studio` | Open Drizzle Studio |
128
+ | `bun run db:seed` | Run seed script |
129
+ | `bun run build:docker` | Build Docker image |
@@ -0,0 +1,9 @@
1
+ {
2
+ "recommendations": [
3
+ "eamodio.gitlens",
4
+ "esbenp.prettier-vscode",
5
+ "TypeScriptTeam.native-preview",
6
+ "usernamehw.errorlens",
7
+ "YoavBls.pretty-ts-errors"
8
+ ]
9
+ }
@@ -0,0 +1,3 @@
1
+ {
2
+ "typescript.experimental.useTsgo": true
3
+ }
@@ -0,0 +1,23 @@
1
+ FROM oven/bun:latest AS build
2
+
3
+ WORKDIR /app
4
+
5
+ COPY package.json bun.lock ./
6
+
7
+ RUN bun install --frozen-lockfile
8
+
9
+ COPY . .
10
+
11
+ ENV NODE_ENV=production
12
+
13
+ RUN bun run build
14
+
15
+ FROM gcr.io/distroless/base
16
+
17
+ WORKDIR /app
18
+
19
+ COPY --from=build /app/server server
20
+
21
+ ENV NODE_ENV=production
22
+
23
+ ENTRYPOINT ["./server"]
@@ -0,0 +1,31 @@
1
+ {
2
+ "name": "my-api",
3
+ "version": "1.0.0",
4
+ "private": true,
5
+ "main": "src/index.ts",
6
+ "scripts": {
7
+ "dev": "bun --watch .",
8
+ "build": "bun type-check && bun build --compile --target bun --outfile server .",
9
+ "build:docker": "docker build -t my-api .",
10
+ "type-check": "tsgo --incremental false",
11
+ "format": "prettier --write \"./src/**/*.{js,cjs,mjs,jsx,ts,tsx}\"",
12
+ "db:push": "drizzle-kit push",
13
+ "db:studio": "drizzle-kit studio",
14
+ "db:seed": "bun ./src/scripts/seed.ts"
15
+ },
16
+ "dependencies": {
17
+ "@elysiajs/openapi": "1.4.14",
18
+ "@t3-oss/env-core": "0.13.10",
19
+ "drizzle-orm": "0.45.1",
20
+ "elysia": "1.4.25",
21
+ "zod": "4.3.6"
22
+ },
23
+ "devDependencies": {
24
+ "@types/bun": "1.3.9",
25
+ "@typescript/native-preview": "7.0.0-dev.20260217.1",
26
+ "drizzle-kit": "0.31.9",
27
+ "postgres": "3.4.8",
28
+ "prettier": "3.8.1",
29
+ "prettier-plugin-organize-imports": "4.3.0"
30
+ }
31
+ }
@@ -0,0 +1,11 @@
1
+ /** @type {import('prettier').Config} */
2
+ const config = {
3
+ plugins: ['prettier-plugin-organize-imports'],
4
+ arrowParens: 'avoid',
5
+ trailingComma: 'none',
6
+ semi: false,
7
+ singleQuote: true,
8
+ tabWidth: 2
9
+ }
10
+
11
+ export default config