@erwininteractive/mvc 0.1.2 → 0.1.4
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 +230 -78
- package/dist/cli.js +1 -1
- package/dist/generators/initApp.js +11 -10
- package/package.json +1 -1
- package/templates/appScaffold/env.example.txt +6 -0
package/README.md
CHANGED
|
@@ -5,91 +5,208 @@ A lightweight, full-featured MVC framework for Node.js 20+ built with TypeScript
|
|
|
5
5
|
## Features
|
|
6
6
|
|
|
7
7
|
- **Express** - Fast, minimal web framework
|
|
8
|
-
- **Prisma** - Modern database ORM with PostgreSQL
|
|
9
8
|
- **EJS + Alpine.js** - Server-side templating with reactive client-side components
|
|
10
|
-
- **
|
|
9
|
+
- **Optional Database** - Add Prisma + PostgreSQL when you need it
|
|
10
|
+
- **Optional Redis Sessions** - Scalable session management
|
|
11
11
|
- **JWT Authentication** - Secure token-based auth with bcrypt password hashing
|
|
12
12
|
- **CLI Tools** - Scaffold apps and generate models/controllers
|
|
13
13
|
|
|
14
14
|
## Quick Start
|
|
15
15
|
|
|
16
|
-
### Create a New Application
|
|
17
|
-
|
|
18
16
|
```bash
|
|
19
17
|
npx @erwininteractive/mvc init myapp
|
|
20
18
|
cd myapp
|
|
21
|
-
cp .env.example .env
|
|
22
|
-
# Edit .env with your database configuration
|
|
23
|
-
npx prisma migrate dev --name init
|
|
24
19
|
npm run dev
|
|
25
20
|
```
|
|
26
21
|
|
|
27
|
-
|
|
22
|
+
Visit http://localhost:3000 - your app is running!
|
|
28
23
|
|
|
29
|
-
|
|
30
|
-
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## Getting Started
|
|
27
|
+
|
|
28
|
+
### Step 1: Create a New Page
|
|
29
|
+
|
|
30
|
+
Create `src/views/about.ejs`:
|
|
31
|
+
|
|
32
|
+
```html
|
|
33
|
+
<!doctype html>
|
|
34
|
+
<html>
|
|
35
|
+
<head>
|
|
36
|
+
<title><%= title %></title>
|
|
37
|
+
</head>
|
|
38
|
+
<body>
|
|
39
|
+
<h1><%= title %></h1>
|
|
40
|
+
<p>Welcome to my about page!</p>
|
|
41
|
+
</body>
|
|
42
|
+
</html>
|
|
31
43
|
```
|
|
32
44
|
|
|
33
|
-
|
|
45
|
+
### Step 2: Add a Route
|
|
34
46
|
|
|
35
|
-
|
|
47
|
+
Edit `src/server.ts`:
|
|
36
48
|
|
|
37
|
-
```
|
|
38
|
-
|
|
49
|
+
```typescript
|
|
50
|
+
app.get("/about", (req, res) => {
|
|
51
|
+
res.render("about", { title: "About Us" });
|
|
52
|
+
});
|
|
39
53
|
```
|
|
40
54
|
|
|
41
|
-
|
|
42
|
-
- `GET /posts` - List all posts
|
|
43
|
-
- `GET /posts/:id` - Show a single post
|
|
44
|
-
- `POST /posts` - Create a post
|
|
45
|
-
- `PUT /posts/:id` - Update a post
|
|
46
|
-
- `DELETE /posts/:id` - Delete a post
|
|
55
|
+
### Step 3: View Your Page
|
|
47
56
|
|
|
48
|
-
|
|
57
|
+
Visit http://localhost:3000/about
|
|
49
58
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
| `erwinmvc generate model <name>` | Generate a Prisma model |
|
|
54
|
-
| `erwinmvc generate controller <name>` | Generate a CRUD controller |
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
## Creating Pages
|
|
55
62
|
|
|
56
|
-
###
|
|
63
|
+
### EJS Templates
|
|
57
64
|
|
|
58
|
-
|
|
59
|
-
- `--skip-install` - Skip running npm install
|
|
60
|
-
- `--skip-prisma` - Skip Prisma client generation
|
|
65
|
+
Create `.ejs` files in `src/views/`. EJS lets you use JavaScript in HTML:
|
|
61
66
|
|
|
62
|
-
|
|
63
|
-
|
|
67
|
+
```html
|
|
68
|
+
<!-- Output a variable (escaped) -->
|
|
69
|
+
<h1><%= title %></h1>
|
|
64
70
|
|
|
65
|
-
|
|
66
|
-
|
|
71
|
+
<!-- Output raw HTML -->
|
|
72
|
+
<%- htmlContent %>
|
|
67
73
|
|
|
68
|
-
|
|
74
|
+
<!-- JavaScript logic -->
|
|
75
|
+
<% if (user) { %>
|
|
76
|
+
<p>Welcome, <%= user.name %>!</p>
|
|
77
|
+
<% } %>
|
|
78
|
+
|
|
79
|
+
<!-- Loop through items -->
|
|
80
|
+
<ul>
|
|
81
|
+
<% items.forEach(item => { %>
|
|
82
|
+
<li><%= item.name %></li>
|
|
83
|
+
<% }); %>
|
|
84
|
+
</ul>
|
|
85
|
+
|
|
86
|
+
<!-- Include another template -->
|
|
87
|
+
<%- include('partials/header') %>
|
|
88
|
+
```
|
|
69
89
|
|
|
70
|
-
###
|
|
90
|
+
### Adding Routes
|
|
71
91
|
|
|
72
92
|
```typescript
|
|
73
|
-
|
|
93
|
+
// Simple page
|
|
94
|
+
app.get("/contact", (req, res) => {
|
|
95
|
+
res.render("contact", { title: "Contact Us" });
|
|
96
|
+
});
|
|
74
97
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
98
|
+
// Handle form submission
|
|
99
|
+
app.post("/contact", (req, res) => {
|
|
100
|
+
const { name, email, message } = req.body;
|
|
101
|
+
console.log(`Message from ${name}: ${message}`);
|
|
102
|
+
res.redirect("/contact?sent=true");
|
|
78
103
|
});
|
|
79
104
|
|
|
80
|
-
|
|
105
|
+
// JSON API endpoint
|
|
106
|
+
app.get("/api/users", (req, res) => {
|
|
107
|
+
res.json([{ id: 1, name: "John" }]);
|
|
108
|
+
});
|
|
81
109
|
```
|
|
82
110
|
|
|
83
|
-
|
|
111
|
+
---
|
|
112
|
+
|
|
113
|
+
## Controllers
|
|
114
|
+
|
|
115
|
+
Generate a controller with the CLI:
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
npx erwinmvc generate controller Product
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
This creates `src/controllers/ProductController.ts` with CRUD actions:
|
|
122
|
+
|
|
123
|
+
| Action | HTTP Method | URL | Description |
|
|
124
|
+
|-----------|-------------|------------------|-------------|
|
|
125
|
+
| `index` | GET | /products | List all |
|
|
126
|
+
| `show` | GET | /products/:id | Show one |
|
|
127
|
+
| `store` | POST | /products | Create |
|
|
128
|
+
| `update` | PUT | /products/:id | Update |
|
|
129
|
+
| `destroy` | DELETE | /products/:id | Delete |
|
|
130
|
+
|
|
131
|
+
### Using Controllers
|
|
132
|
+
|
|
133
|
+
```typescript
|
|
134
|
+
import * as ProductController from "./controllers/ProductController";
|
|
135
|
+
|
|
136
|
+
app.get("/products", ProductController.index);
|
|
137
|
+
app.get("/products/:id", ProductController.show);
|
|
138
|
+
app.post("/products", ProductController.store);
|
|
139
|
+
app.put("/products/:id", ProductController.update);
|
|
140
|
+
app.delete("/products/:id", ProductController.destroy);
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
---
|
|
144
|
+
|
|
145
|
+
## Database (Optional)
|
|
146
|
+
|
|
147
|
+
Your app works without a database. Add one when you need it.
|
|
148
|
+
|
|
149
|
+
### Setup
|
|
150
|
+
|
|
151
|
+
```bash
|
|
152
|
+
npm run db:setup
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
Edit `.env` with your database URL:
|
|
156
|
+
|
|
157
|
+
```
|
|
158
|
+
DATABASE_URL="postgresql://user:password@localhost:5432/mydb"
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
Run migrations:
|
|
162
|
+
|
|
163
|
+
```bash
|
|
164
|
+
npx prisma migrate dev --name init
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
### Generate Models
|
|
168
|
+
|
|
169
|
+
```bash
|
|
170
|
+
npx erwinmvc generate model Post
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
Edit `prisma/schema.prisma` to add fields:
|
|
174
|
+
|
|
175
|
+
```prisma
|
|
176
|
+
model Post {
|
|
177
|
+
id Int @id @default(autoincrement())
|
|
178
|
+
title String
|
|
179
|
+
content String?
|
|
180
|
+
published Boolean @default(false)
|
|
181
|
+
createdAt DateTime @default(now())
|
|
182
|
+
updatedAt DateTime @updatedAt
|
|
183
|
+
|
|
184
|
+
@@map("posts")
|
|
185
|
+
}
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
Run migrations again:
|
|
189
|
+
|
|
190
|
+
```bash
|
|
191
|
+
npx prisma migrate dev --name add-post-fields
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
### Use in Code
|
|
84
195
|
|
|
85
196
|
```typescript
|
|
86
197
|
import { getPrismaClient } from "@erwininteractive/mvc";
|
|
87
198
|
|
|
88
199
|
const prisma = getPrismaClient();
|
|
89
|
-
|
|
200
|
+
|
|
201
|
+
app.get("/posts", async (req, res) => {
|
|
202
|
+
const posts = await prisma.post.findMany();
|
|
203
|
+
res.render("posts/index", { posts });
|
|
204
|
+
});
|
|
90
205
|
```
|
|
91
206
|
|
|
92
|
-
|
|
207
|
+
---
|
|
208
|
+
|
|
209
|
+
## Authentication
|
|
93
210
|
|
|
94
211
|
```typescript
|
|
95
212
|
import {
|
|
@@ -115,59 +232,94 @@ app.get("/protected", authenticate, (req, res) => {
|
|
|
115
232
|
});
|
|
116
233
|
```
|
|
117
234
|
|
|
118
|
-
|
|
235
|
+
---
|
|
119
236
|
|
|
120
|
-
|
|
121
|
-
import { registerControllers } from "@erwininteractive/mvc";
|
|
237
|
+
## CLI Commands
|
|
122
238
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
239
|
+
| Command | Description |
|
|
240
|
+
|---------|-------------|
|
|
241
|
+
| `npx @erwininteractive/mvc init <dir>` | Create a new app |
|
|
242
|
+
| `npx erwinmvc generate controller <name>` | Generate a CRUD controller |
|
|
243
|
+
| `npx erwinmvc generate model <name>` | Generate a database model |
|
|
126
244
|
|
|
127
|
-
|
|
245
|
+
### Init Options
|
|
128
246
|
|
|
129
|
-
|
|
247
|
+
| Option | Description |
|
|
248
|
+
|--------|-------------|
|
|
249
|
+
| `--skip-install` | Skip running npm install |
|
|
250
|
+
| `--with-database` | Include Prisma database setup |
|
|
130
251
|
|
|
131
|
-
|
|
132
|
-
DATABASE_URL="postgresql://USER:PASSWORD@localhost:5432/yourdb?schema=public"
|
|
133
|
-
REDIS_URL="redis://localhost:6379"
|
|
134
|
-
JWT_SECRET="your-secret-key"
|
|
135
|
-
SESSION_SECRET="your-session-secret"
|
|
136
|
-
PORT=3000
|
|
137
|
-
NODE_ENV=development
|
|
138
|
-
```
|
|
252
|
+
### Generate Options
|
|
139
253
|
|
|
140
|
-
|
|
254
|
+
| Option | Description |
|
|
255
|
+
|--------|-------------|
|
|
256
|
+
| `--skip-migrate` | Skip running Prisma migrate (model) |
|
|
257
|
+
| `--no-views` | Skip generating EJS views (controller) |
|
|
258
|
+
|
|
259
|
+
---
|
|
141
260
|
|
|
142
|
-
|
|
261
|
+
## Project Structure
|
|
143
262
|
|
|
144
263
|
```
|
|
145
264
|
myapp/
|
|
146
265
|
├── src/
|
|
147
|
-
│ ├──
|
|
148
|
-
│ ├──
|
|
149
|
-
│ ├──
|
|
150
|
-
│ └──
|
|
151
|
-
├──
|
|
152
|
-
|
|
153
|
-
|
|
266
|
+
│ ├── server.ts # Main app - add routes here
|
|
267
|
+
│ ├── views/ # EJS templates
|
|
268
|
+
│ ├── controllers/ # Route handlers (optional)
|
|
269
|
+
│ └── middleware/ # Express middleware (optional)
|
|
270
|
+
├── public/ # Static files (CSS, JS, images)
|
|
271
|
+
├── prisma/ # Database (after db:setup)
|
|
272
|
+
│ └── schema.prisma
|
|
154
273
|
├── .env.example
|
|
274
|
+
├── .gitignore
|
|
155
275
|
├── package.json
|
|
156
276
|
└── tsconfig.json
|
|
157
277
|
```
|
|
158
278
|
|
|
159
|
-
|
|
279
|
+
### Static Files
|
|
160
280
|
|
|
161
|
-
|
|
281
|
+
Files in `public/` are served at the root URL:
|
|
162
282
|
|
|
163
|
-
```typescript
|
|
164
|
-
// src/controllers/PostController.ts
|
|
165
|
-
export async function index(req, res) { /* GET /posts */ }
|
|
166
|
-
export async function show(req, res) { /* GET /posts/:id */ }
|
|
167
|
-
export async function store(req, res) { /* POST /posts */ }
|
|
168
|
-
export async function update(req, res) { /* PUT /posts/:id */ }
|
|
169
|
-
export async function destroy(req, res) { /* DELETE /posts/:id */ }
|
|
170
283
|
```
|
|
284
|
+
public/css/style.css → /css/style.css
|
|
285
|
+
public/images/logo.png → /images/logo.png
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
---
|
|
289
|
+
|
|
290
|
+
## App Commands
|
|
291
|
+
|
|
292
|
+
| Command | Description |
|
|
293
|
+
|---------|-------------|
|
|
294
|
+
| `npm run dev` | Start development server (auto-reload) |
|
|
295
|
+
| `npm run build` | Build for production |
|
|
296
|
+
| `npm start` | Run production build |
|
|
297
|
+
| `npm run db:setup` | Install database dependencies |
|
|
298
|
+
| `npm run db:migrate` | Run database migrations |
|
|
299
|
+
|
|
300
|
+
---
|
|
301
|
+
|
|
302
|
+
## Environment Variables
|
|
303
|
+
|
|
304
|
+
All optional. Create `.env` from `.env.example`:
|
|
305
|
+
|
|
306
|
+
```env
|
|
307
|
+
DATABASE_URL="postgresql://user:pass@localhost:5432/mydb" # For database
|
|
308
|
+
REDIS_URL="redis://localhost:6379" # For sessions
|
|
309
|
+
JWT_SECRET="your-secret-key" # For auth
|
|
310
|
+
SESSION_SECRET="your-session-secret" # For sessions
|
|
311
|
+
PORT=3000 # Server port
|
|
312
|
+
NODE_ENV=development # Environment
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
---
|
|
316
|
+
|
|
317
|
+
## Learn More
|
|
318
|
+
|
|
319
|
+
- [Express.js Documentation](https://expressjs.com/)
|
|
320
|
+
- [EJS Documentation](https://ejs.co/)
|
|
321
|
+
- [Prisma Documentation](https://www.prisma.io/docs/)
|
|
322
|
+
- [Alpine.js Documentation](https://alpinejs.dev/)
|
|
171
323
|
|
|
172
324
|
## License
|
|
173
325
|
|
package/dist/cli.js
CHANGED
|
@@ -32,16 +32,17 @@ async function initApp(dir, options = {}) {
|
|
|
32
32
|
}
|
|
33
33
|
// Copy app scaffold templates
|
|
34
34
|
copyDirRecursive(templateDir, targetDir);
|
|
35
|
-
// Rename dotfiles (npm doesn't publish
|
|
36
|
-
const
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
35
|
+
// Rename dotfiles (npm doesn't publish dotfiles, so we use .txt extensions)
|
|
36
|
+
const renames = [
|
|
37
|
+
["gitignore.txt", ".gitignore"],
|
|
38
|
+
["env.example.txt", ".env.example"],
|
|
39
|
+
];
|
|
40
|
+
for (const [src, dest] of renames) {
|
|
41
|
+
const srcPath = path_1.default.join(targetDir, src);
|
|
42
|
+
const destPath = path_1.default.join(targetDir, dest);
|
|
43
|
+
if (fs_1.default.existsSync(srcPath)) {
|
|
44
|
+
fs_1.default.renameSync(srcPath, destPath);
|
|
45
|
+
}
|
|
45
46
|
}
|
|
46
47
|
// Update package.json with app name and framework version
|
|
47
48
|
const appPackageJson = path_1.default.join(targetDir, "package.json");
|
package/package.json
CHANGED