@totaland/create-starter-kit 2.0.0 → 2.0.2
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 +27 -3
- package/bin/index.js +88 -30
- package/package.json +1 -1
- package/templates/backend/AGENTS.md +0 -23
- package/templates/backend/ARCHITECTURE.md +0 -53
- package/templates/backend/ORDER_SYSTEM.md +0 -93
- package/templates/backend/biome.json +0 -3
- package/templates/backend/drizzle.config.ts +0 -10
- package/templates/backend/knip.json +0 -10
- package/templates/backend/package.json +0 -55
- package/templates/backend/playwright.config.ts +0 -16
- package/templates/backend/pnpm-workspace.yaml +0 -3
- package/templates/backend/src/features/health/controller.ts +0 -5
- package/templates/backend/src/features/health/index.ts +0 -6
- package/templates/backend/src/features/orders/controller.ts +0 -13
- package/templates/backend/src/features/orders/index.ts +0 -7
- package/templates/backend/src/index.ts +0 -76
- package/templates/backend/tsconfig.build.json +0 -10
- package/templates/backend/tsconfig.json +0 -30
- package/templates/backend/vitest.config.ts +0 -24
- package/templates/frontend/.env.example +0 -7
- package/templates/frontend/README.md +0 -132
- package/templates/frontend/RECOMMENDED_LIBRARIES.md +0 -226
- package/templates/frontend/SETUP_SUMMARY.md +0 -162
- package/templates/frontend/biome.json +0 -47
- package/templates/frontend/components.json +0 -22
- package/templates/frontend/index.html +0 -13
- package/templates/frontend/package.json +0 -44
- package/templates/frontend/postcss.config.js +0 -6
- package/templates/frontend/public/vite.svg +0 -1
- package/templates/frontend/src/App.css +0 -42
- package/templates/frontend/src/App.tsx +0 -17
- package/templates/frontend/src/assets/react.svg +0 -1
- package/templates/frontend/src/components/layout/Layout.tsx +0 -31
- package/templates/frontend/src/components/menu-toggle-icon.tsx +0 -53
- package/templates/frontend/src/components/ui/button.tsx +0 -57
- package/templates/frontend/src/hooks/use-scroll.ts +0 -21
- package/templates/frontend/src/index.css +0 -121
- package/templates/frontend/src/lib/api-client.ts +0 -46
- package/templates/frontend/src/lib/utils.ts +0 -6
- package/templates/frontend/src/main.tsx +0 -30
- package/templates/frontend/src/pages/about/AboutPage.tsx +0 -50
- package/templates/frontend/src/pages/home/HomePage.tsx +0 -43
- package/templates/frontend/tailwind.config.js +0 -59
- package/templates/frontend/tsconfig.app.json +0 -41
- package/templates/frontend/tsconfig.json +0 -13
- package/templates/frontend/tsconfig.node.json +0 -26
- package/templates/frontend/vite.config.ts +0 -14
package/README.md
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
# @totaland/create-starter-kit
|
|
2
2
|
|
|
3
|
-
Scaffolding tool for creating new starter-kit projects with a choice between backend
|
|
3
|
+
Scaffolding tool for creating new starter-kit projects with a choice between backend, frontend, or fullstack templates.
|
|
4
4
|
|
|
5
5
|
## Features
|
|
6
6
|
|
|
7
|
-
- ✨ Interactive template selection (backend or
|
|
7
|
+
- ✨ Interactive template selection (backend, frontend, or fullstack)
|
|
8
8
|
- 🎯 CLI argument support for automation
|
|
9
9
|
- 📦 Clean, production-ready templates
|
|
10
10
|
- 🚀 Quick project initialization
|
|
@@ -20,6 +20,7 @@ pnpm create @totaland/starter-kit my-new-project
|
|
|
20
20
|
You'll be prompted to select a template:
|
|
21
21
|
- **Backend** - Express.js + TypeScript + Drizzle ORM + Scalar API docs
|
|
22
22
|
- **Frontend** - React + Vite + TypeScript + Tailwind CSS v4 + shadcn/ui + TanStack Query
|
|
23
|
+
- **Fullstack** - Both Backend and Frontend in one project
|
|
23
24
|
|
|
24
25
|
### With Template Argument
|
|
25
26
|
|
|
@@ -31,6 +32,9 @@ pnpm create @totaland/starter-kit my-backend backend
|
|
|
31
32
|
|
|
32
33
|
# Create frontend project
|
|
33
34
|
pnpm create @totaland/starter-kit my-frontend frontend
|
|
35
|
+
|
|
36
|
+
# Create fullstack project (both backend and frontend)
|
|
37
|
+
pnpm create @totaland/starter-kit my-app fullstack
|
|
34
38
|
```
|
|
35
39
|
|
|
36
40
|
### Alternative Package Managers
|
|
@@ -107,10 +111,29 @@ pnpm dev
|
|
|
107
111
|
pnpm dlx shadcn@latest add button card dialog
|
|
108
112
|
```
|
|
109
113
|
|
|
114
|
+
### Fullstack Template
|
|
115
|
+
|
|
116
|
+
Creates both backend and frontend in separate subdirectories.
|
|
117
|
+
|
|
118
|
+
**After Creation:**
|
|
119
|
+
```bash
|
|
120
|
+
cd my-app
|
|
121
|
+
|
|
122
|
+
# Run backend
|
|
123
|
+
cd backend
|
|
124
|
+
pnpm install
|
|
125
|
+
pnpm dev
|
|
126
|
+
|
|
127
|
+
# Run frontend (in another terminal)
|
|
128
|
+
cd frontend
|
|
129
|
+
pnpm install
|
|
130
|
+
pnpm dev
|
|
131
|
+
```
|
|
132
|
+
|
|
110
133
|
## What It Does
|
|
111
134
|
|
|
112
135
|
1. ✅ Creates a new directory with your project name
|
|
113
|
-
2. ✅ Copies the selected template (backend or
|
|
136
|
+
2. ✅ Copies the selected template (backend, frontend, or both for fullstack)
|
|
114
137
|
3. ✅ Updates the `package.json` name field to match your project name
|
|
115
138
|
4. ✅ Provides next steps for installation and running the project
|
|
116
139
|
|
|
@@ -139,6 +162,7 @@ To test this locally before publishing:
|
|
|
139
162
|
# Or specify template directly
|
|
140
163
|
pnpm create @totaland/starter-kit test-backend backend
|
|
141
164
|
pnpm create @totaland/starter-kit test-frontend frontend
|
|
165
|
+
pnpm create @totaland/starter-kit test-fullstack fullstack
|
|
142
166
|
```
|
|
143
167
|
|
|
144
168
|
## Publishing
|
package/bin/index.js
CHANGED
|
@@ -21,12 +21,12 @@ const templateArg = process.argv[3]; // Optional template argument
|
|
|
21
21
|
if (!projectName) {
|
|
22
22
|
console.error('Error: Please provide a project name');
|
|
23
23
|
console.log('Usage: pnpm create @totaland/starter-kit <project-name> [template]');
|
|
24
|
-
console.log('Templates: backend, frontend');
|
|
24
|
+
console.log('Templates: backend, frontend, fullstack');
|
|
25
25
|
process.exit(1);
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
// Resolve paths
|
|
29
|
-
const
|
|
28
|
+
// Resolve paths - use sibling directories from the starter-kit root
|
|
29
|
+
const monorepoRoot = resolve(__dirname, '../..');
|
|
30
30
|
const targetDir = resolve(process.cwd(), projectName);
|
|
31
31
|
|
|
32
32
|
// Check if target directory already exists
|
|
@@ -35,20 +35,45 @@ if (existsSync(targetDir)) {
|
|
|
35
35
|
process.exit(1);
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
-
// Available templates
|
|
38
|
+
// Available templates - reference sibling directories in the monorepo
|
|
39
39
|
const TEMPLATES = {
|
|
40
40
|
backend: {
|
|
41
41
|
name: 'Backend',
|
|
42
42
|
description: 'Express.js backend with TypeScript, Drizzle ORM, and Scalar API docs',
|
|
43
|
-
|
|
43
|
+
sourceDir: join(monorepoRoot, 'backend'),
|
|
44
44
|
},
|
|
45
45
|
frontend: {
|
|
46
46
|
name: 'Frontend',
|
|
47
47
|
description: 'React + Vite with TypeScript, Tailwind CSS v4, shadcn/ui, and TanStack Query',
|
|
48
|
-
|
|
48
|
+
sourceDir: join(monorepoRoot, 'frontend'),
|
|
49
|
+
},
|
|
50
|
+
fullstack: {
|
|
51
|
+
name: 'Fullstack',
|
|
52
|
+
description: 'Both Backend and Frontend templates combined',
|
|
53
|
+
sourceDirs: [
|
|
54
|
+
{ name: 'backend', path: join(monorepoRoot, 'backend') },
|
|
55
|
+
{ name: 'frontend', path: join(monorepoRoot, 'frontend') },
|
|
56
|
+
],
|
|
49
57
|
},
|
|
50
58
|
};
|
|
51
59
|
|
|
60
|
+
// Directories and files to exclude when copying
|
|
61
|
+
const EXCLUDE = new Set([
|
|
62
|
+
'node_modules',
|
|
63
|
+
'.git',
|
|
64
|
+
'dist',
|
|
65
|
+
'build',
|
|
66
|
+
'.turbo',
|
|
67
|
+
'.next',
|
|
68
|
+
'.nuxt',
|
|
69
|
+
'.output',
|
|
70
|
+
'.cache',
|
|
71
|
+
'coverage',
|
|
72
|
+
'.env',
|
|
73
|
+
'.env.local',
|
|
74
|
+
'.DS_Store',
|
|
75
|
+
]);
|
|
76
|
+
|
|
52
77
|
// Function to recursively copy directory
|
|
53
78
|
function copyDir(src, dest) {
|
|
54
79
|
mkdirSync(dest, { recursive: true });
|
|
@@ -56,6 +81,11 @@ function copyDir(src, dest) {
|
|
|
56
81
|
const entries = readdirSync(src, { withFileTypes: true });
|
|
57
82
|
|
|
58
83
|
for (const entry of entries) {
|
|
84
|
+
// Skip excluded files and directories
|
|
85
|
+
if (EXCLUDE.has(entry.name)) {
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
|
|
59
89
|
const srcPath = join(src, entry.name);
|
|
60
90
|
const destPath = join(dest, entry.name);
|
|
61
91
|
|
|
@@ -75,10 +105,11 @@ async function promptTemplate() {
|
|
|
75
105
|
});
|
|
76
106
|
|
|
77
107
|
console.log('\n📦 Select a template:\n');
|
|
78
|
-
console.log('1. Backend
|
|
79
|
-
console.log('2. Frontend
|
|
108
|
+
console.log('1. Backend - Express.js + TypeScript + Drizzle ORM');
|
|
109
|
+
console.log('2. Frontend - React + Vite + Tailwind CSS v4 + shadcn/ui');
|
|
110
|
+
console.log('3. Fullstack - Both Backend and Frontend\n');
|
|
80
111
|
|
|
81
|
-
const answer = await rl.question('Enter your choice (1 or
|
|
112
|
+
const answer = await rl.question('Enter your choice (1, 2, or 3): ');
|
|
82
113
|
rl.close();
|
|
83
114
|
|
|
84
115
|
if (answer === '1' || answer.toLowerCase() === 'backend') {
|
|
@@ -87,6 +118,9 @@ async function promptTemplate() {
|
|
|
87
118
|
if (answer === '2' || answer.toLowerCase() === 'frontend') {
|
|
88
119
|
return 'frontend';
|
|
89
120
|
}
|
|
121
|
+
if (answer === '3' || answer.toLowerCase() === 'fullstack') {
|
|
122
|
+
return 'fullstack';
|
|
123
|
+
}
|
|
90
124
|
|
|
91
125
|
console.error('Invalid choice. Please run the command again.');
|
|
92
126
|
process.exit(1);
|
|
@@ -102,7 +136,7 @@ async function main() {
|
|
|
102
136
|
templateKey = templateArg.toLowerCase();
|
|
103
137
|
if (!TEMPLATES[templateKey]) {
|
|
104
138
|
console.error(`Error: Invalid template "${templateArg}"`);
|
|
105
|
-
console.log('Available templates: backend, frontend');
|
|
139
|
+
console.log('Available templates: backend, frontend, fullstack');
|
|
106
140
|
process.exit(1);
|
|
107
141
|
}
|
|
108
142
|
} else {
|
|
@@ -111,37 +145,61 @@ async function main() {
|
|
|
111
145
|
}
|
|
112
146
|
|
|
113
147
|
const template = TEMPLATES[templateKey];
|
|
114
|
-
const templateDir = join(templatesDir, template.dir);
|
|
115
|
-
|
|
116
|
-
// Verify template directory exists
|
|
117
|
-
if (!existsSync(templateDir)) {
|
|
118
|
-
console.error(`Error: Template directory not found: ${templateDir}`);
|
|
119
|
-
process.exit(1);
|
|
120
|
-
}
|
|
121
148
|
|
|
122
149
|
console.log(`\n🚀 Creating ${template.name} project: ${projectName}`);
|
|
123
150
|
console.log(`📁 ${template.description}\n`);
|
|
124
151
|
|
|
125
|
-
//
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
152
|
+
// Handle fullstack (multiple templates) or single template
|
|
153
|
+
if (template.sourceDirs) {
|
|
154
|
+
// Fullstack: copy each template to a subdirectory
|
|
155
|
+
for (const { name, path: sourceDir } of template.sourceDirs) {
|
|
156
|
+
if (!existsSync(sourceDir)) {
|
|
157
|
+
console.error(`Error: Source directory not found: ${sourceDir}`);
|
|
158
|
+
process.exit(1);
|
|
159
|
+
}
|
|
160
|
+
const subTargetDir = join(targetDir, name);
|
|
161
|
+
copyDir(sourceDir, subTargetDir);
|
|
162
|
+
|
|
163
|
+
// Update package.json name for each sub-project
|
|
164
|
+
const packageJsonPath = join(subTargetDir, 'package.json');
|
|
165
|
+
if (existsSync(packageJsonPath)) {
|
|
166
|
+
const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
|
|
167
|
+
packageJson.name = `${projectName}-${name}`;
|
|
168
|
+
writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2) + '\n');
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
} else {
|
|
172
|
+
// Single template
|
|
173
|
+
if (!existsSync(template.sourceDir)) {
|
|
174
|
+
console.error(`Error: Source directory not found: ${template.sourceDir}`);
|
|
175
|
+
process.exit(1);
|
|
176
|
+
}
|
|
177
|
+
copyDir(template.sourceDir, targetDir);
|
|
178
|
+
|
|
179
|
+
// Update package.json with the new project name
|
|
180
|
+
const packageJsonPath = join(targetDir, 'package.json');
|
|
181
|
+
if (existsSync(packageJsonPath)) {
|
|
182
|
+
const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
|
|
183
|
+
packageJson.name = projectName;
|
|
184
|
+
writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2) + '\n');
|
|
185
|
+
}
|
|
134
186
|
}
|
|
135
187
|
|
|
136
188
|
console.log('✅ Project created successfully!\n');
|
|
137
189
|
console.log('📝 Next steps:');
|
|
138
190
|
console.log(` cd ${projectName}`);
|
|
139
|
-
|
|
140
|
-
|
|
191
|
+
if (templateKey === 'fullstack') {
|
|
192
|
+
console.log(' cd backend && pnpm install && pnpm dev');
|
|
193
|
+
console.log(' cd frontend && pnpm install && pnpm dev\n');
|
|
194
|
+
} else {
|
|
195
|
+
console.log(' pnpm install');
|
|
196
|
+
console.log(' pnpm dev\n');
|
|
197
|
+
}
|
|
141
198
|
|
|
142
|
-
if (templateKey === 'frontend') {
|
|
199
|
+
if (templateKey === 'frontend' || templateKey === 'fullstack') {
|
|
143
200
|
console.log('💡 Tip: Add shadcn/ui components with:');
|
|
144
|
-
|
|
201
|
+
const cdPath = templateKey === 'fullstack' ? 'cd frontend && ' : '';
|
|
202
|
+
console.log(` ${cdPath}pnpm dlx shadcn@latest add button card dialog\n`);
|
|
145
203
|
}
|
|
146
204
|
} catch (error) {
|
|
147
205
|
console.error('Error creating project:', error.message);
|
package/package.json
CHANGED
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
# Repository Guidelines
|
|
2
|
-
|
|
3
|
-
## Project Structure & Module Organization
|
|
4
|
-
TypeScript sources live in `src/`, with graph traversal logic under `src/graph/` (crawl policy, state hashing, trajectory storage) and telemetry helpers in `src/telemetry.ts`. Example entrypoints such as `src/demo.ts` show how agents orchestrate flows. Generated JavaScript lands in `build/` (never hand-edit). Documentation and design notes are under `docs/`, and replay artifacts persist to `storage/` while running crawls or demo scripts. Keep tests adjacent to the code (for example `src/graph/xstate.test.ts`).
|
|
5
|
-
|
|
6
|
-
## Build, Test, and Development Commands
|
|
7
|
-
- `pnpm install`: sets up dependencies for both the CLI and Playwright runtime.
|
|
8
|
-
- `pnpm typecheck`: runs `tsc --noEmit` against `tsconfig.json` to catch type regressions early.
|
|
9
|
-
- `pnpm build`: compiles `src/` to ESM output in `build/` via SWC; use `pnpm build:debug` when you need source maps.
|
|
10
|
-
- `pnpm test`, `pnpm test:watch`, `pnpm test:ui`: execute the Vitest suite in batch, watch, or UI mode.
|
|
11
|
-
- `pnpm knip`: detects unused files, exports, and dependencies—fix warnings before raising a PR.
|
|
12
|
-
|
|
13
|
-
## Coding Style & Naming Conventions
|
|
14
|
-
Follow the repository's Biome configuration (`biome.json`) and run `npx biome check .` if needed. Use 2-space indentation, TypeScript `strict` semantics, and ECMAScript modules (`import/export`). Exported symbols and files should read as actions or nouns (`discoverState`, `persist.ts`). Tests mirror the file under test (`xstate.test.ts`). Prefer descriptive async function names reflecting their side effects.
|
|
15
|
-
|
|
16
|
-
## Testing Guidelines
|
|
17
|
-
Vitest drives all unit and integration coverage. Name suites after the module (`describe("bfs")`) and isolate Playwright-heavy tests behind capability checks to keep CI fast. New behavior must include at least one test validating failure handling and a corresponding happy path. Run `pnpm test` locally before committing; `storage/` fixtures may be stubbed with lightweight mocks to avoid hitting real browsers.
|
|
18
|
-
|
|
19
|
-
## Commit & Pull Request Guidelines
|
|
20
|
-
Use imperative, conventional messages observed in history (`feat:`, `chore:`, `fix:`). One logical change per commit, referencing an issue ID when relevant. Pull requests should describe motivation, summarize testing (`pnpm test` output, screenshots for Playwright interactions), and link design docs in `docs/` if expanded. Request review from an owner when touching crawl policy or telemetry layers, and ensure CI (typecheck, build, tests) passes before assignment.
|
|
21
|
-
|
|
22
|
-
## Environment & Configuration Tips
|
|
23
|
-
Configure credentials through `.env` files consumed by Crawlee/Playwright—never hard-code tokens. For telemetry, set OpenTelemetry exporters via environment variables before running demos. Heavy crawls persist checkpoints to `storage/`; clean stale runs before committing to keep diffs focused.
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
# Architecture Pattern Examples
|
|
2
|
-
|
|
3
|
-
This starter kit demonstrates a blend of patterns from **"Designing Data-Intensive Applications"** and **"Patterns of Enterprise Application Architecture"**.
|
|
4
|
-
|
|
5
|
-
## Order System Example
|
|
6
|
-
|
|
7
|
-
### From Designing Data-Intensive Applications (DDIA):
|
|
8
|
-
|
|
9
|
-
1. **Event Sourcing** - All state changes stored as immutable events
|
|
10
|
-
- `order.entity.ts`: Events are the source of truth
|
|
11
|
-
- Events: `OrderCreated`, `ItemAdded`, `OrderPlaced`
|
|
12
|
-
|
|
13
|
-
2. **CQRS (Command Query Responsibility Segregation)**
|
|
14
|
-
- Commands: `commands.usecase.ts` - Write operations that generate events
|
|
15
|
-
- Queries: `queries.usecase.ts` - Read operations from materialized views
|
|
16
|
-
|
|
17
|
-
3. **Materialized Views** - Denormalized data for fast queries
|
|
18
|
-
- `order.repository.impl.ts`: `readModel` maintains pre-computed summaries
|
|
19
|
-
- User index for O(1) lookups by user ID
|
|
20
|
-
|
|
21
|
-
4. **Append-Only Log** - Event store never modifies existing data
|
|
22
|
-
- New events are appended, never updated or deleted
|
|
23
|
-
|
|
24
|
-
### From Patterns of Enterprise Application Architecture (PEAA):
|
|
25
|
-
|
|
26
|
-
1. **Repository Pattern** - Abstract data access
|
|
27
|
-
- `order.repository.ts`: Interface defining data operations
|
|
28
|
-
- Separates domain logic from persistence
|
|
29
|
-
|
|
30
|
-
2. **Domain Model** - Rich business logic in entities
|
|
31
|
-
- `order.entity.ts`: Order entity with business rules and validation
|
|
32
|
-
|
|
33
|
-
3. **Use Case / Service Layer** - Application logic orchestration
|
|
34
|
-
- Commands and queries as separate use cases
|
|
35
|
-
- Transaction boundaries and workflow management
|
|
36
|
-
|
|
37
|
-
## API Endpoints
|
|
38
|
-
|
|
39
|
-
```
|
|
40
|
-
POST /orders # Create new order
|
|
41
|
-
POST /orders/:id/items # Add item to order
|
|
42
|
-
POST /orders/:id/place # Place order
|
|
43
|
-
GET /orders/:id # Get order details
|
|
44
|
-
GET /orders/user/:userId # Get user's orders (fast via materialized view)
|
|
45
|
-
GET /orders/:id/events # Get event stream (audit log)
|
|
46
|
-
```
|
|
47
|
-
|
|
48
|
-
## Key Benefits
|
|
49
|
-
|
|
50
|
-
- **Scalability**: Read/write separation allows independent scaling
|
|
51
|
-
- **Auditability**: Complete event history for compliance/debugging
|
|
52
|
-
- **Performance**: Materialized views optimize common queries
|
|
53
|
-
- **Maintainability**: Clean architecture with separated concerns
|
|
@@ -1,93 +0,0 @@
|
|
|
1
|
-
# Order System with Drizzle ORM
|
|
2
|
-
|
|
3
|
-
## Setup
|
|
4
|
-
|
|
5
|
-
### 1. Environment Variables
|
|
6
|
-
|
|
7
|
-
Create a `.env` file:
|
|
8
|
-
|
|
9
|
-
```bash
|
|
10
|
-
# Optional: PostgreSQL connection (if not set, uses in-memory)
|
|
11
|
-
DATABASE_URL=postgres://user:password@localhost:5432/orders_db
|
|
12
|
-
|
|
13
|
-
PORT=3000
|
|
14
|
-
```
|
|
15
|
-
|
|
16
|
-
### 2. Database Setup (Optional - for PostgreSQL)
|
|
17
|
-
|
|
18
|
-
If you want to use PostgreSQL instead of in-memory storage:
|
|
19
|
-
|
|
20
|
-
```bash
|
|
21
|
-
# Generate migration
|
|
22
|
-
pnpm drizzle-kit generate
|
|
23
|
-
|
|
24
|
-
# Run migration
|
|
25
|
-
pnpm drizzle-kit migrate
|
|
26
|
-
|
|
27
|
-
# Or use push for development
|
|
28
|
-
pnpm drizzle-kit push
|
|
29
|
-
```
|
|
30
|
-
|
|
31
|
-
### 3. Run the Server
|
|
32
|
-
|
|
33
|
-
```bash
|
|
34
|
-
# Development
|
|
35
|
-
pnpm dev
|
|
36
|
-
|
|
37
|
-
# Production
|
|
38
|
-
pnpm build
|
|
39
|
-
pnpm start
|
|
40
|
-
```
|
|
41
|
-
|
|
42
|
-
## API Endpoints
|
|
43
|
-
|
|
44
|
-
Base URL: `http://localhost:3000/api`
|
|
45
|
-
|
|
46
|
-
### Create Order
|
|
47
|
-
```bash
|
|
48
|
-
POST /api/orders
|
|
49
|
-
Content-Type: application/json
|
|
50
|
-
|
|
51
|
-
{
|
|
52
|
-
"userId": "550e8400-e29b-41d4-a716-446655440000"
|
|
53
|
-
}
|
|
54
|
-
```
|
|
55
|
-
|
|
56
|
-
### Add Item to Order
|
|
57
|
-
```bash
|
|
58
|
-
POST /api/orders/:orderId/items
|
|
59
|
-
Content-Type: application/json
|
|
60
|
-
|
|
61
|
-
{
|
|
62
|
-
"productId": "660e8400-e29b-41d4-a716-446655440000",
|
|
63
|
-
"quantity": 2,
|
|
64
|
-
"price": 29.99
|
|
65
|
-
}
|
|
66
|
-
```
|
|
67
|
-
|
|
68
|
-
### Place Order
|
|
69
|
-
```bash
|
|
70
|
-
POST /api/orders/:orderId/place
|
|
71
|
-
```
|
|
72
|
-
|
|
73
|
-
### Get Order Details
|
|
74
|
-
```bash
|
|
75
|
-
GET /api/orders/:orderId
|
|
76
|
-
```
|
|
77
|
-
|
|
78
|
-
### Get User's Orders (Fast - from materialized view)
|
|
79
|
-
```bash
|
|
80
|
-
GET /api/orders/user/:userId
|
|
81
|
-
```
|
|
82
|
-
|
|
83
|
-
### Get Order Event Stream (Audit log)
|
|
84
|
-
```bash
|
|
85
|
-
GET /api/orders/:orderId/events
|
|
86
|
-
```
|
|
87
|
-
|
|
88
|
-
## Architecture
|
|
89
|
-
|
|
90
|
-
- **No DATABASE_URL**: Uses in-memory repository (for testing/demo)
|
|
91
|
-
- **With DATABASE_URL**: Uses Drizzle ORM with PostgreSQL
|
|
92
|
-
- Event Sourcing with materialized views for optimal read performance
|
|
93
|
-
- CQRS pattern with separate command and query operations
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import { defineConfig } from 'drizzle-kit';
|
|
2
|
-
|
|
3
|
-
export default defineConfig({
|
|
4
|
-
schema: './src/infrastructure/database/schema.ts',
|
|
5
|
-
out: './drizzle',
|
|
6
|
-
dialect: 'postgresql',
|
|
7
|
-
dbCredentials: {
|
|
8
|
-
url: process.env.DATABASE_URL || 'postgres://localhost:5432/orders_db',
|
|
9
|
-
},
|
|
10
|
-
});
|
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "ultimate-express-starter",
|
|
3
|
-
"version": "1.0.0",
|
|
4
|
-
"description": "Fast TypeScript backend starter kit with Ultimate Express, Scalar API docs, and Drizzle ORM.",
|
|
5
|
-
"type": "module",
|
|
6
|
-
"main": "build/index.js",
|
|
7
|
-
"scripts": {
|
|
8
|
-
"start": "node build/index.js",
|
|
9
|
-
"dev": "swc src -d build --strip-leading-paths --copy-files --watch & node --watch build/index.js",
|
|
10
|
-
"typecheck": "npx tsc --noEmit",
|
|
11
|
-
"build": "swc src -d build --strip-leading-paths --copy-files",
|
|
12
|
-
"build:debug": "swc src -d build --strip-leading-paths --copy-files --source-maps",
|
|
13
|
-
"test": "vitest run",
|
|
14
|
-
"test:watch": "vitest",
|
|
15
|
-
"test:ui": "vitest --ui",
|
|
16
|
-
"prebuild": "rm -rf build",
|
|
17
|
-
"knip": "knip",
|
|
18
|
-
"db:generate": "drizzle-kit generate",
|
|
19
|
-
"db:migrate": "drizzle-kit migrate",
|
|
20
|
-
"db:push": "drizzle-kit push",
|
|
21
|
-
"db:studio": "drizzle-kit studio"
|
|
22
|
-
},
|
|
23
|
-
"files": [
|
|
24
|
-
"build"
|
|
25
|
-
],
|
|
26
|
-
"devDependencies": {
|
|
27
|
-
"@biomejs/biome": "2.0.6",
|
|
28
|
-
"@swc-node/register": "^1.11.1",
|
|
29
|
-
"@swc/cli": "0.7.7",
|
|
30
|
-
"@swc/core": "^1.10.1",
|
|
31
|
-
"@types/bun": "1.2.14",
|
|
32
|
-
"@types/cors": "^2.8.19",
|
|
33
|
-
"@types/node": "24.0.7",
|
|
34
|
-
"@vitest/ui": "^3.2.4",
|
|
35
|
-
"knip": "^5.69.1",
|
|
36
|
-
"tsx": "^4.20.6",
|
|
37
|
-
"typescript": "^5.8.3",
|
|
38
|
-
"vitest": "^4.0.10"
|
|
39
|
-
},
|
|
40
|
-
"dependencies": {
|
|
41
|
-
"@scalar/express-api-reference": "^0.8.25",
|
|
42
|
-
"cors": "^2.8.5",
|
|
43
|
-
"dotenv": "17.0.0",
|
|
44
|
-
"drizzle-kit": "^0.31.7",
|
|
45
|
-
"drizzle-orm": "^0.44.7",
|
|
46
|
-
"lru-cache": "^11.2.2",
|
|
47
|
-
"postgres": "^3.4.7",
|
|
48
|
-
"ultimate-express": "^2.0.12",
|
|
49
|
-
"ultimate-ws": "^2.0.6",
|
|
50
|
-
"zod": "^4.1.12"
|
|
51
|
-
},
|
|
52
|
-
"trustedDependencies": [
|
|
53
|
-
"@swc/core"
|
|
54
|
-
]
|
|
55
|
-
}
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import { defineConfig } from "@playwright/test";
|
|
2
|
-
import { config as loadEnv } from "dotenv";
|
|
3
|
-
|
|
4
|
-
// Load environment variables (e.g. PLAYWRIGHT_BASE_URL) before tests spin up.
|
|
5
|
-
loadEnv();
|
|
6
|
-
|
|
7
|
-
export default defineConfig({
|
|
8
|
-
testDir: "src/generated/tests",
|
|
9
|
-
reporter: [
|
|
10
|
-
["line"],
|
|
11
|
-
["html", { outputFolder: "playwright-report", open: "never" }],
|
|
12
|
-
],
|
|
13
|
-
use: {
|
|
14
|
-
baseURL: process.env.PLAYWRIGHT_BASE_URL ?? "http://localhost:3000",
|
|
15
|
-
},
|
|
16
|
-
});
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import type { Request, Response } from 'ultimate-express';
|
|
2
|
-
|
|
3
|
-
export const getOrders = (req: Request, res: Response) => {
|
|
4
|
-
res.json([
|
|
5
|
-
{ id: 1, item: 'Laptop', price: 1200 },
|
|
6
|
-
{ id: 2, item: 'Mouse', price: 25 },
|
|
7
|
-
]);
|
|
8
|
-
};
|
|
9
|
-
|
|
10
|
-
export const createOrder = (req: Request, res: Response) => {
|
|
11
|
-
const { item, price } = req.body;
|
|
12
|
-
res.status(201).json({ id: 3, item, price });
|
|
13
|
-
};
|
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
// example backend with ultimate-express
|
|
2
|
-
import 'dotenv/config';
|
|
3
|
-
import express from 'ultimate-express';
|
|
4
|
-
import type { Request, Response } from 'ultimate-express';
|
|
5
|
-
import cors from 'cors';
|
|
6
|
-
import { ordersRouter } from './features/orders/index.js';
|
|
7
|
-
import { healthRouter } from './features/health/index.js';
|
|
8
|
-
import { apiReference } from '@scalar/express-api-reference';
|
|
9
|
-
|
|
10
|
-
const app = express();
|
|
11
|
-
|
|
12
|
-
app.use(cors());
|
|
13
|
-
app.use(express.json());
|
|
14
|
-
|
|
15
|
-
app.get('/', (req: Request, res: Response) => {
|
|
16
|
-
res.send('Hello, Ultimate Express!');
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
// Mount feature routes
|
|
20
|
-
app.use('/api/orders', ordersRouter);
|
|
21
|
-
app.use('/api/health', healthRouter);
|
|
22
|
-
|
|
23
|
-
// API Documentation with Scalar
|
|
24
|
-
app.use(
|
|
25
|
-
'/api/docs',
|
|
26
|
-
apiReference({
|
|
27
|
-
spec: {
|
|
28
|
-
url: '/openapi.json',
|
|
29
|
-
},
|
|
30
|
-
}),
|
|
31
|
-
);
|
|
32
|
-
|
|
33
|
-
// Example OpenAPI spec endpoint
|
|
34
|
-
app.get('/openapi.json', (req: Request, res: Response) => {
|
|
35
|
-
res.json({
|
|
36
|
-
openapi: '3.1.0',
|
|
37
|
-
info: {
|
|
38
|
-
title: 'Ultimate Express API',
|
|
39
|
-
version: '1.0.0',
|
|
40
|
-
description: 'API documentation for Ultimate Express starter kit',
|
|
41
|
-
},
|
|
42
|
-
servers: [
|
|
43
|
-
{
|
|
44
|
-
url: `http://localhost:${process.env.PORT || 3000}`,
|
|
45
|
-
description: 'Development server',
|
|
46
|
-
},
|
|
47
|
-
],
|
|
48
|
-
paths: {
|
|
49
|
-
'/': {
|
|
50
|
-
get: {
|
|
51
|
-
summary: 'Hello endpoint',
|
|
52
|
-
description: 'Returns a greeting message',
|
|
53
|
-
responses: {
|
|
54
|
-
'200': {
|
|
55
|
-
description: 'Successful response',
|
|
56
|
-
content: {
|
|
57
|
-
'text/html': {
|
|
58
|
-
schema: {
|
|
59
|
-
type: 'string',
|
|
60
|
-
example: 'Hello, Ultimate Express!',
|
|
61
|
-
},
|
|
62
|
-
},
|
|
63
|
-
},
|
|
64
|
-
},
|
|
65
|
-
},
|
|
66
|
-
},
|
|
67
|
-
},
|
|
68
|
-
},
|
|
69
|
-
});
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
const PORT = process.env.PORT || 3000;
|
|
73
|
-
app.listen(PORT, () => {
|
|
74
|
-
console.log(`Server is running on http://localhost:${PORT}`);
|
|
75
|
-
console.log(`API Documentation available at http://localhost:${PORT}/api/docs`);
|
|
76
|
-
});
|