@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.
- package/README.md +174 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +61 -0
- package/dist/framework/App.d.ts +38 -0
- package/dist/framework/App.js +100 -0
- package/dist/framework/Auth.d.ts +27 -0
- package/dist/framework/Auth.js +67 -0
- package/dist/framework/Router.d.ts +29 -0
- package/dist/framework/Router.js +125 -0
- package/dist/framework/db.d.ts +13 -0
- package/dist/framework/db.js +41 -0
- package/dist/framework/index.d.ts +5 -0
- package/dist/framework/index.js +22 -0
- package/dist/generators/generateController.d.ts +7 -0
- package/dist/generators/generateController.js +110 -0
- package/dist/generators/generateModel.d.ts +7 -0
- package/dist/generators/generateModel.js +77 -0
- package/dist/generators/initApp.d.ts +8 -0
- package/dist/generators/initApp.js +113 -0
- package/dist/generators/paths.d.ts +17 -0
- package/dist/generators/paths.js +55 -0
- package/package.json +72 -0
- package/prisma/schema.prisma +19 -0
- package/templates/appScaffold/README.md +297 -0
- package/templates/appScaffold/package.json +23 -0
- package/templates/appScaffold/public/favicon.svg +16 -0
- package/templates/appScaffold/src/controllers/HomeController.ts +9 -0
- package/templates/appScaffold/src/middleware/auth.ts +8 -0
- package/templates/appScaffold/src/server.ts +24 -0
- package/templates/appScaffold/src/views/index.ejs +300 -0
- package/templates/appScaffold/tsconfig.json +16 -0
- package/templates/controller.ts.ejs +98 -0
- package/templates/model.prisma.ejs +7 -0
- 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,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><!doctype html>
|
|
144
|
+
<html>
|
|
145
|
+
<head>
|
|
146
|
+
<title><%= title %></title>
|
|
147
|
+
</head>
|
|
148
|
+
<body>
|
|
149
|
+
<h1><%= title %></h1>
|
|
150
|
+
<p>Get in touch with us!</p>
|
|
151
|
+
|
|
152
|
+
<form method="POST" action="/contact">
|
|
153
|
+
<input type="text" name="name" placeholder="Name">
|
|
154
|
+
<input type="email" name="email" placeholder="Email">
|
|
155
|
+
<button type="submit">Send</button>
|
|
156
|
+
</form>
|
|
157
|
+
</body>
|
|
158
|
+
</html></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><%= variable %></code> - Output escaped value</li>
|
|
177
|
+
<li><code><%- html %></code> - Output raw HTML</li>
|
|
178
|
+
<li><code><% if (condition) { %></code> - JavaScript logic</li>
|
|
179
|
+
<li><code><%- include('partial') %></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
|
+
server.ts # Main app - routes go here<br>
|
|
269
|
+
views/<br>
|
|
270
|
+
index.ejs # This page<br>
|
|
271
|
+
*.ejs # Your page templates<br>
|
|
272
|
+
controllers/ # Route handlers (optional)<br>
|
|
273
|
+
middleware/ # Express middleware (optional)<br>
|
|
274
|
+
public/ # Static files<br>
|
|
275
|
+
css/ # Stylesheets<br>
|
|
276
|
+
js/ # Client-side JavaScript<br>
|
|
277
|
+
images/ # Images<br>
|
|
278
|
+
prisma/ # Database (after db:setup)<br>
|
|
279
|
+
schema.prisma # 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>
|