create-ej-app 17.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 +89 -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 → 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}/package.json +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/app/not-found.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/instrumentation.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/otel.ts +0 -0
- /package/template/{base → nextjs}/src/lib/safe-action-client.ts +0 -0
- /package/template/{base → nextjs}/src/lib/safe-action.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
|
|
@@ -49,6 +49,20 @@ bunx create-ej-app@latest
|
|
|
49
49
|
|
|
50
50
|
---
|
|
51
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
|
+
|
|
52
66
|
## 🚀 Quick Start (Let’s fucking go)
|
|
53
67
|
|
|
54
68
|
```bash
|
|
@@ -59,7 +73,7 @@ You’ll be prompted for:
|
|
|
59
73
|
|
|
60
74
|
- Project name
|
|
61
75
|
- Description
|
|
62
|
-
- Template
|
|
76
|
+
- Template (nextjs or api)
|
|
63
77
|
- Git init (because not using git in 2026 is wild)
|
|
64
78
|
|
|
65
79
|
Example:
|
|
@@ -67,7 +81,7 @@ Example:
|
|
|
67
81
|
```bash
|
|
68
82
|
? Enter the project name: badass-app
|
|
69
83
|
? Enter a description: My savage Next.js monster
|
|
70
|
-
? Select a template:
|
|
84
|
+
? Select a template: nextjs
|
|
71
85
|
? Initialize a git repository? yes
|
|
72
86
|
```
|
|
73
87
|
|
|
@@ -77,7 +91,9 @@ Boom. Done.
|
|
|
77
91
|
|
|
78
92
|
## 📦 What You Actually Get
|
|
79
93
|
|
|
80
|
-
###
|
|
94
|
+
### Next.js Full-Stack Template
|
|
95
|
+
|
|
96
|
+
**Tech Stack (The real weapons)**
|
|
81
97
|
|
|
82
98
|
- **Framework**: Next.js
|
|
83
99
|
- **Runtime**: Bun (fast as shit)
|
|
@@ -93,6 +109,20 @@ Boom. Done.
|
|
|
93
109
|
- **Forms**: react-hook-form + Zod
|
|
94
110
|
- **Theme**: Dark mode out of the box
|
|
95
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
|
+
|
|
96
126
|
---
|
|
97
127
|
|
|
98
128
|
## 🛠 Setup & Development
|
|
@@ -101,8 +131,8 @@ Boom. Done.
|
|
|
101
131
|
|
|
102
132
|
- Node 24+ or Bun
|
|
103
133
|
- PostgreSQL running
|
|
104
|
-
- OAuth creds if you want GitHub/Google login
|
|
105
|
-
- 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)
|
|
106
136
|
|
|
107
137
|
### Install
|
|
108
138
|
|
|
@@ -124,26 +154,38 @@ Don’t commit this crap. Seriously.
|
|
|
124
154
|
|
|
125
155
|
## 💻 Development
|
|
126
156
|
|
|
157
|
+
### Next.js Template
|
|
158
|
+
|
|
127
159
|
```bash
|
|
128
160
|
bun run dev
|
|
129
161
|
```
|
|
130
162
|
|
|
131
163
|
Open `http://localhost:3000` and admire your badass setup.
|
|
132
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
|
+
|
|
133
175
|
---
|
|
134
176
|
|
|
135
177
|
## 🐳 Docker (Ship it like a pro)
|
|
136
178
|
|
|
137
179
|
```bash
|
|
138
|
-
|
|
139
|
-
docker run -p 3000:3000 my-
|
|
180
|
+
bun run build:docker
|
|
181
|
+
docker run -p 3000:3000 my-awesome-project
|
|
140
182
|
```
|
|
141
183
|
|
|
142
|
-
No
|
|
184
|
+
No "works on my machine" bullshit anymore.
|
|
143
185
|
|
|
144
186
|
---
|
|
145
187
|
|
|
146
|
-
## ☁️ Infrastructure (The Cloud Shit)
|
|
188
|
+
## ☁️ Infrastructure (The Cloud Shit) - Next.js Template
|
|
147
189
|
|
|
148
190
|
Terraform config included for:
|
|
149
191
|
|
|
@@ -167,7 +209,7 @@ And now you’re running production infra like a grown-ass engineer.
|
|
|
167
209
|
|
|
168
210
|
---
|
|
169
211
|
|
|
170
|
-
## 🔐 Authentication
|
|
212
|
+
## 🔐 Authentication - Next.js Template
|
|
171
213
|
|
|
172
214
|
Better Auth gives you:
|
|
173
215
|
|
|
@@ -181,7 +223,7 @@ Secure, extensible, no duct-taped auth spaghetti.
|
|
|
181
223
|
|
|
182
224
|
---
|
|
183
225
|
|
|
184
|
-
## 🎨 UI Components
|
|
226
|
+
## 🎨 UI Components - Next.js Template
|
|
185
227
|
|
|
186
228
|
Preinstalled:
|
|
187
229
|
|
|
@@ -201,7 +243,7 @@ Ship beautiful shit without reinventing buttons for the 900th time.
|
|
|
201
243
|
|
|
202
244
|
---
|
|
203
245
|
|
|
204
|
-
## 🎯 Route Groups
|
|
246
|
+
## 🎯 Route Groups - Next.js Template
|
|
205
247
|
|
|
206
248
|
Organized like a sane human:
|
|
207
249
|
|
|
@@ -216,6 +258,8 @@ No messy folder hell.
|
|
|
216
258
|
|
|
217
259
|
## 🔧 Customization
|
|
218
260
|
|
|
261
|
+
### Next.js Template
|
|
262
|
+
|
|
219
263
|
Edit:
|
|
220
264
|
|
|
221
265
|
- `siteConfig.ts` – change name/description
|
|
@@ -224,6 +268,38 @@ Edit:
|
|
|
224
268
|
|
|
225
269
|
Then push schema and move on with your life.
|
|
226
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
|
+
|
|
227
303
|
---
|
|
228
304
|
|
|
229
305
|
## 🤝 Contributing
|
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
|
+
}
|