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.
- package/README.md +108 -13
- package/cli.js +32 -19
- package/cli.ts +45 -27
- package/package.json +2 -2
- package/template/api/.env.example +3 -0
- package/template/api/.github/copilot-instructions.md +129 -0
- package/template/api/.vscode/extensions.json +9 -0
- package/template/api/.vscode/settings.json +3 -0
- package/template/api/Dockerfile +23 -0
- package/template/api/package.json +31 -0
- package/template/api/prettier.config.mjs +11 -0
- package/template/api/src/data-access/todo.ts +142 -0
- package/template/api/src/db/fields.ts +46 -0
- package/template/api/src/db/index.ts +21 -0
- package/template/api/src/db/schema.ts +19 -0
- package/template/api/src/env.ts +24 -0
- package/template/api/src/index.ts +39 -0
- package/template/api/src/lib/util.ts +55 -0
- package/template/api/src/routes/index.ts +18 -0
- package/template/api/src/routes/todo/index.ts +241 -0
- package/template/api/src/scripts/seed.ts +25 -0
- package/template/api/tsconfig.json +33 -0
- package/template/nextjs/drizzle.config.ts +10 -0
- package/template/{base → nextjs}/package.json +7 -4
- package/template/{base → nextjs}/src/app/not-found.tsx +8 -2
- package/template/nextjs/src/instrumentation.ts +26 -0
- package/template/nextjs/src/lib/otel.ts +27 -0
- package/template/{base → nextjs}/src/lib/safe-action.ts +8 -0
- /package/template/{base → api}/drizzle.config.ts +0 -0
- /package/template/{base → nextjs}/.dockerignore +0 -0
- /package/template/{base → nextjs}/.env.example +0 -0
- /package/template/{base → nextjs}/Dockerfile +0 -0
- /package/template/{base → nextjs}/components.json +0 -0
- /package/template/{base → nextjs}/eslint.config.mjs +0 -0
- /package/template/{base → nextjs}/next.config.ts +0 -0
- /package/template/{base → nextjs}/postcss.config.mjs +0 -0
- /package/template/{base → nextjs}/prettier.config.mjs +0 -0
- /package/template/{base → nextjs}/public/placeholder.svg +0 -0
- /package/template/{base → nextjs}/src/app/(admin)/admin/page.tsx +0 -0
- /package/template/{base → nextjs}/src/app/(admin)/layout.tsx +0 -0
- /package/template/{base → nextjs}/src/app/(app)/layout.tsx +0 -0
- /package/template/{base → nextjs}/src/app/(auth)/login/email-login-form.tsx +0 -0
- /package/template/{base → nextjs}/src/app/(auth)/login/page.tsx +0 -0
- /package/template/{base → nextjs}/src/app/(auth)/login/social-login-buttons.tsx +0 -0
- /package/template/{base → nextjs}/src/app/(marketing)/layout.tsx +0 -0
- /package/template/{base → nextjs}/src/app/(marketing)/page.tsx +0 -0
- /package/template/{base → nextjs}/src/app/(marketing)/privacy/page.tsx +0 -0
- /package/template/{base → nextjs}/src/app/(marketing)/terms/page.tsx +0 -0
- /package/template/{base → nextjs}/src/app/api/[[...slugs]]/route.ts +0 -0
- /package/template/{base → nextjs}/src/app/api/auth/[...all]/route.ts +0 -0
- /package/template/{base → nextjs}/src/app/favicon.ico +0 -0
- /package/template/{base → nextjs}/src/app/globals.css +0 -0
- /package/template/{base → nextjs}/src/app/layout.tsx +0 -0
- /package/template/{base → nextjs}/src/components/loader.tsx +0 -0
- /package/template/{base → nextjs}/src/components/react-query-provider.tsx +0 -0
- /package/template/{base → nextjs}/src/components/screen-size.tsx +0 -0
- /package/template/{base → nextjs}/src/components/site-footer.tsx +0 -0
- /package/template/{base → nextjs}/src/components/theme-toggle.tsx +0 -0
- /package/template/{base → nextjs}/src/components/ui/button.tsx +0 -0
- /package/template/{base → nextjs}/src/components/ui/card.tsx +0 -0
- /package/template/{base → nextjs}/src/components/ui/form.tsx +0 -0
- /package/template/{base → nextjs}/src/components/ui/input.tsx +0 -0
- /package/template/{base → nextjs}/src/components/ui/label.tsx +0 -0
- /package/template/{base → nextjs}/src/components/ui/separator.tsx +0 -0
- /package/template/{base → nextjs}/src/components/ui/sonner.tsx +0 -0
- /package/template/{base → nextjs}/src/db/index.ts +0 -0
- /package/template/{base → nextjs}/src/db/schema.ts +0 -0
- /package/template/{base → nextjs}/src/emails/components/email-layout.tsx +0 -0
- /package/template/{base → nextjs}/src/emails/magic-link.tsx +0 -0
- /package/template/{base → nextjs}/src/emails/test.tsx +0 -0
- /package/template/{base → nextjs}/src/emails/welcome.tsx +0 -0
- /package/template/{base → nextjs}/src/env.ts +0 -0
- /package/template/{base → nextjs}/src/lib/auth-client.ts +0 -0
- /package/template/{base → nextjs}/src/lib/auth.ts +0 -0
- /package/template/{base → nextjs}/src/lib/cn.ts +0 -0
- /package/template/{base → nextjs}/src/lib/delay.ts +0 -0
- /package/template/{base → nextjs}/src/lib/eden.ts +0 -0
- /package/template/{base → nextjs}/src/lib/email-service.ts +0 -0
- /package/template/{base → nextjs}/src/lib/nodemailer.ts +0 -0
- /package/template/{base → nextjs}/src/lib/safe-action-client.ts +0 -0
- /package/template/{base → nextjs}/src/lib/siteConfig.ts +0 -0
- /package/template/{base → nextjs}/src/lib/toast-message.ts +0 -0
- /package/template/{base → nextjs}/src/scripts/test-mail.ts +0 -0
- /package/template/{base → nextjs}/terraform/artifact-registry.tf +0 -0
- /package/template/{base → nextjs}/terraform/backend-service.tf +0 -0
- /package/template/{base → nextjs}/terraform/cleanup.sh +0 -0
- /package/template/{base → nextjs}/terraform/cloudbuild.tf +0 -0
- /package/template/{base → nextjs}/terraform/cloudrun.tf +0 -0
- /package/template/{base → nextjs}/terraform/database.tf +0 -0
- /package/template/{base → nextjs}/terraform/forwarding-rule.tf +0 -0
- /package/template/{base → nextjs}/terraform/ip-address.tf +0 -0
- /package/template/{base → nextjs}/terraform/network-endpoint-group.tf +0 -0
- /package/template/{base → nextjs}/terraform/network.tf +0 -0
- /package/template/{base → nextjs}/terraform/pre.sh +0 -0
- /package/template/{base → nextjs}/terraform/providers.tf +0 -0
- /package/template/{base → nextjs}/terraform/secret.txt +0 -0
- /package/template/{base → nextjs}/terraform/setup.sh +0 -0
- /package/template/{base → nextjs}/terraform/ssl-certs.tf +0 -0
- /package/template/{base → nextjs}/terraform/target-https-proxy copy.tf +0 -0
- /package/template/{base → nextjs}/terraform/target-https-proxy.tf +0 -0
- /package/template/{base → nextjs}/terraform/terraform.tfvars +0 -0
- /package/template/{base → nextjs}/terraform/url-map.tf +0 -0
- /package/template/{base → nextjs}/terraform/variables.tf +0 -0
- /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
|
|
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:
|
|
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
|
-
###
|
|
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
|
-
|
|
121
|
-
docker run -p 3000:3000 my-
|
|
180
|
+
bun run build:docker
|
|
181
|
+
docker run -p 3000:3000 my-awesome-project
|
|
122
182
|
```
|
|
123
183
|
|
|
124
|
-
No
|
|
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-
|
|
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
|
|
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: '
|
|
80
|
-
message: '
|
|
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
|
-
|
|
114
|
-
.
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
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
|
-
|
|
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-
|
|
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
|
|
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: '
|
|
77
|
-
message:
|
|
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
|
-
|
|
120
|
-
path.join(projectDir, '.
|
|
121
|
-
|
|
122
|
-
.
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
.
|
|
127
|
-
.
|
|
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
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
.
|
|
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": "
|
|
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.
|
|
38
|
+
"@types/node": "^25.3.0",
|
|
39
39
|
"typescript": "^5.9.3"
|
|
40
40
|
}
|
|
41
41
|
}
|
|
@@ -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,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
|
+
}
|