frontend-hamroun 1.2.63 → 1.2.65
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 +1062 -111
- package/package.json +1 -1
package/README.md
CHANGED
@@ -1,11 +1,7 @@
|
|
1
1
|
# Frontend Hamroun
|
2
2
|
|
3
3
|
<p align="center">
|
4
|
-
<img src="https://drive.google.com/uc?export=view&id=
|
5
|
-
</p>
|
6
|
-
|
7
|
-
<p align="center">
|
8
|
-
<code style="font-size: 24px; color: #4A90E2; background-color: transparent">{ Frontend Hamroun }</code>
|
4
|
+
<img src="https://drive.google.com/uc?export=view&id=15VsMSNDhWAfV_R6ZWJltOJd-RMs9UH_y" alt="Frontend Hamroun Logo" width="300">
|
9
5
|
</p>
|
10
6
|
|
11
7
|
A lightweight full-stack JavaScript framework with Virtual DOM and hooks implementation
|
@@ -15,21 +11,61 @@ A lightweight full-stack JavaScript framework with Virtual DOM and hooks impleme
|
|
15
11
|
[](https://www.npmjs.com/package/frontend-hamroun)
|
16
12
|
[](LICENSE)
|
17
13
|
|
18
|
-
##
|
14
|
+
## 📋 Table of Contents
|
15
|
+
|
16
|
+
- [Introduction](#-introduction)
|
17
|
+
- [Installation](#-installation)
|
18
|
+
- [Quick Start](#-quick-start)
|
19
|
+
- [Core Concepts](#-core-concepts)
|
20
|
+
- [Frontend Features](#-frontend-features)
|
21
|
+
- [Backend Features](#-backend-features)
|
22
|
+
- [API Reference](#-api-reference)
|
23
|
+
- [CLI Tools](#-cli-tools)
|
24
|
+
- [Advanced Usage](#-advanced-usage)
|
25
|
+
- [TypeScript Support](#-typescript-support)
|
26
|
+
- [Browser Compatibility](#-browser-compatibility)
|
27
|
+
- [FAQ](#-frequently-asked-questions)
|
28
|
+
- [License](#-license)
|
29
|
+
|
30
|
+
## 🚀 Introduction
|
31
|
+
|
32
|
+
Frontend Hamroun is a lightweight (~5KB gzipped) JavaScript framework designed for building modern web applications. It combines React-like frontend development with powerful backend capabilities in a single, unified package.
|
33
|
+
|
34
|
+
### Key Features
|
35
|
+
|
36
|
+
- **Efficient Virtual DOM**: Minimizes DOM operations with intelligent diffing
|
37
|
+
- **Complete Hooks API**: useState, useEffect, useRef, useMemo, and more
|
38
|
+
- **Full-Stack Solution**: Unified frontend and backend development
|
39
|
+
- **Server-Side Rendering**: Optimized SSR with hydration
|
40
|
+
- **File-Based Routing**: Intuitive API endpoint creation
|
41
|
+
- **Database Integration**: Built-in support for MongoDB, MySQL, PostgreSQL
|
42
|
+
- **TypeScript Support**: Full type definitions and excellent DX
|
43
|
+
- **Automatic Batch Updates**: Efficient state management
|
44
|
+
- **CLI Tooling**: Scaffolding for components, pages, and API routes
|
45
|
+
|
46
|
+
## 📦 Installation
|
19
47
|
|
20
48
|
```bash
|
49
|
+
# Using npm
|
21
50
|
npm install frontend-hamroun
|
51
|
+
|
52
|
+
# Using yarn
|
53
|
+
yarn add frontend-hamroun
|
54
|
+
|
55
|
+
# Using pnpm
|
56
|
+
pnpm add frontend-hamroun
|
22
57
|
```
|
23
58
|
|
24
|
-
## Quick Start
|
59
|
+
## 🏁 Quick Start
|
25
60
|
|
26
|
-
|
61
|
+
<details>
|
62
|
+
<summary><b>Method 1: Create a new project with CLI</b></summary>
|
27
63
|
|
28
64
|
```bash
|
29
|
-
# Using
|
65
|
+
# Using npx
|
30
66
|
npx create-frontend-app my-app
|
31
67
|
|
32
|
-
# Or
|
68
|
+
# Or with the frontend-hamroun CLI
|
33
69
|
npx frontend-hamroun create my-app
|
34
70
|
```
|
35
71
|
|
@@ -41,256 +77,1171 @@ npm install
|
|
41
77
|
npm run dev
|
42
78
|
```
|
43
79
|
|
44
|
-
|
80
|
+
This will scaffold a new project with all the necessary configuration.
|
81
|
+
</details>
|
82
|
+
|
83
|
+
<details>
|
84
|
+
<summary><b>Method 2: Add to an existing project</b></summary>
|
85
|
+
|
86
|
+
```bash
|
87
|
+
# Install the package
|
88
|
+
npm install frontend-hamroun
|
89
|
+
|
90
|
+
# Import and use in your code
|
91
|
+
import { render, useState } from 'frontend-hamroun';
|
92
|
+
```
|
45
93
|
|
46
94
|
```jsx
|
95
|
+
// Create a simple app
|
47
96
|
import { render, useState } from 'frontend-hamroun';
|
48
97
|
|
49
|
-
function
|
98
|
+
function Counter() {
|
50
99
|
const [count, setCount] = useState(0);
|
100
|
+
|
51
101
|
return (
|
52
102
|
<div>
|
53
103
|
<h1>Count: {count}</h1>
|
54
|
-
<button onClick={() => setCount(count + 1)}>
|
104
|
+
<button onClick={() => setCount(count + 1)}>
|
105
|
+
Increment
|
106
|
+
</button>
|
55
107
|
</div>
|
56
108
|
);
|
57
109
|
}
|
58
110
|
|
59
|
-
render(<
|
111
|
+
render(<Counter />, document.getElementById('root'));
|
60
112
|
```
|
113
|
+
</details>
|
61
114
|
|
62
|
-
##
|
115
|
+
## 🧠 Core Concepts
|
63
116
|
|
64
|
-
|
65
|
-
- **Full-Stack Solution**: Client and server capabilities in one package
|
66
|
-
- **Virtual DOM**: Efficient rendering and diffing algorithm
|
67
|
-
- **Hooks API**: Complete hooks system (useState, useEffect, useMemo, useRef)
|
68
|
-
- **Context API**: Simple state management across components
|
69
|
-
- **Server-Side Rendering**: Optimized SSR with hydration
|
70
|
-
- **Database Integration**: Support for MongoDB, MySQL, and PostgreSQL
|
71
|
-
- **Authentication**: Built-in JWT authentication system
|
72
|
-
- **API Routing**: Express-based file system routing
|
73
|
-
- **TypeScript Support**: Full type definitions included
|
74
|
-
- **Interactive CLI**: Powerful tools for project scaffolding and component creation
|
117
|
+
### Virtual DOM
|
75
118
|
|
76
|
-
|
119
|
+
Frontend Hamroun uses a lightweight Virtual DOM implementation to efficiently update the real DOM. It only applies the minimal necessary changes by:
|
77
120
|
|
78
|
-
|
121
|
+
```jsx
|
122
|
+
// Virtual DOM diffing occurs automatically
|
123
|
+
function App() {
|
124
|
+
const [count, setCount] = useState(0);
|
125
|
+
return (
|
126
|
+
<div>
|
127
|
+
<h1>Counter</h1>
|
128
|
+
<p>Count: {count}</p> {/* Only this text node updates */}
|
129
|
+
<button onClick={() => setCount(count + 1)}>Increment</button>
|
130
|
+
</div>
|
131
|
+
);
|
132
|
+
}
|
133
|
+
```
|
134
|
+
|
135
|
+
### Component Model
|
136
|
+
|
137
|
+
Components are the building blocks of your UI. Each component encapsulates its own logic and rendering:
|
79
138
|
|
80
139
|
```jsx
|
81
|
-
|
140
|
+
// Function components with hooks
|
141
|
+
function Greeting({ name }) {
|
142
|
+
// State management
|
143
|
+
const [clicked, setClicked] = useState(false);
|
144
|
+
|
145
|
+
// Side effects
|
146
|
+
useEffect(() => {
|
147
|
+
document.title = `Hello, ${name}`;
|
148
|
+
return () => { document.title = 'App'; };
|
149
|
+
}, [name]);
|
150
|
+
|
151
|
+
return (
|
152
|
+
<div>
|
153
|
+
<h1>{clicked ? `Thanks, ${name}!` : `Hello, ${name}!`}</h1>
|
154
|
+
<button onClick={() => setClicked(true)}>
|
155
|
+
{clicked ? 'Clicked!' : 'Click me'}
|
156
|
+
</button>
|
157
|
+
</div>
|
158
|
+
);
|
159
|
+
}
|
160
|
+
```
|
161
|
+
|
162
|
+
## 🎨 Frontend Features
|
163
|
+
|
164
|
+
### Hooks System
|
165
|
+
|
166
|
+
<details>
|
167
|
+
<summary><b>State Management with useState</b></summary>
|
168
|
+
|
169
|
+
```jsx
|
170
|
+
import { useState } from 'frontend-hamroun';
|
82
171
|
|
83
172
|
function Counter() {
|
84
173
|
const [count, setCount] = useState(0);
|
85
174
|
|
175
|
+
function increment() {
|
176
|
+
setCount(count + 1);
|
177
|
+
}
|
178
|
+
|
179
|
+
function decrement() {
|
180
|
+
setCount(count - 1);
|
181
|
+
}
|
182
|
+
|
183
|
+
// Functional updates for derived state
|
184
|
+
function double() {
|
185
|
+
setCount(prevCount => prevCount * 2);
|
186
|
+
}
|
187
|
+
|
188
|
+
return (
|
189
|
+
<div>
|
190
|
+
<h2>Count: {count}</h2>
|
191
|
+
<button onClick={increment}>+</button>
|
192
|
+
<button onClick={decrement}>-</button>
|
193
|
+
<button onClick={double}>×2</button>
|
194
|
+
</div>
|
195
|
+
);
|
196
|
+
}
|
197
|
+
```
|
198
|
+
</details>
|
199
|
+
|
200
|
+
<details>
|
201
|
+
<summary><b>Side Effects with useEffect</b></summary>
|
202
|
+
|
203
|
+
```jsx
|
204
|
+
import { useState, useEffect } from 'frontend-hamroun';
|
205
|
+
|
206
|
+
function UserProfile({ userId }) {
|
207
|
+
const [user, setUser] = useState(null);
|
208
|
+
const [loading, setLoading] = useState(true);
|
209
|
+
|
86
210
|
useEffect(() => {
|
87
|
-
|
88
|
-
|
89
|
-
|
211
|
+
// Reset state when userId changes
|
212
|
+
setLoading(true);
|
213
|
+
|
214
|
+
// Fetch user data
|
215
|
+
fetch(`/api/users/${userId}`)
|
216
|
+
.then(res => res.json())
|
217
|
+
.then(data => {
|
218
|
+
setUser(data);
|
219
|
+
setLoading(false);
|
220
|
+
})
|
221
|
+
.catch(err => {
|
222
|
+
console.error(err);
|
223
|
+
setLoading(false);
|
224
|
+
});
|
225
|
+
|
226
|
+
// Cleanup function runs on component unmount or before effect re-runs
|
227
|
+
return () => {
|
228
|
+
// Cancel any pending requests or subscriptions
|
229
|
+
console.log('Cleaning up effect for userId:', userId);
|
230
|
+
};
|
231
|
+
}, [userId]); // Only re-run when userId changes
|
232
|
+
|
233
|
+
if (loading) return <div>Loading...</div>;
|
234
|
+
if (!user) return <div>User not found</div>;
|
90
235
|
|
91
236
|
return (
|
92
|
-
<div
|
93
|
-
<
|
94
|
-
<
|
95
|
-
<button onClick={() => setCount(count - 1)}>-</button>
|
237
|
+
<div>
|
238
|
+
<h1>{user.name}</h1>
|
239
|
+
<p>Email: {user.email}</p>
|
96
240
|
</div>
|
97
241
|
);
|
98
242
|
}
|
99
243
|
```
|
244
|
+
</details>
|
245
|
+
|
246
|
+
<details>
|
247
|
+
<summary><b>Performance Optimization with useMemo and useRef</b></summary>
|
100
248
|
|
101
|
-
|
249
|
+
```jsx
|
250
|
+
import { useState, useMemo, useRef } from 'frontend-hamroun';
|
102
251
|
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
252
|
+
function ExpensiveCalculation({ items, filter }) {
|
253
|
+
const [selectedId, setSelectedId] = useState(null);
|
254
|
+
|
255
|
+
// Cache expensive calculation results, only recalculate when dependencies change
|
256
|
+
const filteredItems = useMemo(() => {
|
257
|
+
console.log('Filtering items...');
|
258
|
+
return items.filter(item => item.name.includes(filter));
|
259
|
+
}, [items, filter]);
|
260
|
+
|
261
|
+
// Create a persistent reference that doesn't trigger re-renders
|
262
|
+
const lastRenderTime = useRef(Date.now());
|
263
|
+
|
264
|
+
console.log(`Time since last render: ${Date.now() - lastRenderTime.current}ms`);
|
265
|
+
lastRenderTime.current = Date.now();
|
266
|
+
|
267
|
+
// DOM element references
|
268
|
+
const listRef = useRef(null);
|
269
|
+
|
270
|
+
function scrollToTop() {
|
271
|
+
listRef.current.scrollTop = 0;
|
272
|
+
}
|
273
|
+
|
274
|
+
return (
|
275
|
+
<div>
|
276
|
+
<button onClick={scrollToTop}>Scroll to top</button>
|
277
|
+
<div ref={listRef} style={{ height: '200px', overflow: 'auto' }}>
|
278
|
+
{filteredItems.map(item => (
|
279
|
+
<div
|
280
|
+
key={item.id}
|
281
|
+
onClick={() => setSelectedId(item.id)}
|
282
|
+
style={{
|
283
|
+
background: item.id === selectedId ? 'lightblue' : 'white'
|
284
|
+
}}
|
285
|
+
>
|
286
|
+
{item.name}
|
287
|
+
</div>
|
288
|
+
))}
|
289
|
+
</div>
|
290
|
+
</div>
|
291
|
+
);
|
292
|
+
}
|
293
|
+
```
|
294
|
+
</details>
|
108
295
|
|
109
|
-
|
296
|
+
<details>
|
297
|
+
<summary><b>Context API for State Management</b></summary>
|
110
298
|
|
111
299
|
```jsx
|
112
|
-
|
300
|
+
import { createContext, useContext, useState } from 'frontend-hamroun';
|
301
|
+
|
302
|
+
// Create a context with default value
|
113
303
|
const ThemeContext = createContext('light');
|
114
304
|
|
115
|
-
// Provider
|
116
|
-
function
|
305
|
+
// Provider component to supply context value
|
306
|
+
function ThemeProvider({ children }) {
|
117
307
|
const [theme, setTheme] = useState('light');
|
118
308
|
|
309
|
+
const toggleTheme = () => {
|
310
|
+
setTheme(theme === 'light' ? 'dark' : 'light');
|
311
|
+
};
|
312
|
+
|
119
313
|
return (
|
120
|
-
<ThemeContext.Provider value={theme}>
|
121
|
-
|
122
|
-
<Main />
|
123
|
-
<button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>
|
124
|
-
Toggle theme
|
125
|
-
</button>
|
314
|
+
<ThemeContext.Provider value={{ theme, toggleTheme }}>
|
315
|
+
{children}
|
126
316
|
</ThemeContext.Provider>
|
127
317
|
);
|
128
318
|
}
|
129
319
|
|
130
|
-
// Consumer
|
131
|
-
function
|
132
|
-
const theme = useContext(ThemeContext);
|
133
|
-
|
320
|
+
// Consumer component using the context value
|
321
|
+
function ThemedButton() {
|
322
|
+
const { theme, toggleTheme } = useContext(ThemeContext);
|
323
|
+
|
324
|
+
return (
|
325
|
+
<button
|
326
|
+
onClick={toggleTheme}
|
327
|
+
style={{
|
328
|
+
background: theme === 'light' ? '#fff' : '#333',
|
329
|
+
color: theme === 'light' ? '#333' : '#fff',
|
330
|
+
border: '1px solid #ccc',
|
331
|
+
padding: '8px 16px',
|
332
|
+
}}
|
333
|
+
>
|
334
|
+
Switch to {theme === 'light' ? 'dark' : 'light'} mode
|
335
|
+
</button>
|
336
|
+
);
|
337
|
+
}
|
338
|
+
|
339
|
+
// Usage in application
|
340
|
+
function App() {
|
341
|
+
return (
|
342
|
+
<ThemeProvider>
|
343
|
+
<div>
|
344
|
+
<h1>Themed Application</h1>
|
345
|
+
<ThemedButton />
|
346
|
+
</div>
|
347
|
+
</ThemeProvider>
|
348
|
+
);
|
134
349
|
}
|
135
350
|
```
|
351
|
+
</details>
|
136
352
|
|
137
|
-
|
353
|
+
<details>
|
354
|
+
<summary><b>Error Boundaries with useErrorBoundary</b></summary>
|
355
|
+
|
356
|
+
```jsx
|
357
|
+
import { useErrorBoundary } from 'frontend-hamroun';
|
358
|
+
|
359
|
+
function ErrorBoundary({ children }) {
|
360
|
+
const [error, resetError] = useErrorBoundary();
|
361
|
+
|
362
|
+
if (error) {
|
363
|
+
return (
|
364
|
+
<div className="error-boundary">
|
365
|
+
<h2>Something went wrong</h2>
|
366
|
+
<p>{error.message}</p>
|
367
|
+
<button onClick={resetError}>Try again</button>
|
368
|
+
</div>
|
369
|
+
);
|
370
|
+
}
|
371
|
+
|
372
|
+
return children;
|
373
|
+
}
|
374
|
+
|
375
|
+
// Usage
|
376
|
+
function App() {
|
377
|
+
return (
|
378
|
+
<ErrorBoundary>
|
379
|
+
<UserProfile userId="123" />
|
380
|
+
</ErrorBoundary>
|
381
|
+
);
|
382
|
+
}
|
383
|
+
```
|
384
|
+
</details>
|
385
|
+
|
386
|
+
### Batch Updates for Efficiency
|
387
|
+
|
388
|
+
Frontend Hamroun automatically batches state updates within event handlers and can manually batch other updates with `batchUpdates`:
|
389
|
+
|
390
|
+
```jsx
|
391
|
+
import { useState, batchUpdates } from 'frontend-hamroun';
|
392
|
+
|
393
|
+
function Form() {
|
394
|
+
const [name, setName] = useState('');
|
395
|
+
const [email, setEmail] = useState('');
|
396
|
+
const [isSubmitting, setSubmitting] = useState(false);
|
397
|
+
const [errors, setErrors] = useState({});
|
398
|
+
|
399
|
+
async function handleSubmit(e) {
|
400
|
+
e.preventDefault();
|
401
|
+
|
402
|
+
// Group multiple state updates into a single render
|
403
|
+
batchUpdates(() => {
|
404
|
+
setSubmitting(true);
|
405
|
+
setErrors({});
|
406
|
+
});
|
407
|
+
|
408
|
+
try {
|
409
|
+
const response = await fetch('/api/users', {
|
410
|
+
method: 'POST',
|
411
|
+
headers: { 'Content-Type': 'application/json' },
|
412
|
+
body: JSON.stringify({ name, email })
|
413
|
+
});
|
414
|
+
|
415
|
+
const result = await response.json();
|
416
|
+
|
417
|
+
batchUpdates(() => {
|
418
|
+
setSubmitting(false);
|
419
|
+
setName('');
|
420
|
+
setEmail('');
|
421
|
+
});
|
422
|
+
} catch (error) {
|
423
|
+
batchUpdates(() => {
|
424
|
+
setSubmitting(false);
|
425
|
+
setErrors({ submit: error.message });
|
426
|
+
});
|
427
|
+
}
|
428
|
+
}
|
429
|
+
|
430
|
+
return (
|
431
|
+
<form onSubmit={handleSubmit}>
|
432
|
+
{/* Form fields */}
|
433
|
+
<button type="submit" disabled={isSubmitting}>
|
434
|
+
{isSubmitting ? 'Submitting...' : 'Submit'}
|
435
|
+
</button>
|
436
|
+
</form>
|
437
|
+
);
|
438
|
+
}
|
439
|
+
```
|
440
|
+
|
441
|
+
## 🖥️ Backend Features
|
138
442
|
|
139
443
|
### Express Server Integration
|
140
444
|
|
445
|
+
<details>
|
446
|
+
<summary><b>Server Setup</b></summary>
|
447
|
+
|
141
448
|
```js
|
142
449
|
import { server } from 'frontend-hamroun/server';
|
143
450
|
|
144
451
|
const app = await server.createServer({
|
145
452
|
port: 3000,
|
146
|
-
apiDir: './api',
|
147
|
-
pagesDir: './pages',
|
148
|
-
staticDir: './public',
|
453
|
+
apiDir: './api', // Directory for API routes
|
454
|
+
pagesDir: './pages', // Directory for page components
|
455
|
+
staticDir: './public', // Directory for static files
|
456
|
+
|
457
|
+
// Database configuration
|
149
458
|
db: {
|
150
459
|
url: process.env.DATABASE_URL,
|
151
|
-
type: 'mongodb'
|
460
|
+
type: 'mongodb' // mongodb, mysql, or postgres
|
152
461
|
},
|
462
|
+
|
463
|
+
// Authentication configuration
|
153
464
|
auth: {
|
154
465
|
secret: process.env.JWT_SECRET,
|
155
|
-
expiresIn: '7d'
|
466
|
+
expiresIn: '7d' // Token expiration time
|
156
467
|
}
|
157
468
|
});
|
158
469
|
|
159
470
|
await app.start();
|
160
471
|
console.log('Server running at http://localhost:3000');
|
161
472
|
```
|
473
|
+
</details>
|
474
|
+
|
475
|
+
### File-Based API Routing
|
162
476
|
|
163
|
-
|
477
|
+
<details>
|
478
|
+
<summary><b>API Routes</b></summary>
|
164
479
|
|
165
480
|
```js
|
166
|
-
// api/users.js
|
481
|
+
// api/users.js (automatically maps to /api/users)
|
167
482
|
export async function get(req, res) {
|
483
|
+
// GET /api/users - List all users
|
168
484
|
const users = await req.db.collection('users').find().toArray();
|
169
485
|
res.json(users);
|
170
486
|
}
|
171
487
|
|
172
488
|
export async function post(req, res) {
|
489
|
+
// POST /api/users - Create a new user
|
173
490
|
const { name, email } = req.body;
|
174
491
|
|
175
492
|
if (!name || !email) {
|
176
493
|
return res.status(400).json({ error: 'Name and email are required' });
|
177
494
|
}
|
178
495
|
|
179
|
-
const result = await req.db.collection('users').insertOne({
|
180
|
-
|
496
|
+
const result = await req.db.collection('users').insertOne({
|
497
|
+
name,
|
498
|
+
email,
|
499
|
+
createdAt: new Date()
|
500
|
+
});
|
501
|
+
|
502
|
+
res.status(201).json({ id: result.insertedId });
|
181
503
|
}
|
182
504
|
```
|
183
505
|
|
184
|
-
|
506
|
+
```js
|
507
|
+
// api/users/[id].js (automatically maps to /api/users/:id)
|
508
|
+
export async function get(req, res) {
|
509
|
+
// GET /api/users/:id - Get user by ID
|
510
|
+
const { id } = req.params;
|
511
|
+
|
512
|
+
try {
|
513
|
+
const user = await req.db.collection('users').findOne({
|
514
|
+
_id: new ObjectId(id)
|
515
|
+
});
|
516
|
+
|
517
|
+
if (!user) {
|
518
|
+
return res.status(404).json({ error: 'User not found' });
|
519
|
+
}
|
520
|
+
|
521
|
+
res.json(user);
|
522
|
+
} catch (error) {
|
523
|
+
res.status(500).json({ error: 'Server error' });
|
524
|
+
}
|
525
|
+
}
|
526
|
+
|
527
|
+
export async function put(req, res) {
|
528
|
+
// PUT /api/users/:id - Update user
|
529
|
+
const { id } = req.params;
|
530
|
+
const { name, email } = req.body;
|
531
|
+
|
532
|
+
await req.db.collection('users').updateOne(
|
533
|
+
{ _id: new ObjectId(id) },
|
534
|
+
{ $set: { name, email, updatedAt: new Date() } }
|
535
|
+
);
|
536
|
+
|
537
|
+
res.json({ success: true });
|
538
|
+
}
|
539
|
+
|
540
|
+
export async function del(req, res) {
|
541
|
+
// DELETE /api/users/:id - Delete user
|
542
|
+
// Note: 'delete' is a reserved word, so we use 'del'
|
543
|
+
const { id } = req.params;
|
544
|
+
|
545
|
+
await req.db.collection('users').deleteOne({
|
546
|
+
_id: new ObjectId(id)
|
547
|
+
});
|
548
|
+
|
549
|
+
res.status(204).end();
|
550
|
+
}
|
551
|
+
```
|
552
|
+
</details>
|
553
|
+
|
554
|
+
### Database Integration
|
555
|
+
|
556
|
+
<details>
|
557
|
+
<summary><b>MongoDB Example</b></summary>
|
558
|
+
|
559
|
+
```js
|
560
|
+
// api/posts.js
|
561
|
+
export async function get(req, res) {
|
562
|
+
// Complex MongoDB aggregation
|
563
|
+
const posts = await req.db.collection('posts')
|
564
|
+
.aggregate([
|
565
|
+
{ $match: { published: true } },
|
566
|
+
{ $sort: { createdAt: -1 } },
|
567
|
+
{ $limit: 10 },
|
568
|
+
{ $lookup: {
|
569
|
+
from: 'users',
|
570
|
+
localField: 'authorId',
|
571
|
+
foreignField: '_id',
|
572
|
+
as: 'author'
|
573
|
+
}},
|
574
|
+
{ $unwind: '$author' },
|
575
|
+
{ $project: {
|
576
|
+
title: 1,
|
577
|
+
content: 1,
|
578
|
+
createdAt: 1,
|
579
|
+
'author.name': 1,
|
580
|
+
'author.email': 1
|
581
|
+
}}
|
582
|
+
])
|
583
|
+
.toArray();
|
584
|
+
|
585
|
+
res.json(posts);
|
586
|
+
}
|
587
|
+
```
|
588
|
+
</details>
|
589
|
+
|
590
|
+
<details>
|
591
|
+
<summary><b>MySQL Example</b></summary>
|
592
|
+
|
593
|
+
```js
|
594
|
+
// api/products.js
|
595
|
+
export async function get(req, res) {
|
596
|
+
// Complex SQL query with joins
|
597
|
+
const [products] = await req.db.execute(`
|
598
|
+
SELECT
|
599
|
+
p.id,
|
600
|
+
p.name,
|
601
|
+
p.price,
|
602
|
+
c.name as categoryName
|
603
|
+
FROM
|
604
|
+
products p
|
605
|
+
JOIN
|
606
|
+
categories c ON p.category_id = c.id
|
607
|
+
WHERE
|
608
|
+
p.active = ?
|
609
|
+
ORDER BY
|
610
|
+
p.created_at DESC
|
611
|
+
LIMIT 20
|
612
|
+
`, [true]);
|
613
|
+
|
614
|
+
res.json(products);
|
615
|
+
}
|
616
|
+
```
|
617
|
+
</details>
|
618
|
+
|
619
|
+
<details>
|
620
|
+
<summary><b>PostgreSQL Example</b></summary>
|
621
|
+
|
622
|
+
```js
|
623
|
+
// api/analytics.js
|
624
|
+
export async function get(req, res) {
|
625
|
+
// Advanced PostgreSQL features
|
626
|
+
const result = await req.db.query(`
|
627
|
+
WITH monthly_sales AS (
|
628
|
+
SELECT
|
629
|
+
date_trunc('month', order_date) as month,
|
630
|
+
SUM(total_amount) as revenue
|
631
|
+
FROM
|
632
|
+
orders
|
633
|
+
WHERE
|
634
|
+
order_date > NOW() - INTERVAL '1 year'
|
635
|
+
GROUP BY
|
636
|
+
date_trunc('month', order_date)
|
637
|
+
)
|
638
|
+
SELECT
|
639
|
+
month,
|
640
|
+
revenue,
|
641
|
+
lag(revenue) OVER (ORDER BY month) as prev_month_revenue,
|
642
|
+
round((revenue - lag(revenue) OVER (ORDER BY month)) /
|
643
|
+
lag(revenue) OVER (ORDER BY month) * 100, 2) as growth_percent
|
644
|
+
FROM
|
645
|
+
monthly_sales
|
646
|
+
ORDER BY
|
647
|
+
month
|
648
|
+
`);
|
649
|
+
|
650
|
+
res.json(result.rows);
|
651
|
+
}
|
652
|
+
```
|
653
|
+
</details>
|
654
|
+
|
655
|
+
### Authentication System
|
656
|
+
|
657
|
+
<details>
|
658
|
+
<summary><b>JWT Authentication</b></summary>
|
659
|
+
|
660
|
+
```js
|
661
|
+
// api/auth/login.js
|
662
|
+
export async function post(req, res) {
|
663
|
+
const { email, password } = req.body;
|
664
|
+
|
665
|
+
// Validate input
|
666
|
+
if (!email || !password) {
|
667
|
+
return res.status(400).json({ error: 'Email and password are required' });
|
668
|
+
}
|
669
|
+
|
670
|
+
try {
|
671
|
+
// Find user by email
|
672
|
+
const user = await req.db.collection('users').findOne({ email });
|
673
|
+
|
674
|
+
// Check if user exists and password is correct
|
675
|
+
if (!user || !await req.auth.comparePasswords(password, user.password)) {
|
676
|
+
return res.status(401).json({ error: 'Invalid credentials' });
|
677
|
+
}
|
678
|
+
|
679
|
+
// Generate JWT token
|
680
|
+
const token = req.auth.generateToken({
|
681
|
+
id: user._id,
|
682
|
+
name: user.name,
|
683
|
+
email: user.email,
|
684
|
+
roles: user.roles || ['user']
|
685
|
+
});
|
686
|
+
|
687
|
+
// Return token and user info (excluding sensitive data)
|
688
|
+
res.json({
|
689
|
+
token,
|
690
|
+
user: {
|
691
|
+
id: user._id,
|
692
|
+
name: user.name,
|
693
|
+
email: user.email,
|
694
|
+
roles: user.roles || ['user']
|
695
|
+
}
|
696
|
+
});
|
697
|
+
} catch (error) {
|
698
|
+
res.status(500).json({ error: 'Authentication failed' });
|
699
|
+
}
|
700
|
+
}
|
701
|
+
```
|
702
|
+
|
703
|
+
```js
|
704
|
+
// api/auth/register.js
|
705
|
+
export async function post(req, res) {
|
706
|
+
const { name, email, password } = req.body;
|
707
|
+
|
708
|
+
// Validate input
|
709
|
+
if (!name || !email || !password) {
|
710
|
+
return res.status(400).json({
|
711
|
+
error: 'Name, email, and password are required'
|
712
|
+
});
|
713
|
+
}
|
714
|
+
|
715
|
+
try {
|
716
|
+
// Check if user already exists
|
717
|
+
const existingUser = await req.db.collection('users').findOne({ email });
|
718
|
+
if (existingUser) {
|
719
|
+
return res.status(409).json({ error: 'Email already in use' });
|
720
|
+
}
|
721
|
+
|
722
|
+
// Hash password
|
723
|
+
const hashedPassword = await req.auth.hashPassword(password);
|
724
|
+
|
725
|
+
// Create user
|
726
|
+
const result = await req.db.collection('users').insertOne({
|
727
|
+
name,
|
728
|
+
email,
|
729
|
+
password: hashedPassword,
|
730
|
+
roles: ['user'],
|
731
|
+
createdAt: new Date()
|
732
|
+
});
|
733
|
+
|
734
|
+
res.status(201).json({
|
735
|
+
id: result.insertedId,
|
736
|
+
name,
|
737
|
+
email,
|
738
|
+
roles: ['user']
|
739
|
+
});
|
740
|
+
} catch (error) {
|
741
|
+
res.status(500).json({ error: 'Registration failed' });
|
742
|
+
}
|
743
|
+
}
|
744
|
+
```
|
745
|
+
|
746
|
+
```js
|
747
|
+
// middleware/requireAuth.js - Protected route middleware
|
748
|
+
export default function requireAuth(req, res, next) {
|
749
|
+
const token = req.headers.authorization?.split(' ')[1];
|
750
|
+
|
751
|
+
if (!token) {
|
752
|
+
return res.status(401).json({ error: 'Authentication required' });
|
753
|
+
}
|
754
|
+
|
755
|
+
try {
|
756
|
+
// Verify token
|
757
|
+
const decoded = req.auth.verifyToken(token);
|
758
|
+
req.user = decoded; // Attach user to request
|
759
|
+
next();
|
760
|
+
} catch (error) {
|
761
|
+
return res.status(401).json({ error: 'Invalid or expired token' });
|
762
|
+
}
|
763
|
+
}
|
764
|
+
|
765
|
+
// api/profile.js - Protected route example
|
766
|
+
import requireAuth from '../middleware/requireAuth.js';
|
767
|
+
|
768
|
+
export const middleware = [requireAuth];
|
769
|
+
|
770
|
+
export async function get(req, res) {
|
771
|
+
// req.user is available from requireAuth middleware
|
772
|
+
const { id } = req.user;
|
773
|
+
|
774
|
+
const profile = await req.db.collection('users').findOne(
|
775
|
+
{ _id: new ObjectId(id) },
|
776
|
+
{ projection: { password: 0 } } // Exclude password
|
777
|
+
);
|
778
|
+
|
779
|
+
res.json(profile);
|
780
|
+
}
|
781
|
+
```
|
782
|
+
</details>
|
783
|
+
|
784
|
+
## 🔄 Server-Side Rendering
|
785
|
+
|
786
|
+
Frontend Hamroun provides built-in server-side rendering capabilities to improve performance and SEO:
|
787
|
+
|
788
|
+
<details>
|
789
|
+
<summary><b>Server-Side Rendering Setup</b></summary>
|
185
790
|
|
186
791
|
```jsx
|
187
|
-
//
|
792
|
+
// server.js
|
793
|
+
import express from 'express';
|
188
794
|
import { renderToString } from 'frontend-hamroun/ssr';
|
189
795
|
import App from './App';
|
190
796
|
|
797
|
+
const app = express();
|
798
|
+
app.use(express.static('public'));
|
799
|
+
|
191
800
|
app.get('*', async (req, res) => {
|
801
|
+
// Render app to string
|
192
802
|
const html = await renderToString(<App url={req.url} />);
|
193
803
|
|
804
|
+
// Send complete HTML document
|
194
805
|
res.send(`
|
195
806
|
<!DOCTYPE html>
|
196
807
|
<html>
|
197
808
|
<head>
|
198
809
|
<title>My SSR App</title>
|
810
|
+
<meta charset="UTF-8">
|
811
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
199
812
|
<link rel="stylesheet" href="/styles.css">
|
200
813
|
</head>
|
201
814
|
<body>
|
202
815
|
<div id="root">${html}</div>
|
203
|
-
<script src="/
|
816
|
+
<script src="/bundle.js"></script>
|
204
817
|
</body>
|
205
818
|
</html>
|
206
819
|
`);
|
207
820
|
});
|
208
821
|
|
209
|
-
|
822
|
+
app.listen(3000, () => {
|
823
|
+
console.log('Server running at http://localhost:3000');
|
824
|
+
});
|
825
|
+
|
826
|
+
// client.js (for hydration)
|
210
827
|
import { hydrate } from 'frontend-hamroun';
|
211
828
|
import App from './App';
|
212
829
|
|
830
|
+
// Hydrate the app in the browser
|
213
831
|
hydrate(<App url={window.location.pathname} />, document.getElementById('root'));
|
214
832
|
```
|
833
|
+
</details>
|
215
834
|
|
216
|
-
##
|
835
|
+
## 🛠️ CLI Tools
|
217
836
|
|
218
|
-
|
219
|
-
Group multiple state updates together to prevent unnecessary re-renders:
|
837
|
+
Frontend Hamroun includes a powerful CLI for scaffolding projects, components, and API routes:
|
220
838
|
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
});
|
231
|
-
}
|
839
|
+
<details>
|
840
|
+
<summary><b>Project Creation</b></summary>
|
841
|
+
|
842
|
+
```bash
|
843
|
+
# Create a new project with interactive prompts
|
844
|
+
npx frontend-hamroun create my-app
|
845
|
+
|
846
|
+
# Create with specific template
|
847
|
+
npx frontend-hamroun create my-app --template fullstack-app
|
232
848
|
```
|
233
849
|
|
234
|
-
|
850
|
+
Available templates:
|
851
|
+
- `basic-app`: Minimal client-side setup
|
852
|
+
- `ssr-template`: Server-side rendering with hydration
|
853
|
+
- `fullstack-app`: Complete solution with frontend, backend, auth, and DB
|
854
|
+
</details>
|
235
855
|
|
236
|
-
|
856
|
+
<details>
|
857
|
+
<summary><b>Component Generation</b></summary>
|
237
858
|
|
238
859
|
```bash
|
239
|
-
# Create a new project
|
240
|
-
npx frontend-hamroun create my-app
|
241
|
-
|
242
860
|
# Generate a new component
|
243
861
|
npx frontend-hamroun add:component Button
|
244
862
|
|
245
|
-
#
|
246
|
-
npx frontend-hamroun add:
|
863
|
+
# Generate a TypeScript component
|
864
|
+
npx frontend-hamroun add:component UserProfile --typescript
|
247
865
|
|
248
|
-
#
|
249
|
-
npx frontend-hamroun add:
|
866
|
+
# Specify path and hooks
|
867
|
+
npx frontend-hamroun add:component Sidebar --path=src/layout --hooks=useState,useEffect
|
250
868
|
```
|
869
|
+
</details>
|
251
870
|
|
252
|
-
|
871
|
+
<details>
|
872
|
+
<summary><b>API Route Generation</b></summary>
|
253
873
|
|
254
|
-
|
874
|
+
```bash
|
875
|
+
# Generate API route
|
876
|
+
npx frontend-hamroun add:api products
|
877
|
+
|
878
|
+
# Specify HTTP methods and auth requirement
|
879
|
+
npx frontend-hamroun add:api orders --methods=get,post --auth
|
880
|
+
```
|
881
|
+
</details>
|
882
|
+
|
883
|
+
## 🧩 Advanced Usage
|
884
|
+
|
885
|
+
<details>
|
886
|
+
<summary><b>Custom Hooks</b></summary>
|
255
887
|
|
256
|
-
|
257
|
-
|
258
|
-
|
888
|
+
```jsx
|
889
|
+
import { useState, useEffect } from 'frontend-hamroun';
|
890
|
+
|
891
|
+
// Custom hook for fetching data
|
892
|
+
function useFetch(url, options = {}) {
|
893
|
+
const [data, setData] = useState(null);
|
894
|
+
const [loading, setLoading] = useState(true);
|
895
|
+
const [error, setError] = useState(null);
|
896
|
+
|
897
|
+
useEffect(() => {
|
898
|
+
setLoading(true);
|
899
|
+
setError(null);
|
900
|
+
|
901
|
+
fetch(url, options)
|
902
|
+
.then(response => {
|
903
|
+
if (!response.ok) {
|
904
|
+
throw new Error(`HTTP error ${response.status}`);
|
905
|
+
}
|
906
|
+
return response.json();
|
907
|
+
})
|
908
|
+
.then(json => {
|
909
|
+
setData(json);
|
910
|
+
setLoading(false);
|
911
|
+
})
|
912
|
+
.catch(err => {
|
913
|
+
setError(err.message);
|
914
|
+
setLoading(false);
|
915
|
+
});
|
916
|
+
}, [url]);
|
917
|
+
|
918
|
+
return { data, loading, error };
|
919
|
+
}
|
920
|
+
|
921
|
+
// Usage
|
922
|
+
function UserList() {
|
923
|
+
const { data, loading, error } = useFetch('/api/users');
|
924
|
+
|
925
|
+
if (loading) return <div>Loading...</div>;
|
926
|
+
if (error) return <div>Error: {error}</div>;
|
927
|
+
|
928
|
+
return (
|
929
|
+
<ul>
|
930
|
+
{data.map(user => (
|
931
|
+
<li key={user.id}>{user.name}</li>
|
932
|
+
))}
|
933
|
+
</ul>
|
934
|
+
);
|
935
|
+
}
|
936
|
+
```
|
937
|
+
</details>
|
259
938
|
|
260
|
-
|
939
|
+
<details>
|
940
|
+
<summary><b>Performance Optimization Techniques</b></summary>
|
941
|
+
|
942
|
+
```jsx
|
943
|
+
import { useMemo, useState, useRef, batchUpdates } from 'frontend-hamroun';
|
944
|
+
|
945
|
+
function OptimizedList({ items }) {
|
946
|
+
const [filter, setFilter] = useState('');
|
947
|
+
const [sortOrder, setSortOrder] = useState('asc');
|
948
|
+
const prevItems = useRef(items);
|
949
|
+
|
950
|
+
// Memoize expensive calculations
|
951
|
+
const processedItems = useMemo(() => {
|
952
|
+
console.log('Processing items...');
|
953
|
+
let result = [...items];
|
954
|
+
|
955
|
+
// Apply filter
|
956
|
+
if (filter) {
|
957
|
+
result = result.filter(item =>
|
958
|
+
item.name.toLowerCase().includes(filter.toLowerCase())
|
959
|
+
);
|
960
|
+
}
|
961
|
+
|
962
|
+
// Apply sorting
|
963
|
+
result.sort((a, b) => {
|
964
|
+
const comparison = a.name.localeCompare(b.name);
|
965
|
+
return sortOrder === 'asc' ? comparison : -comparison;
|
966
|
+
});
|
967
|
+
|
968
|
+
return result;
|
969
|
+
}, [items, filter, sortOrder]);
|
970
|
+
|
971
|
+
// Check for changed items with more control than dependency arrays
|
972
|
+
if (items !== prevItems.current) {
|
973
|
+
console.log('Items array reference changed');
|
974
|
+
prevItems.current = items;
|
975
|
+
}
|
976
|
+
|
977
|
+
// Batch multiple state updates together
|
978
|
+
const toggleSortAndClear = () => {
|
979
|
+
batchUpdates(() => {
|
980
|
+
setSortOrder(sortOrder === 'asc' ? 'desc' : 'asc');
|
981
|
+
setFilter('');
|
982
|
+
});
|
983
|
+
};
|
984
|
+
|
985
|
+
return (
|
986
|
+
<div>
|
987
|
+
<div>
|
988
|
+
<input
|
989
|
+
type="text"
|
990
|
+
value={filter}
|
991
|
+
onChange={e => setFilter(e.target.value)}
|
992
|
+
placeholder="Filter items..."
|
993
|
+
/>
|
994
|
+
<button onClick={toggleSortAndClear}>
|
995
|
+
Toggle Sort ({sortOrder})
|
996
|
+
</button>
|
997
|
+
</div>
|
998
|
+
|
999
|
+
{/* Using key for efficient list rendering */}
|
1000
|
+
<ul>
|
1001
|
+
{processedItems.map(item => (
|
1002
|
+
<li key={item.id}>{item.name}</li>
|
1003
|
+
))}
|
1004
|
+
</ul>
|
1005
|
+
</div>
|
1006
|
+
);
|
1007
|
+
}
|
1008
|
+
```
|
1009
|
+
</details>
|
1010
|
+
|
1011
|
+
## 📝 TypeScript Support
|
1012
|
+
|
1013
|
+
Frontend Hamroun is built with TypeScript and comes with complete type definitions:
|
1014
|
+
|
1015
|
+
<details>
|
1016
|
+
<summary><b>Type-Safe Component Example</b></summary>
|
261
1017
|
|
262
1018
|
```tsx
|
263
|
-
import { useState } from 'frontend-hamroun';
|
1019
|
+
import { useState, useEffect } from 'frontend-hamroun';
|
264
1020
|
|
265
|
-
|
1021
|
+
// Define props interface
|
1022
|
+
interface UserProfileProps {
|
266
1023
|
id: number;
|
267
1024
|
name: string;
|
268
1025
|
email: string;
|
1026
|
+
role: 'admin' | 'user' | 'guest';
|
1027
|
+
onStatusChange?: (id: number, active: boolean) => void;
|
1028
|
+
}
|
1029
|
+
|
1030
|
+
// Define state interface
|
1031
|
+
interface UserProfileState {
|
1032
|
+
isActive: boolean;
|
1033
|
+
isEditing: boolean;
|
1034
|
+
formData: {
|
1035
|
+
name: string;
|
1036
|
+
email: string;
|
1037
|
+
};
|
269
1038
|
}
|
270
1039
|
|
271
|
-
function UserProfile({
|
272
|
-
|
1040
|
+
function UserProfile({
|
1041
|
+
id,
|
1042
|
+
name,
|
1043
|
+
email,
|
1044
|
+
role,
|
1045
|
+
onStatusChange
|
1046
|
+
}: UserProfileProps) {
|
1047
|
+
// Type-safe state
|
1048
|
+
const [state, setState] = useState<UserProfileState>({
|
1049
|
+
isActive: true,
|
1050
|
+
isEditing: false,
|
1051
|
+
formData: {
|
1052
|
+
name,
|
1053
|
+
email
|
1054
|
+
}
|
1055
|
+
});
|
1056
|
+
|
1057
|
+
// Type-safe event handlers
|
1058
|
+
const toggleStatus = (): void => {
|
1059
|
+
const newStatus = !state.isActive;
|
1060
|
+
setState(prev => ({
|
1061
|
+
...prev,
|
1062
|
+
isActive: newStatus
|
1063
|
+
}));
|
1064
|
+
|
1065
|
+
if (onStatusChange) {
|
1066
|
+
onStatusChange(id, newStatus);
|
1067
|
+
}
|
1068
|
+
};
|
1069
|
+
|
1070
|
+
// Type-safe refs
|
1071
|
+
const formRef = useRef<HTMLFormElement>(null);
|
273
1072
|
|
274
1073
|
return (
|
275
1074
|
<div className="user-profile">
|
276
1075
|
<h2>{name}</h2>
|
277
|
-
<p>{email}</p>
|
278
|
-
<
|
279
|
-
|
1076
|
+
<p>Email: {email}</p>
|
1077
|
+
<p>Role: {role}</p>
|
1078
|
+
<p>Status: {state.isActive ? 'Active' : 'Inactive'}</p>
|
1079
|
+
|
1080
|
+
<button onClick={toggleStatus}>
|
1081
|
+
{state.isActive ? 'Deactivate' : 'Activate'}
|
280
1082
|
</button>
|
1083
|
+
|
1084
|
+
{state.isEditing ? (
|
1085
|
+
<form ref={formRef}>
|
1086
|
+
{/* Form fields */}
|
1087
|
+
</form>
|
1088
|
+
) : (
|
1089
|
+
<button onClick={() => setState(prev => ({
|
1090
|
+
...prev,
|
1091
|
+
isEditing: true
|
1092
|
+
}))}>
|
1093
|
+
Edit
|
1094
|
+
</button>
|
1095
|
+
)}
|
281
1096
|
</div>
|
282
1097
|
);
|
283
1098
|
}
|
1099
|
+
|
1100
|
+
// Usage with type checking
|
1101
|
+
const App = () => (
|
1102
|
+
<div>
|
1103
|
+
<UserProfile
|
1104
|
+
id={1}
|
1105
|
+
name="John Doe"
|
1106
|
+
email="john@example.com"
|
1107
|
+
role="admin"
|
1108
|
+
onStatusChange={(id, active) => {
|
1109
|
+
console.log(`User ${id} status changed to ${active}`);
|
1110
|
+
}}
|
1111
|
+
/>
|
1112
|
+
|
1113
|
+
{/* This would cause TypeScript errors */}
|
1114
|
+
{/*
|
1115
|
+
<UserProfile
|
1116
|
+
id="1" // Error: string is not assignable to number
|
1117
|
+
name="Jane Doe"
|
1118
|
+
role="manager" // Error: 'manager' is not assignable
|
1119
|
+
/>
|
1120
|
+
*/}
|
1121
|
+
</div>
|
1122
|
+
);
|
284
1123
|
```
|
1124
|
+
</details>
|
285
1125
|
|
286
|
-
|
1126
|
+
<details>
|
1127
|
+
<summary><b>Type-Safe API Routes</b></summary>
|
287
1128
|
|
288
|
-
|
1129
|
+
```ts
|
1130
|
+
// types.ts
|
1131
|
+
export interface User {
|
1132
|
+
id?: string;
|
1133
|
+
name: string;
|
1134
|
+
email: string;
|
1135
|
+
password?: string;
|
1136
|
+
role: 'admin' | 'user';
|
1137
|
+
createdAt?: Date;
|
1138
|
+
updatedAt?: Date;
|
1139
|
+
}
|
289
1140
|
|
290
|
-
|
1141
|
+
// api/users/[id].ts
|
1142
|
+
import type { User } from '../../types';
|
291
1143
|
|
292
|
-
|
1144
|
+
export async function get(req, res) {
|
1145
|
+
const { id } = req.params;
|
1146
|
+
|
1147
|
+
try {
|
1148
|
+
const user = await req.db.collection('users').findOne({
|
1149
|
+
_id: new ObjectId(id)
|
1150
|
+
}) as User;
|
1151
|
+
|
1152
|
+
if (!user) {
|
1153
|
+
return res.status(404).json({ error: 'User not found' });
|
1154
|
+
}
|
1155
|
+
|
1156
|
+
// Omit sensitive data
|
1157
|
+
const { password, ...safeUser } = user;
|
1158
|
+
|
1159
|
+
res.json(safeUser);
|
1160
|
+
} catch (error) {
|
1161
|
+
res.status(500).json({ error: 'Server error' });
|
1162
|
+
}
|
1163
|
+
}
|
293
1164
|
|
294
|
-
|
1165
|
+
export async function put(req, res) {
|
1166
|
+
const { id } = req.params;
|
1167
|
+
const { name, email, role } = req.body as Partial<User>;
|
1168
|
+
|
1169
|
+
// Validate that role is a valid enum value
|
1170
|
+
if (role && !['admin', 'user'].includes(role)) {
|
1171
|
+
return res.status(400).json({ error: 'Invalid role' });
|
1172
|
+
}
|
1173
|
+
|
1174
|
+
try {
|
1175
|
+
await req.db.collection('users').updateOne(
|
1176
|
+
{ _id: new ObjectId(id) },
|
1177
|
+
{
|
1178
|
+
$set: {
|
1179
|
+
...(name && { name }),
|
1180
|
+
...(email && { email }),
|
1181
|
+
...(role && { role }),
|
1182
|
+
updatedAt: new Date()
|
1183
|
+
}
|
1184
|
+
}
|
1185
|
+
);
|
1186
|
+
|
1187
|
+
res.json({ success: true });
|
1188
|
+
} catch (error) {
|
1189
|
+
res.status(500).json({ error: 'Server error' });
|
1190
|
+
}
|
1191
|
+
}
|
1192
|
+
```
|
1193
|
+
</details>
|
1194
|
+
|
1195
|
+
## 🌐 Browser Compatibility
|
1196
|
+
|
1197
|
+
Frontend Hamroun supports all modern browsers out of the box:
|
1198
|
+
|
1199
|
+
- Chrome (latest 2 versions)
|
1200
|
+
- Firefox (latest 2 versions)
|
1201
|
+
- Safari (latest 2 versions)
|
1202
|
+
- Edge (latest 2 versions)
|
1203
|
+
|
1204
|
+
For older browsers like IE11, you'll need:
|
1205
|
+
- Appropriate polyfills
|
1206
|
+
- Transpilation to ES5
|
1207
|
+
- CSS compatibility work
|
1208
|
+
|
1209
|
+
## ❓ Frequently Asked Questions
|
1210
|
+
|
1211
|
+
<details>
|
1212
|
+
<summary><b>How does Frontend Hamroun compare to React?</b></summary>
|
1213
|
+
|
1214
|
+
Frontend Hamroun offers a React-like API but with additional built-in features:
|
1215
|
+
- Smaller bundle size (~5KB vs. 42KB+)
|
1216
|
+
- Integrated backend capabilities
|
1217
|
+
- Built-in server-side rendering
|
1218
|
+
- Database integrations
|
1219
|
+
- Authentication system
|
1220
|
+
|
1221
|
+
For React developers, the learning curve is minimal.
|
1222
|
+
</details>
|
1223
|
+
|
1224
|
+
<details>
|
1225
|
+
<summary><b>Can I use it with existing React components?</b></summary>
|
1226
|
+
|
1227
|
+
While Frontend Hamroun's API is similar to React's, they have different internal implementations. You can:
|
1228
|
+
- Port React components to Frontend Hamroun with minimal changes
|
1229
|
+
- Use React components in dedicated sections with a compatibility layer
|
1230
|
+
- Share non-UI code and logic between both frameworks
|
1231
|
+
</details>
|
1232
|
+
|
1233
|
+
<details>
|
1234
|
+
<summary><b>Does it support static site generation (SSG)?</b></summary>
|
1235
|
+
|
1236
|
+
Yes, Frontend Hamroun provides tools for static site generation. Use the `renderToString` API to pre-render pages at build time.
|
1237
|
+
</details>
|
1238
|
+
|
1239
|
+
## 📄 License
|
295
1240
|
|
296
1241
|
MIT © Hamroun
|
1242
|
+
|
1243
|
+
---
|
1244
|
+
|
1245
|
+
<p align="center">
|
1246
|
+
<a href="#-table-of-contents">⬆️ Back to top</a>
|
1247
|
+
</p>
|