@erwininteractive/mvc 0.1.1

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 (34) hide show
  1. package/README.md +174 -0
  2. package/dist/cli.d.ts +2 -0
  3. package/dist/cli.js +61 -0
  4. package/dist/framework/App.d.ts +38 -0
  5. package/dist/framework/App.js +100 -0
  6. package/dist/framework/Auth.d.ts +27 -0
  7. package/dist/framework/Auth.js +67 -0
  8. package/dist/framework/Router.d.ts +29 -0
  9. package/dist/framework/Router.js +125 -0
  10. package/dist/framework/db.d.ts +13 -0
  11. package/dist/framework/db.js +41 -0
  12. package/dist/framework/index.d.ts +5 -0
  13. package/dist/framework/index.js +22 -0
  14. package/dist/generators/generateController.d.ts +7 -0
  15. package/dist/generators/generateController.js +110 -0
  16. package/dist/generators/generateModel.d.ts +7 -0
  17. package/dist/generators/generateModel.js +77 -0
  18. package/dist/generators/initApp.d.ts +8 -0
  19. package/dist/generators/initApp.js +113 -0
  20. package/dist/generators/paths.d.ts +17 -0
  21. package/dist/generators/paths.js +55 -0
  22. package/package.json +72 -0
  23. package/prisma/schema.prisma +19 -0
  24. package/templates/appScaffold/README.md +297 -0
  25. package/templates/appScaffold/package.json +23 -0
  26. package/templates/appScaffold/public/favicon.svg +16 -0
  27. package/templates/appScaffold/src/controllers/HomeController.ts +9 -0
  28. package/templates/appScaffold/src/middleware/auth.ts +8 -0
  29. package/templates/appScaffold/src/server.ts +24 -0
  30. package/templates/appScaffold/src/views/index.ejs +300 -0
  31. package/templates/appScaffold/tsconfig.json +16 -0
  32. package/templates/controller.ts.ejs +98 -0
  33. package/templates/model.prisma.ejs +7 -0
  34. package/templates/view.ejs.ejs +42 -0
@@ -0,0 +1,297 @@
1
+ # My MVC App
2
+
3
+ Built with [@erwininteractive/mvc](https://github.com/erwininteractive/mvc).
4
+
5
+ ## Quick Start
6
+
7
+ ```bash
8
+ npm run dev
9
+ ```
10
+
11
+ Visit http://localhost:3000
12
+
13
+ ---
14
+
15
+ ## Getting Started
16
+
17
+ ### Step 1: Create a New Page
18
+
19
+ Create a new file `src/views/about.ejs`:
20
+
21
+ ```html
22
+ <!doctype html>
23
+ <html>
24
+ <head>
25
+ <title><%= title %></title>
26
+ </head>
27
+ <body>
28
+ <h1><%= title %></h1>
29
+ <p>Welcome to my about page!</p>
30
+ </body>
31
+ </html>
32
+ ```
33
+
34
+ ### Step 2: Add a Route
35
+
36
+ Edit `src/server.ts` and add a route for your page:
37
+
38
+ ```typescript
39
+ app.get("/about", (req, res) => {
40
+ res.render("about", { title: "About Us" });
41
+ });
42
+ ```
43
+
44
+ ### Step 3: View Your Page
45
+
46
+ Visit http://localhost:3000/about
47
+
48
+ ---
49
+
50
+ ## Creating Pages
51
+
52
+ ### EJS Templates
53
+
54
+ Create `.ejs` files in `src/views/`. EJS lets you use JavaScript in your HTML:
55
+
56
+ ```html
57
+ <!-- Output a variable (escaped) -->
58
+ <h1><%= title %></h1>
59
+
60
+ <!-- Output raw HTML -->
61
+ <%- htmlContent %>
62
+
63
+ <!-- JavaScript logic -->
64
+ <% if (user) { %>
65
+ <p>Welcome, <%= user.name %>!</p>
66
+ <% } %>
67
+
68
+ <!-- Loop through items -->
69
+ <ul>
70
+ <% items.forEach(item => { %>
71
+ <li><%= item.name %></li>
72
+ <% }); %>
73
+ </ul>
74
+
75
+ <!-- Include another template -->
76
+ <%- include('partials/header') %>
77
+ ```
78
+
79
+ ### Adding Routes
80
+
81
+ Edit `src/server.ts` to add routes:
82
+
83
+ ```typescript
84
+ // Simple page
85
+ app.get("/contact", (req, res) => {
86
+ res.render("contact", { title: "Contact Us" });
87
+ });
88
+
89
+ // Handle form submission
90
+ app.post("/contact", (req, res) => {
91
+ const { name, email, message } = req.body;
92
+ console.log(`Message from ${name}: ${message}`);
93
+ res.redirect("/contact?sent=true");
94
+ });
95
+
96
+ // JSON API endpoint
97
+ app.get("/api/users", (req, res) => {
98
+ res.json([{ id: 1, name: "John" }]);
99
+ });
100
+ ```
101
+
102
+ ---
103
+
104
+ ## Controllers
105
+
106
+ Controllers help organize your route handlers. Generate one with the CLI:
107
+
108
+ ```bash
109
+ npx erwinmvc generate controller Product
110
+ ```
111
+
112
+ This creates `src/controllers/ProductController.ts` with CRUD actions:
113
+
114
+ | Action | HTTP Method | URL | Description |
115
+ |-----------|-------------|------------------|-------------|
116
+ | `index` | GET | /products | List all |
117
+ | `show` | GET | /products/:id | Show one |
118
+ | `store` | POST | /products | Create |
119
+ | `update` | PUT | /products/:id | Update |
120
+ | `destroy` | DELETE | /products/:id | Delete |
121
+
122
+ ### Using Controllers
123
+
124
+ Import and wire up your controller in `src/server.ts`:
125
+
126
+ ```typescript
127
+ import * as ProductController from "./controllers/ProductController";
128
+
129
+ app.get("/products", ProductController.index);
130
+ app.get("/products/:id", ProductController.show);
131
+ app.post("/products", ProductController.store);
132
+ app.put("/products/:id", ProductController.update);
133
+ app.delete("/products/:id", ProductController.destroy);
134
+ ```
135
+
136
+ ### Writing a Simple Controller
137
+
138
+ ```typescript
139
+ // src/controllers/PagesController.ts
140
+ import { Request, Response } from "express";
141
+
142
+ export function home(req: Request, res: Response) {
143
+ res.render("home", { title: "Home" });
144
+ }
145
+
146
+ export function about(req: Request, res: Response) {
147
+ res.render("about", { title: "About Us" });
148
+ }
149
+ ```
150
+
151
+ ---
152
+
153
+ ## Database (Optional)
154
+
155
+ This app works without a database. Add one when you need it.
156
+
157
+ ### Step 1: Setup Prisma
158
+
159
+ ```bash
160
+ npm run db:setup
161
+ ```
162
+
163
+ ### Step 2: Configure Database URL
164
+
165
+ Copy `.env.example` to `.env` and set your database URL:
166
+
167
+ ```
168
+ DATABASE_URL="postgresql://user:password@localhost:5432/mydb"
169
+ ```
170
+
171
+ ### Step 3: Run Migrations
172
+
173
+ ```bash
174
+ npx prisma migrate dev --name init
175
+ ```
176
+
177
+ ### Step 4: Generate Models
178
+
179
+ ```bash
180
+ npx erwinmvc generate model Post
181
+ ```
182
+
183
+ This adds a model to `prisma/schema.prisma`. Edit it to add your fields:
184
+
185
+ ```prisma
186
+ model Post {
187
+ id Int @id @default(autoincrement())
188
+ title String
189
+ content String?
190
+ published Boolean @default(false)
191
+ createdAt DateTime @default(now())
192
+ updatedAt DateTime @updatedAt
193
+
194
+ @@map("posts")
195
+ }
196
+ ```
197
+
198
+ Then run migrations again:
199
+
200
+ ```bash
201
+ npx prisma migrate dev --name add-post-fields
202
+ ```
203
+
204
+ ### Step 5: Use in Your Code
205
+
206
+ ```typescript
207
+ import { getPrismaClient } from "@erwininteractive/mvc";
208
+
209
+ const prisma = getPrismaClient();
210
+
211
+ // List all posts
212
+ app.get("/posts", async (req, res) => {
213
+ const posts = await prisma.post.findMany();
214
+ res.render("posts/index", { posts });
215
+ });
216
+
217
+ // Get single post
218
+ app.get("/posts/:id", async (req, res) => {
219
+ const post = await prisma.post.findUnique({
220
+ where: { id: Number(req.params.id) }
221
+ });
222
+ res.render("posts/show", { post });
223
+ });
224
+
225
+ // Create post
226
+ app.post("/posts", async (req, res) => {
227
+ const post = await prisma.post.create({
228
+ data: { title: req.body.title, content: req.body.content }
229
+ });
230
+ res.redirect(`/posts/${post.id}`);
231
+ });
232
+ ```
233
+
234
+ ---
235
+
236
+ ## Project Structure
237
+
238
+ ```
239
+ src/
240
+ server.ts # Main app - add routes here
241
+ views/
242
+ index.ejs # Home page
243
+ *.ejs # Your page templates
244
+ controllers/ # Route handlers (optional)
245
+ middleware/ # Express middleware (optional)
246
+ public/ # Static files (served directly)
247
+ css/ # Stylesheets
248
+ js/ # Client-side JavaScript
249
+ images/ # Images
250
+ prisma/ # Database (after db:setup)
251
+ schema.prisma # Database models
252
+ ```
253
+
254
+ ### Static Files
255
+
256
+ Files in `public/` are served at the root URL:
257
+
258
+ - `public/css/style.css` → http://localhost:3000/css/style.css
259
+ - `public/images/logo.png` → http://localhost:3000/images/logo.png
260
+ - `public/js/app.js` → http://localhost:3000/js/app.js
261
+
262
+ Link to them in your templates:
263
+
264
+ ```html
265
+ <link rel="stylesheet" href="/css/style.css">
266
+ <script src="/js/app.js"></script>
267
+ <img src="/images/logo.png" alt="Logo">
268
+ ```
269
+
270
+ ---
271
+
272
+ ## Commands
273
+
274
+ | Command | Description |
275
+ |---------|-------------|
276
+ | `npm run dev` | Start development server (auto-reload) |
277
+ | `npm run build` | Build for production |
278
+ | `npm start` | Run production build |
279
+ | `npm run db:setup` | Install database dependencies |
280
+ | `npm run db:migrate` | Run database migrations |
281
+ | `npm run db:push` | Push schema changes (no migration) |
282
+
283
+ ### CLI Commands
284
+
285
+ | Command | Description |
286
+ |---------|-------------|
287
+ | `npx erwinmvc generate controller <Name>` | Create a CRUD controller |
288
+ | `npx erwinmvc generate model <Name>` | Create a database model |
289
+
290
+ ---
291
+
292
+ ## Learn More
293
+
294
+ - [Express.js Documentation](https://expressjs.com/)
295
+ - [EJS Documentation](https://ejs.co/)
296
+ - [Prisma Documentation](https://www.prisma.io/docs/)
297
+ - [Alpine.js Documentation](https://alpinejs.dev/)
@@ -0,0 +1,23 @@
1
+ {
2
+ "name": "my-mvc-app",
3
+ "version": "1.0.0",
4
+ "description": "MVC application built with @erwininteractive/mvc",
5
+ "main": "dist/server.js",
6
+ "scripts": {
7
+ "dev": "tsx watch src/server.ts",
8
+ "build": "tsc",
9
+ "start": "node dist/server.js",
10
+ "db:setup": "npm install @prisma/client prisma && npx prisma generate",
11
+ "db:migrate": "prisma migrate dev",
12
+ "db:push": "prisma db push"
13
+ },
14
+ "dependencies": {
15
+ "@erwininteractive/mvc": "^1.0.0"
16
+ },
17
+ "devDependencies": {
18
+ "@types/express": "^5.0.0",
19
+ "@types/node": "^22.7.5",
20
+ "tsx": "^4.19.1",
21
+ "typescript": "^5.6.3"
22
+ }
23
+ }
@@ -0,0 +1,16 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
2
+ <defs>
3
+ <linearGradient id="grad" x1="0%" y1="0%" x2="100%" y2="100%">
4
+ <stop offset="0%" style="stop-color:#667eea"/>
5
+ <stop offset="100%" style="stop-color:#764ba2"/>
6
+ </linearGradient>
7
+ </defs>
8
+ <!-- Background rounded square -->
9
+ <rect x="2" y="2" width="28" height="28" rx="6" fill="url(#grad)"/>
10
+ <!-- Letter E stylized as MVC structure (3 horizontal bars) -->
11
+ <rect x="8" y="7" width="16" height="3" rx="1" fill="white"/>
12
+ <rect x="8" y="14.5" width="12" height="3" rx="1" fill="white"/>
13
+ <rect x="8" y="22" width="16" height="3" rx="1" fill="white"/>
14
+ <!-- Vertical bar of E -->
15
+ <rect x="8" y="7" width="3" height="18" rx="1" fill="white"/>
16
+ </svg>
@@ -0,0 +1,9 @@
1
+ import type { Request, Response } from "express";
2
+
3
+ /**
4
+ * GET /homes (or configure as root route)
5
+ * Display the home page.
6
+ */
7
+ export async function index(req: Request, res: Response): Promise<void> {
8
+ res.render("index", { title: "Welcome" });
9
+ }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Auth middleware wrapper for easy import in routes.
3
+ * Re-exports the authenticate middleware from the framework.
4
+ *
5
+ * Note: Authentication requires database setup.
6
+ * See README.md for instructions on enabling auth.
7
+ */
8
+ export { authenticate } from "@erwininteractive/mvc";
@@ -0,0 +1,24 @@
1
+ import { createMvcApp, startServer } from "@erwininteractive/mvc";
2
+
3
+ async function main() {
4
+ const { app } = await createMvcApp({
5
+ viewsPath: "src/views",
6
+ publicPath: "public",
7
+ });
8
+
9
+ // Root route - displays welcome page
10
+ app.get("/", (req, res) => {
11
+ res.render("index", { title: "Welcome" });
12
+ });
13
+
14
+ // Add your routes here
15
+ // Example: app.get("/users", UsersController.index);
16
+
17
+ // Start server
18
+ startServer(app);
19
+ }
20
+
21
+ main().catch((err) => {
22
+ console.error("Failed to start server:", err);
23
+ process.exit(1);
24
+ });
@@ -0,0 +1,300 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ <title><%= title %> - MVC App</title>
7
+ <link rel="icon" type="image/svg+xml" href="/favicon.svg">
8
+ <link rel="icon" type="image/x-icon" href="/favicon.ico">
9
+ <script src="https://unpkg.com/alpinejs" defer></script>
10
+ <style>
11
+ * { box-sizing: border-box; }
12
+ body {
13
+ font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
14
+ line-height: 1.6;
15
+ max-width: 900px;
16
+ margin: 0 auto;
17
+ padding: 2rem;
18
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
19
+ min-height: 100vh;
20
+ }
21
+ .card {
22
+ background: white;
23
+ border-radius: 12px;
24
+ padding: 2rem;
25
+ box-shadow: 0 10px 40px rgba(0,0,0,0.2);
26
+ }
27
+ h1 { color: #333; margin-top: 0; }
28
+ h2 { color: #333; margin-top: 1.5rem; margin-bottom: 0.75rem; font-size: 1.25rem; }
29
+ h3 { color: #444; margin-top: 1.25rem; margin-bottom: 0.5rem; font-size: 1.1rem; }
30
+ p { color: #666; margin: 0.5rem 0; }
31
+ ul, ol { color: #666; margin: 0.5rem 0; padding-left: 1.5rem; }
32
+ li { margin: 0.25rem 0; }
33
+ code {
34
+ background: #f4f4f4;
35
+ padding: 0.2em 0.5em;
36
+ border-radius: 4px;
37
+ font-size: 0.9em;
38
+ }
39
+ pre {
40
+ background: #1e1e1e;
41
+ color: #d4d4d4;
42
+ padding: 1rem;
43
+ border-radius: 8px;
44
+ overflow-x: auto;
45
+ margin: 0.75rem 0;
46
+ }
47
+ pre code { background: none; padding: 0; }
48
+ .success-banner {
49
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
50
+ color: white;
51
+ padding: 1rem 1.5rem;
52
+ border-radius: 8px;
53
+ margin-bottom: 1.5rem;
54
+ }
55
+ .success-banner h1 { color: white; margin: 0; }
56
+ .success-banner p { color: rgba(255,255,255,0.9); margin: 0.25rem 0 0; }
57
+ .tabs { display: flex; flex-wrap: wrap; gap: 0.5rem; margin-bottom: 1rem; border-bottom: 2px solid #e9ecef; padding-bottom: 0.5rem; }
58
+ .tab {
59
+ padding: 0.5rem 1rem;
60
+ border: none;
61
+ background: transparent;
62
+ border-radius: 6px;
63
+ cursor: pointer;
64
+ font-size: 0.95rem;
65
+ color: #666;
66
+ }
67
+ .tab:hover { background: #f4f4f4; }
68
+ .tab.active { background: #667eea; color: white; }
69
+ a { color: #667eea; }
70
+ .file-tree {
71
+ background: #f8f9fa;
72
+ padding: 1rem;
73
+ border-radius: 8px;
74
+ font-family: monospace;
75
+ font-size: 0.9rem;
76
+ }
77
+ .step-number {
78
+ display: inline-block;
79
+ width: 24px;
80
+ height: 24px;
81
+ background: #667eea;
82
+ color: white;
83
+ border-radius: 50%;
84
+ text-align: center;
85
+ line-height: 24px;
86
+ font-size: 0.85rem;
87
+ margin-right: 0.5rem;
88
+ }
89
+ .step { margin: 1rem 0; }
90
+ </style>
91
+ </head>
92
+ <body>
93
+ <div class="card" x-data="{ tab: 'next' }">
94
+ <div class="success-banner">
95
+ <h1>Your app is running!</h1>
96
+ <p>Server started at http://localhost:<%= process.env.PORT || 3000 %></p>
97
+ </div>
98
+
99
+ <div class="tabs">
100
+ <button class="tab" :class="{ active: tab === 'next' }" @click="tab = 'next'">Next Steps</button>
101
+ <button class="tab" :class="{ active: tab === 'pages' }" @click="tab = 'pages'">Creating Pages</button>
102
+ <button class="tab" :class="{ active: tab === 'controllers' }" @click="tab = 'controllers'">Controllers</button>
103
+ <button class="tab" :class="{ active: tab === 'database' }" @click="tab = 'database'">Database</button>
104
+ <button class="tab" :class="{ active: tab === 'structure' }" @click="tab = 'structure'">Project Structure</button>
105
+ </div>
106
+
107
+ <!-- NEXT STEPS TAB -->
108
+ <div x-show="tab === 'next'" x-transition>
109
+ <h2>Getting Started</h2>
110
+
111
+ <div class="step">
112
+ <p><span class="step-number">1</span><strong>Create a new page</strong></p>
113
+ <p>Create <code>src/views/about.ejs</code> with your HTML content.</p>
114
+ </div>
115
+
116
+ <div class="step">
117
+ <p><span class="step-number">2</span><strong>Add a route for it</strong></p>
118
+ <p>Edit <code>src/server.ts</code> and add:</p>
119
+ <pre><code>app.get("/about", (req, res) => {
120
+ res.render("about", { title: "About Us" });
121
+ });</code></pre>
122
+ </div>
123
+
124
+ <div class="step">
125
+ <p><span class="step-number">3</span><strong>View your page</strong></p>
126
+ <p>Visit <a href="/about">http://localhost:3000/about</a></p>
127
+ </div>
128
+
129
+ <h3>Key Files to Edit</h3>
130
+ <ul>
131
+ <li><code>src/server.ts</code> - Add routes and app configuration</li>
132
+ <li><code>src/views/*.ejs</code> - Create page templates</li>
133
+ <li><code>public/</code> - Add CSS, images, JavaScript files</li>
134
+ </ul>
135
+ </div>
136
+
137
+ <!-- CREATING PAGES TAB -->
138
+ <div x-show="tab === 'pages'" x-transition>
139
+ <h2>Creating Pages</h2>
140
+
141
+ <h3>1. Create a View Template</h3>
142
+ <p>Create a new file in <code>src/views/</code>. Example: <code>src/views/contact.ejs</code></p>
143
+ <pre><code>&lt;!doctype html&gt;
144
+ &lt;html&gt;
145
+ &lt;head&gt;
146
+ &lt;title&gt;&lt;%= title %&gt;&lt;/title&gt;
147
+ &lt;/head&gt;
148
+ &lt;body&gt;
149
+ &lt;h1&gt;&lt;%= title %&gt;&lt;/h1&gt;
150
+ &lt;p&gt;Get in touch with us!&lt;/p&gt;
151
+
152
+ &lt;form method="POST" action="/contact"&gt;
153
+ &lt;input type="text" name="name" placeholder="Name"&gt;
154
+ &lt;input type="email" name="email" placeholder="Email"&gt;
155
+ &lt;button type="submit"&gt;Send&lt;/button&gt;
156
+ &lt;/form&gt;
157
+ &lt;/body&gt;
158
+ &lt;/html&gt;</code></pre>
159
+
160
+ <h3>2. Add a Route</h3>
161
+ <p>In <code>src/server.ts</code>, add routes for your page:</p>
162
+ <pre><code>// Show the contact page
163
+ app.get("/contact", (req, res) => {
164
+ res.render("contact", { title: "Contact Us" });
165
+ });
166
+
167
+ // Handle form submission
168
+ app.post("/contact", (req, res) => {
169
+ const { name, email } = req.body;
170
+ console.log(`Message from ${name} (${email})`);
171
+ res.redirect("/contact?sent=true");
172
+ });</code></pre>
173
+
174
+ <h3>EJS Template Features</h3>
175
+ <ul>
176
+ <li><code>&lt;%= variable %&gt;</code> - Output escaped value</li>
177
+ <li><code>&lt;%- html %&gt;</code> - Output raw HTML</li>
178
+ <li><code>&lt;% if (condition) { %&gt;</code> - JavaScript logic</li>
179
+ <li><code>&lt;%- include('partial') %&gt;</code> - Include another template</li>
180
+ </ul>
181
+ </div>
182
+
183
+ <!-- CONTROLLERS TAB -->
184
+ <div x-show="tab === 'controllers'" x-transition>
185
+ <h2>Creating Controllers</h2>
186
+ <p>Controllers help organize your routes. Generate one with the CLI:</p>
187
+ <pre><code>npx erwinmvc generate controller Product</code></pre>
188
+
189
+ <p>This creates <code>src/controllers/ProductController.ts</code> with CRUD actions:</p>
190
+ <ul>
191
+ <li><code>index</code> - GET /products (list all)</li>
192
+ <li><code>show</code> - GET /products/:id (show one)</li>
193
+ <li><code>store</code> - POST /products (create)</li>
194
+ <li><code>update</code> - PUT /products/:id (update)</li>
195
+ <li><code>destroy</code> - DELETE /products/:id (delete)</li>
196
+ </ul>
197
+
198
+ <h3>Using Controllers</h3>
199
+ <p>Import and use your controller in <code>src/server.ts</code>:</p>
200
+ <pre><code>import * as ProductController from "./controllers/ProductController";
201
+
202
+ app.get("/products", ProductController.index);
203
+ app.get("/products/:id", ProductController.show);
204
+ app.post("/products", ProductController.store);
205
+ app.put("/products/:id", ProductController.update);
206
+ app.delete("/products/:id", ProductController.destroy);</code></pre>
207
+
208
+ <h3>Simple Controller Example</h3>
209
+ <p>You can also write controllers manually:</p>
210
+ <pre><code>// src/controllers/PagesController.ts
211
+ import { Request, Response } from "express";
212
+
213
+ export function home(req: Request, res: Response) {
214
+ res.render("home", { title: "Home" });
215
+ }
216
+
217
+ export function about(req: Request, res: Response) {
218
+ res.render("about", { title: "About" });
219
+ }</code></pre>
220
+ </div>
221
+
222
+ <!-- DATABASE TAB -->
223
+ <div x-show="tab === 'database'" x-transition>
224
+ <h2>Adding a Database</h2>
225
+ <p>Your app works without a database, but you can add one when needed.</p>
226
+
227
+ <h3>1. Setup Prisma</h3>
228
+ <pre><code>npm run db:setup</code></pre>
229
+
230
+ <h3>2. Configure Database URL</h3>
231
+ <p>Copy <code>.env.example</code> to <code>.env</code> and set your database URL:</p>
232
+ <pre><code>DATABASE_URL="postgresql://user:password@localhost:5432/mydb"</code></pre>
233
+
234
+ <h3>3. Create Your First Migration</h3>
235
+ <pre><code>npx prisma migrate dev --name init</code></pre>
236
+
237
+ <h3>4. Generate a Model</h3>
238
+ <pre><code>npx erwinmvc generate model Post</code></pre>
239
+ <p>This adds a model to <code>prisma/schema.prisma</code>. Edit it to add fields:</p>
240
+ <pre><code>model Post {
241
+ id Int @id @default(autoincrement())
242
+ title String
243
+ content String?
244
+ published Boolean @default(false)
245
+ createdAt DateTime @default(now())
246
+ updatedAt DateTime @updatedAt
247
+
248
+ @@map("posts")
249
+ }</code></pre>
250
+
251
+ <h3>5. Use in Your Code</h3>
252
+ <pre><code>import { getPrismaClient } from "@erwininteractive/mvc";
253
+
254
+ const prisma = getPrismaClient();
255
+
256
+ // In a route handler:
257
+ app.get("/posts", async (req, res) => {
258
+ const posts = await prisma.post.findMany();
259
+ res.render("posts/index", { posts });
260
+ });</code></pre>
261
+ </div>
262
+
263
+ <!-- PROJECT STRUCTURE TAB -->
264
+ <div x-show="tab === 'structure'" x-transition>
265
+ <h2>Project Structure</h2>
266
+ <div class="file-tree">
267
+ src/<br>
268
+ &nbsp;&nbsp;server.ts&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;# Main app - routes go here<br>
269
+ &nbsp;&nbsp;views/<br>
270
+ &nbsp;&nbsp;&nbsp;&nbsp;index.ejs&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;# This page<br>
271
+ &nbsp;&nbsp;&nbsp;&nbsp;*.ejs&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;# Your page templates<br>
272
+ &nbsp;&nbsp;controllers/&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;# Route handlers (optional)<br>
273
+ &nbsp;&nbsp;middleware/&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;# Express middleware (optional)<br>
274
+ public/&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;# Static files<br>
275
+ &nbsp;&nbsp;css/&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;# Stylesheets<br>
276
+ &nbsp;&nbsp;js/&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;# Client-side JavaScript<br>
277
+ &nbsp;&nbsp;images/&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;# Images<br>
278
+ prisma/&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;# Database (after db:setup)<br>
279
+ &nbsp;&nbsp;schema.prisma&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;# Database models<br>
280
+ </div>
281
+
282
+ <h3>CLI Commands</h3>
283
+ <ul>
284
+ <li><code>npm run dev</code> - Start development server (auto-reload)</li>
285
+ <li><code>npm run build</code> - Build for production</li>
286
+ <li><code>npm start</code> - Run production build</li>
287
+ <li><code>npx erwinmvc generate controller Name</code> - Create a controller</li>
288
+ <li><code>npx erwinmvc generate model Name</code> - Create a database model</li>
289
+ </ul>
290
+
291
+ <h3>Static Files</h3>
292
+ <p>Files in <code>public/</code> are served directly:</p>
293
+ <ul>
294
+ <li><code>public/css/style.css</code> → <code>/css/style.css</code></li>
295
+ <li><code>public/images/logo.png</code> → <code>/images/logo.png</code></li>
296
+ </ul>
297
+ </div>
298
+ </div>
299
+ </body>
300
+ </html>