create-aziz-app 2.0.11 ā 2.0.12
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/package.json +1 -1
- package/template/README.md +9 -5
- package/template/api/hello.js +5 -6
- package/template/api/users.js +6 -8
- package/template/app.html +21 -25
- package/template/docs/API-ROUTES.md +538 -0
- package/template/modules/api.js +20 -124
- package/template/scripts/dev-server.js +170 -1
- package/template/scripts/generate-imports.js +0 -10
package/package.json
CHANGED
package/template/README.md
CHANGED
|
@@ -125,10 +125,12 @@ navigate("about");
|
|
|
125
125
|
|
|
126
126
|
### API Routes
|
|
127
127
|
```javascript
|
|
128
|
-
// api/todos.js
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
});
|
|
128
|
+
// api/todos.js - Runs on SERVER (Node.js)!
|
|
129
|
+
module.exports = async function handler(req, res) {
|
|
130
|
+
if (req.method === 'GET') {
|
|
131
|
+
return res.status(200).json({ todos: [] });
|
|
132
|
+
}
|
|
133
|
+
};
|
|
132
134
|
|
|
133
135
|
// Use in components
|
|
134
136
|
const response = await fetchApi('/api/todos');
|
|
@@ -152,9 +154,11 @@ The CLI will automatically set up your chosen framework!
|
|
|
152
154
|
|
|
153
155
|
- **[Getting Started](docs/GETTING-STARTED.md)** - Complete setup guide
|
|
154
156
|
- **[Auto-Registration](docs/AUTO-REGISTRATION.md)** - How pages auto-register
|
|
155
|
-
- **[API
|
|
157
|
+
- **[API Routes](docs/API-ROUTES.md)** - Server-side API endpoints (like Next.js!)
|
|
158
|
+
- **[API Guide](docs/API-GUIDE.md)** - Legacy API guide
|
|
156
159
|
- **[Auto-Import](docs/AUTO-IMPORT.md)** - Auto-import system details
|
|
157
160
|
- **[Dev Server](docs/DEV-SERVER.md)** - Live reload and development
|
|
161
|
+
- **[How It Works](docs/HOW-IT-WORKS.md)** - System architecture
|
|
158
162
|
- **[Troubleshooting](docs/TROUBLESHOOTING.md)** - Fix common issues
|
|
159
163
|
- **[Quick Start](docs/QUICK-START.md)** - Quick examples
|
|
160
164
|
|
package/template/api/hello.js
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
|
|
1
|
+
// API Route: /api/hello
|
|
2
|
+
// This runs on the SERVER (Node.js), not in the browser!
|
|
2
3
|
|
|
3
|
-
//
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
registerApiRoute('hello', async (req, res) => {
|
|
4
|
+
// Export the handler function (like Next.js)
|
|
5
|
+
module.exports = async function handler(req, res) {
|
|
7
6
|
const name = req.query.name || 'World';
|
|
8
7
|
|
|
9
8
|
return res.status(200).json({
|
|
@@ -11,4 +10,4 @@ registerApiRoute('hello', async (req, res) => {
|
|
|
11
10
|
timestamp: new Date().toISOString(),
|
|
12
11
|
method: req.method
|
|
13
12
|
});
|
|
14
|
-
}
|
|
13
|
+
};
|
package/template/api/users.js
CHANGED
|
@@ -1,17 +1,15 @@
|
|
|
1
|
-
|
|
1
|
+
// API Route: /api/users
|
|
2
|
+
// This runs on the SERVER (Node.js), not in the browser!
|
|
2
3
|
|
|
3
|
-
//
|
|
4
|
-
// This simulates a backend API endpoint
|
|
5
|
-
|
|
6
|
-
// Mock database
|
|
4
|
+
// Mock database (in real app, use a real database)
|
|
7
5
|
const users = [
|
|
8
6
|
{ id: 1, name: 'Aziz Ahmed', email: 'aziz@example.com' },
|
|
9
7
|
{ id: 2, name: 'John Doe', email: 'john@example.com' },
|
|
10
8
|
{ id: 3, name: 'Jane Smith', email: 'jane@example.com' }
|
|
11
9
|
];
|
|
12
10
|
|
|
13
|
-
//
|
|
14
|
-
|
|
11
|
+
// Export the handler function (like Next.js)
|
|
12
|
+
module.exports = async function handler(req, res) {
|
|
15
13
|
// Handle different HTTP methods
|
|
16
14
|
switch (req.method) {
|
|
17
15
|
case 'GET':
|
|
@@ -59,4 +57,4 @@ registerApiRoute('users', async (req, res) => {
|
|
|
59
57
|
default:
|
|
60
58
|
return res.status(405).json({ error: 'Method not allowed' });
|
|
61
59
|
}
|
|
62
|
-
}
|
|
60
|
+
};
|
package/template/app.html
CHANGED
|
@@ -28,31 +28,27 @@
|
|
|
28
28
|
<!-- Footer is always visible -->
|
|
29
29
|
<div id="footer"></div>
|
|
30
30
|
|
|
31
|
-
<!-- Load core modules -->
|
|
32
|
-
<script type="text/babel" src="modules/router.js"></script>
|
|
33
|
-
<script type="text/babel" src="modules/state.js"></script>
|
|
34
|
-
<script type="text/babel" src="modules/api.js"></script>
|
|
35
|
-
|
|
36
|
-
<!-- Load
|
|
37
|
-
<script type="text/babel" src="
|
|
38
|
-
<script type="text/babel" src="
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
<script type="text/babel" src="
|
|
43
|
-
<script type="text/babel" src="
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
<script type="text/babel" src="pages/
|
|
47
|
-
<script type="text/babel" src="pages/
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
<script type="text/babel"
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
<!-- Start the app -->
|
|
54
|
-
<script type="text/babel">
|
|
55
|
-
startApp();
|
|
31
|
+
<!-- Load core modules -->
|
|
32
|
+
<script type="text/babel" src="modules/router.js"></script>
|
|
33
|
+
<script type="text/babel" src="modules/state.js"></script>
|
|
34
|
+
<script type="text/babel" src="modules/api.js"></script>
|
|
35
|
+
|
|
36
|
+
<!-- Load shared components -->
|
|
37
|
+
<script type="text/babel" src="components/Counter.jsx"></script>
|
|
38
|
+
<script type="text/babel" src="components/Footer.jsx"></script>
|
|
39
|
+
<script type="text/babel" src="components/Navbar.jsx"></script>
|
|
40
|
+
|
|
41
|
+
<!-- Load pages -->
|
|
42
|
+
<script type="text/babel" src="pages/About.jsx"></script>
|
|
43
|
+
<script type="text/babel" src="pages/ApiDemo.jsx"></script>
|
|
44
|
+
<script type="text/babel" src="pages/Contact.jsx"></script>
|
|
45
|
+
<script type="text/babel" src="pages/Example.jsx"></script>
|
|
46
|
+
<script type="text/babel" src="pages/Home.jsx"></script>
|
|
47
|
+
<script type="text/babel" src="pages/test.jsx"></script>
|
|
48
|
+
|
|
49
|
+
<!-- Start the app -->
|
|
50
|
+
<script type="text/babel">
|
|
51
|
+
startApp();
|
|
56
52
|
</script>
|
|
57
53
|
|
|
58
54
|
</body>
|
|
@@ -0,0 +1,538 @@
|
|
|
1
|
+
# API Routes Guide
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
API routes run on the **SERVER** (Node.js), just like Next.js! They're real backend endpoints, not client-side mocks.
|
|
6
|
+
|
|
7
|
+
## How It Works
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
Browser Dev Server (Node.js)
|
|
11
|
+
ā ā
|
|
12
|
+
ā GET /api/users ā
|
|
13
|
+
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāŗā
|
|
14
|
+
ā ā Loads api/users.js
|
|
15
|
+
ā ā Executes handler()
|
|
16
|
+
ā ā
|
|
17
|
+
ā { users: [...] } ā
|
|
18
|
+
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā¤
|
|
19
|
+
ā ā
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Creating an API Route
|
|
23
|
+
|
|
24
|
+
### File Structure
|
|
25
|
+
|
|
26
|
+
```
|
|
27
|
+
api/
|
|
28
|
+
āāā hello.js ā /api/hello
|
|
29
|
+
āāā users.js ā /api/users
|
|
30
|
+
āāā posts.js ā /api/posts
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Each file in `api/` becomes an endpoint!
|
|
34
|
+
|
|
35
|
+
### Basic Example
|
|
36
|
+
|
|
37
|
+
```javascript
|
|
38
|
+
// api/hello.js
|
|
39
|
+
|
|
40
|
+
module.exports = async function handler(req, res) {
|
|
41
|
+
return res.status(200).json({
|
|
42
|
+
message: 'Hello from the server!'
|
|
43
|
+
});
|
|
44
|
+
};
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### Request Object
|
|
48
|
+
|
|
49
|
+
The `req` object contains:
|
|
50
|
+
|
|
51
|
+
```javascript
|
|
52
|
+
{
|
|
53
|
+
method: 'GET' | 'POST' | 'PUT' | 'DELETE',
|
|
54
|
+
body: {}, // Parsed JSON body
|
|
55
|
+
query: {}, // URL query parameters
|
|
56
|
+
headers: {}, // Request headers
|
|
57
|
+
url: '/api/users?id=1'
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### Response Object
|
|
62
|
+
|
|
63
|
+
The `res` object has these methods:
|
|
64
|
+
|
|
65
|
+
```javascript
|
|
66
|
+
// Set status code
|
|
67
|
+
res.status(200)
|
|
68
|
+
|
|
69
|
+
// Send JSON response
|
|
70
|
+
res.json({ data: 'value' })
|
|
71
|
+
|
|
72
|
+
// Send text response
|
|
73
|
+
res.send('Hello')
|
|
74
|
+
|
|
75
|
+
// Set custom header
|
|
76
|
+
res.setHeader('X-Custom', 'value')
|
|
77
|
+
|
|
78
|
+
// Chain methods
|
|
79
|
+
res.status(201).json({ created: true })
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Examples
|
|
83
|
+
|
|
84
|
+
### GET Request
|
|
85
|
+
|
|
86
|
+
```javascript
|
|
87
|
+
// api/users.js
|
|
88
|
+
|
|
89
|
+
const users = [
|
|
90
|
+
{ id: 1, name: 'Aziz' },
|
|
91
|
+
{ id: 2, name: 'John' }
|
|
92
|
+
];
|
|
93
|
+
|
|
94
|
+
module.exports = async function handler(req, res) {
|
|
95
|
+
if (req.method === 'GET') {
|
|
96
|
+
// Get specific user
|
|
97
|
+
if (req.query.id) {
|
|
98
|
+
const user = users.find(u => u.id === parseInt(req.query.id));
|
|
99
|
+
if (user) {
|
|
100
|
+
return res.status(200).json(user);
|
|
101
|
+
}
|
|
102
|
+
return res.status(404).json({ error: 'User not found' });
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Get all users
|
|
106
|
+
return res.status(200).json(users);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return res.status(405).json({ error: 'Method not allowed' });
|
|
110
|
+
};
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
**Usage:**
|
|
114
|
+
```javascript
|
|
115
|
+
// Get all users
|
|
116
|
+
const response = await fetchApi('/api/users');
|
|
117
|
+
const users = await response.json();
|
|
118
|
+
|
|
119
|
+
// Get specific user
|
|
120
|
+
const response = await fetchApi('/api/users?id=1');
|
|
121
|
+
const user = await response.json();
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### POST Request
|
|
125
|
+
|
|
126
|
+
```javascript
|
|
127
|
+
// api/users.js
|
|
128
|
+
|
|
129
|
+
module.exports = async function handler(req, res) {
|
|
130
|
+
if (req.method === 'POST') {
|
|
131
|
+
const { name, email } = req.body;
|
|
132
|
+
|
|
133
|
+
// Validate
|
|
134
|
+
if (!name || !email) {
|
|
135
|
+
return res.status(400).json({
|
|
136
|
+
error: 'Name and email are required'
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Create user
|
|
141
|
+
const newUser = {
|
|
142
|
+
id: Date.now(),
|
|
143
|
+
name,
|
|
144
|
+
email
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
return res.status(201).json(newUser);
|
|
148
|
+
}
|
|
149
|
+
};
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
**Usage:**
|
|
153
|
+
```javascript
|
|
154
|
+
const response = await fetchApi('/api/users', {
|
|
155
|
+
method: 'POST',
|
|
156
|
+
body: JSON.stringify({
|
|
157
|
+
name: 'Aziz',
|
|
158
|
+
email: 'aziz@example.com'
|
|
159
|
+
})
|
|
160
|
+
});
|
|
161
|
+
const user = await response.json();
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
### PUT Request
|
|
165
|
+
|
|
166
|
+
```javascript
|
|
167
|
+
// api/users.js
|
|
168
|
+
|
|
169
|
+
module.exports = async function handler(req, res) {
|
|
170
|
+
if (req.method === 'PUT') {
|
|
171
|
+
const userId = req.query.id;
|
|
172
|
+
const updates = req.body;
|
|
173
|
+
|
|
174
|
+
// Update user logic here
|
|
175
|
+
|
|
176
|
+
return res.status(200).json({
|
|
177
|
+
message: 'User updated',
|
|
178
|
+
user: updatedUser
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
};
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
**Usage:**
|
|
185
|
+
```javascript
|
|
186
|
+
const response = await fetchApi('/api/users?id=1', {
|
|
187
|
+
method: 'PUT',
|
|
188
|
+
body: JSON.stringify({
|
|
189
|
+
name: 'New Name'
|
|
190
|
+
})
|
|
191
|
+
});
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
### DELETE Request
|
|
195
|
+
|
|
196
|
+
```javascript
|
|
197
|
+
// api/users.js
|
|
198
|
+
|
|
199
|
+
module.exports = async function handler(req, res) {
|
|
200
|
+
if (req.method === 'DELETE') {
|
|
201
|
+
const userId = req.query.id;
|
|
202
|
+
|
|
203
|
+
// Delete user logic here
|
|
204
|
+
|
|
205
|
+
return res.status(200).json({
|
|
206
|
+
message: 'User deleted'
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
};
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
**Usage:**
|
|
213
|
+
```javascript
|
|
214
|
+
const response = await fetchApi('/api/users?id=1', {
|
|
215
|
+
method: 'DELETE'
|
|
216
|
+
});
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
### Multiple Methods
|
|
220
|
+
|
|
221
|
+
```javascript
|
|
222
|
+
// api/users.js
|
|
223
|
+
|
|
224
|
+
module.exports = async function handler(req, res) {
|
|
225
|
+
switch (req.method) {
|
|
226
|
+
case 'GET':
|
|
227
|
+
// Handle GET
|
|
228
|
+
return res.status(200).json({ method: 'GET' });
|
|
229
|
+
|
|
230
|
+
case 'POST':
|
|
231
|
+
// Handle POST
|
|
232
|
+
return res.status(201).json({ method: 'POST' });
|
|
233
|
+
|
|
234
|
+
case 'PUT':
|
|
235
|
+
// Handle PUT
|
|
236
|
+
return res.status(200).json({ method: 'PUT' });
|
|
237
|
+
|
|
238
|
+
case 'DELETE':
|
|
239
|
+
// Handle DELETE
|
|
240
|
+
return res.status(200).json({ method: 'DELETE' });
|
|
241
|
+
|
|
242
|
+
default:
|
|
243
|
+
return res.status(405).json({
|
|
244
|
+
error: 'Method not allowed'
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
};
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
## Using in Components
|
|
251
|
+
|
|
252
|
+
### Basic Fetch
|
|
253
|
+
|
|
254
|
+
```jsx
|
|
255
|
+
function MyComponent() {
|
|
256
|
+
const [data, setData] = useState(null);
|
|
257
|
+
const [loading, setLoading] = useState(false);
|
|
258
|
+
|
|
259
|
+
const loadData = async () => {
|
|
260
|
+
setLoading(true);
|
|
261
|
+
try {
|
|
262
|
+
const response = await fetchApi('/api/users');
|
|
263
|
+
const users = await response.json();
|
|
264
|
+
setData(users);
|
|
265
|
+
} catch (error) {
|
|
266
|
+
console.error('Error:', error);
|
|
267
|
+
}
|
|
268
|
+
setLoading(false);
|
|
269
|
+
};
|
|
270
|
+
|
|
271
|
+
return (
|
|
272
|
+
<div>
|
|
273
|
+
<button onClick={loadData}>Load Users</button>
|
|
274
|
+
{loading && <p>Loading...</p>}
|
|
275
|
+
{data && <pre>{JSON.stringify(data, null, 2)}</pre>}
|
|
276
|
+
</div>
|
|
277
|
+
);
|
|
278
|
+
}
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
### With Error Handling
|
|
282
|
+
|
|
283
|
+
```jsx
|
|
284
|
+
const [error, setError] = useState(null);
|
|
285
|
+
|
|
286
|
+
const loadData = async () => {
|
|
287
|
+
try {
|
|
288
|
+
const response = await fetchApi('/api/users');
|
|
289
|
+
|
|
290
|
+
if (!response.ok) {
|
|
291
|
+
throw new Error('Failed to fetch');
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
const users = await response.json();
|
|
295
|
+
setData(users);
|
|
296
|
+
setError(null);
|
|
297
|
+
} catch (err) {
|
|
298
|
+
setError(err.message);
|
|
299
|
+
setData(null);
|
|
300
|
+
}
|
|
301
|
+
};
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
## Database Integration
|
|
305
|
+
|
|
306
|
+
### Using a Real Database
|
|
307
|
+
|
|
308
|
+
```javascript
|
|
309
|
+
// api/users.js
|
|
310
|
+
|
|
311
|
+
// Example with a database (you need to install the package)
|
|
312
|
+
// npm install better-sqlite3
|
|
313
|
+
|
|
314
|
+
const Database = require('better-sqlite3');
|
|
315
|
+
const db = new Database('mydb.sqlite');
|
|
316
|
+
|
|
317
|
+
module.exports = async function handler(req, res) {
|
|
318
|
+
if (req.method === 'GET') {
|
|
319
|
+
const users = db.prepare('SELECT * FROM users').all();
|
|
320
|
+
return res.status(200).json(users);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
if (req.method === 'POST') {
|
|
324
|
+
const { name, email } = req.body;
|
|
325
|
+
const result = db.prepare(
|
|
326
|
+
'INSERT INTO users (name, email) VALUES (?, ?)'
|
|
327
|
+
).run(name, email);
|
|
328
|
+
|
|
329
|
+
return res.status(201).json({
|
|
330
|
+
id: result.lastInsertRowid,
|
|
331
|
+
name,
|
|
332
|
+
email
|
|
333
|
+
});
|
|
334
|
+
}
|
|
335
|
+
};
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
### Using External APIs
|
|
339
|
+
|
|
340
|
+
```javascript
|
|
341
|
+
// api/weather.js
|
|
342
|
+
|
|
343
|
+
module.exports = async function handler(req, res) {
|
|
344
|
+
const city = req.query.city || 'London';
|
|
345
|
+
|
|
346
|
+
try {
|
|
347
|
+
const response = await fetch(
|
|
348
|
+
`https://api.openweathermap.org/data/2.5/weather?q=${city}&appid=YOUR_KEY`
|
|
349
|
+
);
|
|
350
|
+
const data = await response.json();
|
|
351
|
+
|
|
352
|
+
return res.status(200).json(data);
|
|
353
|
+
} catch (error) {
|
|
354
|
+
return res.status(500).json({
|
|
355
|
+
error: 'Failed to fetch weather'
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
};
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
## Environment Variables
|
|
362
|
+
|
|
363
|
+
```javascript
|
|
364
|
+
// api/config.js
|
|
365
|
+
|
|
366
|
+
module.exports = async function handler(req, res) {
|
|
367
|
+
// Access environment variables
|
|
368
|
+
const apiKey = process.env.API_KEY;
|
|
369
|
+
const dbUrl = process.env.DATABASE_URL;
|
|
370
|
+
|
|
371
|
+
return res.status(200).json({
|
|
372
|
+
hasApiKey: !!apiKey,
|
|
373
|
+
hasDbUrl: !!dbUrl
|
|
374
|
+
});
|
|
375
|
+
};
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
Set environment variables:
|
|
379
|
+
```bash
|
|
380
|
+
# Windows
|
|
381
|
+
set API_KEY=your_key
|
|
382
|
+
npm run dev
|
|
383
|
+
|
|
384
|
+
# Mac/Linux
|
|
385
|
+
API_KEY=your_key npm run dev
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
## Hot Reload
|
|
389
|
+
|
|
390
|
+
API routes support hot reload! When you save an API file:
|
|
391
|
+
|
|
392
|
+
1. Dev server detects the change
|
|
393
|
+
2. Reloads the API handler
|
|
394
|
+
3. Next request uses the new code
|
|
395
|
+
4. No server restart needed!
|
|
396
|
+
|
|
397
|
+
## CORS
|
|
398
|
+
|
|
399
|
+
CORS is automatically enabled for all API routes:
|
|
400
|
+
```
|
|
401
|
+
Access-Control-Allow-Origin: *
|
|
402
|
+
Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS
|
|
403
|
+
Access-Control-Allow-Headers: Content-Type
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
## Error Handling
|
|
407
|
+
|
|
408
|
+
### Server Errors
|
|
409
|
+
|
|
410
|
+
```javascript
|
|
411
|
+
module.exports = async function handler(req, res) {
|
|
412
|
+
try {
|
|
413
|
+
// Your code here
|
|
414
|
+
const data = await someAsyncOperation();
|
|
415
|
+
return res.status(200).json(data);
|
|
416
|
+
} catch (error) {
|
|
417
|
+
console.error('API Error:', error);
|
|
418
|
+
return res.status(500).json({
|
|
419
|
+
error: 'Internal server error',
|
|
420
|
+
message: error.message
|
|
421
|
+
});
|
|
422
|
+
}
|
|
423
|
+
};
|
|
424
|
+
```
|
|
425
|
+
|
|
426
|
+
### Validation Errors
|
|
427
|
+
|
|
428
|
+
```javascript
|
|
429
|
+
module.exports = async function handler(req, res) {
|
|
430
|
+
if (req.method === 'POST') {
|
|
431
|
+
const { name, email } = req.body;
|
|
432
|
+
|
|
433
|
+
if (!name) {
|
|
434
|
+
return res.status(400).json({
|
|
435
|
+
error: 'Name is required'
|
|
436
|
+
});
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
if (!email || !email.includes('@')) {
|
|
440
|
+
return res.status(400).json({
|
|
441
|
+
error: 'Valid email is required'
|
|
442
|
+
});
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
// Process valid data
|
|
446
|
+
}
|
|
447
|
+
};
|
|
448
|
+
```
|
|
449
|
+
|
|
450
|
+
## Status Codes
|
|
451
|
+
|
|
452
|
+
Common HTTP status codes:
|
|
453
|
+
|
|
454
|
+
- `200` - OK (successful GET, PUT, DELETE)
|
|
455
|
+
- `201` - Created (successful POST)
|
|
456
|
+
- `400` - Bad Request (validation error)
|
|
457
|
+
- `401` - Unauthorized (authentication required)
|
|
458
|
+
- `403` - Forbidden (no permission)
|
|
459
|
+
- `404` - Not Found (resource doesn't exist)
|
|
460
|
+
- `405` - Method Not Allowed (wrong HTTP method)
|
|
461
|
+
- `500` - Internal Server Error (server error)
|
|
462
|
+
|
|
463
|
+
## Best Practices
|
|
464
|
+
|
|
465
|
+
### 1. Always Handle Methods
|
|
466
|
+
|
|
467
|
+
```javascript
|
|
468
|
+
module.exports = async function handler(req, res) {
|
|
469
|
+
if (req.method === 'GET') {
|
|
470
|
+
// Handle GET
|
|
471
|
+
} else {
|
|
472
|
+
return res.status(405).json({
|
|
473
|
+
error: 'Method not allowed'
|
|
474
|
+
});
|
|
475
|
+
}
|
|
476
|
+
};
|
|
477
|
+
```
|
|
478
|
+
|
|
479
|
+
### 2. Validate Input
|
|
480
|
+
|
|
481
|
+
```javascript
|
|
482
|
+
if (!req.body.name || typeof req.body.name !== 'string') {
|
|
483
|
+
return res.status(400).json({ error: 'Invalid name' });
|
|
484
|
+
}
|
|
485
|
+
```
|
|
486
|
+
|
|
487
|
+
### 3. Use Try-Catch
|
|
488
|
+
|
|
489
|
+
```javascript
|
|
490
|
+
try {
|
|
491
|
+
const data = await database.query();
|
|
492
|
+
return res.status(200).json(data);
|
|
493
|
+
} catch (error) {
|
|
494
|
+
return res.status(500).json({ error: error.message });
|
|
495
|
+
}
|
|
496
|
+
```
|
|
497
|
+
|
|
498
|
+
### 4. Return Early
|
|
499
|
+
|
|
500
|
+
```javascript
|
|
501
|
+
if (!user) {
|
|
502
|
+
return res.status(404).json({ error: 'Not found' });
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
// Continue with logic
|
|
506
|
+
```
|
|
507
|
+
|
|
508
|
+
### 5. Use Async/Await
|
|
509
|
+
|
|
510
|
+
```javascript
|
|
511
|
+
module.exports = async function handler(req, res) {
|
|
512
|
+
const data = await fetchData();
|
|
513
|
+
return res.status(200).json(data);
|
|
514
|
+
};
|
|
515
|
+
```
|
|
516
|
+
|
|
517
|
+
## Comparison with Next.js
|
|
518
|
+
|
|
519
|
+
| Feature | Aziz App | Next.js |
|
|
520
|
+
|---------|----------|---------|
|
|
521
|
+
| File-based routing | ā
| ā
|
|
|
522
|
+
| Server-side execution | ā
| ā
|
|
|
523
|
+
| Hot reload | ā
| ā
|
|
|
524
|
+
| Request/Response API | ā
| ā
|
|
|
525
|
+
| Dynamic routes | ā | ā
|
|
|
526
|
+
| Middleware | ā | ā
|
|
|
527
|
+
| Edge runtime | ā | ā
|
|
|
528
|
+
|
|
529
|
+
## Summary
|
|
530
|
+
|
|
531
|
+
- API routes run on the **server** (Node.js)
|
|
532
|
+
- Each file in `api/` becomes an endpoint
|
|
533
|
+
- Use `module.exports` to export the handler
|
|
534
|
+
- Access via `fetchApi('/api/routename')`
|
|
535
|
+
- Supports GET, POST, PUT, DELETE
|
|
536
|
+
- Hot reload enabled
|
|
537
|
+
- CORS enabled by default
|
|
538
|
+
- Just like Next.js API routes!
|
package/template/modules/api.js
CHANGED
|
@@ -1,96 +1,12 @@
|
|
|
1
1
|
/** @jsx h */
|
|
2
2
|
|
|
3
3
|
// ============================================================
|
|
4
|
-
// API
|
|
5
|
-
//
|
|
6
|
-
//
|
|
4
|
+
// API CLIENT
|
|
5
|
+
// Fetch-like API for making requests to backend API routes
|
|
6
|
+
// API routes run on the SERVER (Node.js), not in the browser
|
|
7
7
|
// ============================================================
|
|
8
8
|
|
|
9
|
-
// API
|
|
10
|
-
const apiRoutes = {};
|
|
11
|
-
|
|
12
|
-
// Register an API route
|
|
13
|
-
function registerApiRoute(path, handler) {
|
|
14
|
-
apiRoutes[path] = handler;
|
|
15
|
-
console.log(`Registered API route: /api/${path}`);
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
// API request/response helpers
|
|
19
|
-
class ApiRequest {
|
|
20
|
-
constructor(method, body, query, params) {
|
|
21
|
-
this.method = method;
|
|
22
|
-
this.body = body;
|
|
23
|
-
this.query = query;
|
|
24
|
-
this.params = params;
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
class ApiResponse {
|
|
29
|
-
constructor() {
|
|
30
|
-
this.statusCode = 200;
|
|
31
|
-
this.headers = {};
|
|
32
|
-
this.data = null;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
status(code) {
|
|
36
|
-
this.statusCode = code;
|
|
37
|
-
return this;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
json(data) {
|
|
41
|
-
this.data = data;
|
|
42
|
-
this.headers['Content-Type'] = 'application/json';
|
|
43
|
-
return this;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
send(data) {
|
|
47
|
-
this.data = data;
|
|
48
|
-
return this;
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
// Execute an API route
|
|
53
|
-
async function callApi(path, options = {}) {
|
|
54
|
-
const {
|
|
55
|
-
method = 'GET',
|
|
56
|
-
body = null,
|
|
57
|
-
query = {},
|
|
58
|
-
params = {}
|
|
59
|
-
} = options;
|
|
60
|
-
|
|
61
|
-
// Find the route handler
|
|
62
|
-
const handler = apiRoutes[path];
|
|
63
|
-
|
|
64
|
-
if (!handler) {
|
|
65
|
-
return {
|
|
66
|
-
status: 404,
|
|
67
|
-
data: { error: 'API route not found' }
|
|
68
|
-
};
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
// Create request and response objects
|
|
72
|
-
const req = new ApiRequest(method, body, query, params);
|
|
73
|
-
const res = new ApiResponse();
|
|
74
|
-
|
|
75
|
-
try {
|
|
76
|
-
// Call the handler
|
|
77
|
-
await handler(req, res);
|
|
78
|
-
|
|
79
|
-
return {
|
|
80
|
-
status: res.statusCode,
|
|
81
|
-
data: res.data,
|
|
82
|
-
headers: res.headers
|
|
83
|
-
};
|
|
84
|
-
} catch (error) {
|
|
85
|
-
console.error(`API Error on ${path}:`, error);
|
|
86
|
-
return {
|
|
87
|
-
status: 500,
|
|
88
|
-
data: { error: error.message }
|
|
89
|
-
};
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
// Fetch-like API for making requests
|
|
9
|
+
// Fetch-like API for making requests to backend
|
|
94
10
|
async function fetchApi(path, options = {}) {
|
|
95
11
|
const {
|
|
96
12
|
method = 'GET',
|
|
@@ -98,51 +14,31 @@ async function fetchApi(path, options = {}) {
|
|
|
98
14
|
headers = {}
|
|
99
15
|
} = options;
|
|
100
16
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
const [key, value] = param.split('=');
|
|
107
|
-
query[decodeURIComponent(key)] = decodeURIComponent(value);
|
|
108
|
-
});
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
// Parse body if it's JSON
|
|
112
|
-
let parsedBody = body;
|
|
113
|
-
if (body && typeof body === 'string') {
|
|
114
|
-
try {
|
|
115
|
-
parsedBody = JSON.parse(body);
|
|
116
|
-
} catch (e) {
|
|
117
|
-
parsedBody = body;
|
|
17
|
+
const fetchOptions = {
|
|
18
|
+
method,
|
|
19
|
+
headers: {
|
|
20
|
+
'Content-Type': 'application/json',
|
|
21
|
+
...headers
|
|
118
22
|
}
|
|
119
|
-
}
|
|
23
|
+
};
|
|
120
24
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
query
|
|
125
|
-
});
|
|
25
|
+
if (body) {
|
|
26
|
+
fetchOptions.body = typeof body === 'string' ? body : JSON.stringify(body);
|
|
27
|
+
}
|
|
126
28
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
text: async () => JSON.stringify(result.data)
|
|
135
|
-
};
|
|
29
|
+
try {
|
|
30
|
+
const response = await fetch(path, fetchOptions);
|
|
31
|
+
return response;
|
|
32
|
+
} catch (error) {
|
|
33
|
+
console.error('API Error:', error);
|
|
34
|
+
throw error;
|
|
35
|
+
}
|
|
136
36
|
}
|
|
137
37
|
|
|
138
38
|
// Export to global scope
|
|
139
|
-
window.registerApiRoute = registerApiRoute;
|
|
140
39
|
window.fetchApi = fetchApi;
|
|
141
|
-
window.callApi = callApi;
|
|
142
40
|
|
|
143
41
|
// Also make available without window prefix
|
|
144
42
|
if (typeof globalThis !== 'undefined') {
|
|
145
|
-
globalThis.registerApiRoute = registerApiRoute;
|
|
146
43
|
globalThis.fetchApi = fetchApi;
|
|
147
|
-
globalThis.callApi = callApi;
|
|
148
44
|
}
|
|
@@ -7,9 +7,152 @@ const { exec } = require('child_process');
|
|
|
7
7
|
|
|
8
8
|
const PORT = process.env.PORT || 3000;
|
|
9
9
|
const clients = new Set();
|
|
10
|
+
const apiHandlers = {};
|
|
10
11
|
|
|
11
12
|
console.log('š Dev server starting...\n');
|
|
12
13
|
|
|
14
|
+
// Load API routes from api/ folder
|
|
15
|
+
function loadApiRoutes() {
|
|
16
|
+
const apiDir = path.join(process.cwd(), 'api');
|
|
17
|
+
if (!fs.existsSync(apiDir)) {
|
|
18
|
+
console.log('ā ļø No api/ folder found\n');
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const files = fs.readdirSync(apiDir).filter(f => f.endsWith('.js'));
|
|
23
|
+
console.log(`š” Loading ${files.length} API routes...\n`);
|
|
24
|
+
|
|
25
|
+
files.forEach(file => {
|
|
26
|
+
const routeName = path.basename(file, '.js');
|
|
27
|
+
const filePath = path.join(apiDir, file);
|
|
28
|
+
|
|
29
|
+
try {
|
|
30
|
+
// Clear require cache to allow hot reload
|
|
31
|
+
delete require.cache[require.resolve(filePath)];
|
|
32
|
+
|
|
33
|
+
// Load the API handler
|
|
34
|
+
const handler = require(filePath);
|
|
35
|
+
|
|
36
|
+
if (typeof handler === 'function') {
|
|
37
|
+
apiHandlers[routeName] = handler;
|
|
38
|
+
console.log(` ā
/api/${routeName}`);
|
|
39
|
+
} else if (handler.default && typeof handler.default === 'function') {
|
|
40
|
+
apiHandlers[routeName] = handler.default;
|
|
41
|
+
console.log(` ā
/api/${routeName}`);
|
|
42
|
+
} else {
|
|
43
|
+
console.log(` ā ļø /api/${routeName} - No default export`);
|
|
44
|
+
}
|
|
45
|
+
} catch (error) {
|
|
46
|
+
console.error(` ā /api/${routeName} - ${error.message}`);
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
console.log('');
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Parse request body
|
|
54
|
+
function parseBody(req) {
|
|
55
|
+
return new Promise((resolve, reject) => {
|
|
56
|
+
let body = '';
|
|
57
|
+
req.on('data', chunk => {
|
|
58
|
+
body += chunk.toString();
|
|
59
|
+
});
|
|
60
|
+
req.on('end', () => {
|
|
61
|
+
try {
|
|
62
|
+
if (body && req.headers['content-type']?.includes('application/json')) {
|
|
63
|
+
resolve(JSON.parse(body));
|
|
64
|
+
} else {
|
|
65
|
+
resolve(body);
|
|
66
|
+
}
|
|
67
|
+
} catch (e) {
|
|
68
|
+
resolve(body);
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
req.on('error', reject);
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Parse query string
|
|
76
|
+
function parseQuery(url) {
|
|
77
|
+
const query = {};
|
|
78
|
+
const queryString = url.split('?')[1];
|
|
79
|
+
if (queryString) {
|
|
80
|
+
queryString.split('&').forEach(param => {
|
|
81
|
+
const [key, value] = param.split('=');
|
|
82
|
+
query[decodeURIComponent(key)] = decodeURIComponent(value || '');
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
return query;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Handle API requests
|
|
89
|
+
async function handleApiRequest(req, res) {
|
|
90
|
+
const urlPath = req.url.split('?')[0];
|
|
91
|
+
const routeName = urlPath.replace('/api/', '');
|
|
92
|
+
|
|
93
|
+
const handler = apiHandlers[routeName];
|
|
94
|
+
|
|
95
|
+
if (!handler) {
|
|
96
|
+
res.writeHead(404, { 'Content-Type': 'application/json' });
|
|
97
|
+
res.end(JSON.stringify({ error: 'API route not found' }));
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
try {
|
|
102
|
+
// Parse request
|
|
103
|
+
const body = await parseBody(req);
|
|
104
|
+
const query = parseQuery(req.url);
|
|
105
|
+
|
|
106
|
+
// Create Next.js-like req/res objects
|
|
107
|
+
const apiReq = {
|
|
108
|
+
method: req.method,
|
|
109
|
+
body: body,
|
|
110
|
+
query: query,
|
|
111
|
+
headers: req.headers,
|
|
112
|
+
url: req.url
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
const apiRes = {
|
|
116
|
+
statusCode: 200,
|
|
117
|
+
headers: {},
|
|
118
|
+
status(code) {
|
|
119
|
+
this.statusCode = code;
|
|
120
|
+
return this;
|
|
121
|
+
},
|
|
122
|
+
json(data) {
|
|
123
|
+
res.writeHead(this.statusCode, {
|
|
124
|
+
'Content-Type': 'application/json',
|
|
125
|
+
'Access-Control-Allow-Origin': '*',
|
|
126
|
+
...this.headers
|
|
127
|
+
});
|
|
128
|
+
res.end(JSON.stringify(data));
|
|
129
|
+
return this;
|
|
130
|
+
},
|
|
131
|
+
send(data) {
|
|
132
|
+
res.writeHead(this.statusCode, {
|
|
133
|
+
'Content-Type': 'text/plain',
|
|
134
|
+
'Access-Control-Allow-Origin': '*',
|
|
135
|
+
...this.headers
|
|
136
|
+
});
|
|
137
|
+
res.end(data);
|
|
138
|
+
return this;
|
|
139
|
+
},
|
|
140
|
+
setHeader(key, value) {
|
|
141
|
+
this.headers[key] = value;
|
|
142
|
+
return this;
|
|
143
|
+
}
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
// Call the handler
|
|
147
|
+
await handler(apiReq, apiRes);
|
|
148
|
+
|
|
149
|
+
} catch (error) {
|
|
150
|
+
console.error(`ā API Error on /api/${routeName}:`, error);
|
|
151
|
+
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
152
|
+
res.end(JSON.stringify({ error: error.message }));
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
13
156
|
// Function to generate imports
|
|
14
157
|
function generateImports() {
|
|
15
158
|
console.log('š Regenerating imports...');
|
|
@@ -38,7 +181,24 @@ function notifyClients() {
|
|
|
38
181
|
}
|
|
39
182
|
|
|
40
183
|
// Create HTTP server
|
|
41
|
-
const server = http.createServer((req, res) => {
|
|
184
|
+
const server = http.createServer(async (req, res) => {
|
|
185
|
+
// Handle API routes
|
|
186
|
+
if (req.url.startsWith('/api/')) {
|
|
187
|
+
await handleApiRequest(req, res);
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Handle CORS preflight
|
|
192
|
+
if (req.method === 'OPTIONS') {
|
|
193
|
+
res.writeHead(200, {
|
|
194
|
+
'Access-Control-Allow-Origin': '*',
|
|
195
|
+
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
|
|
196
|
+
'Access-Control-Allow-Headers': 'Content-Type'
|
|
197
|
+
});
|
|
198
|
+
res.end();
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
|
|
42
202
|
// Strip query parameters for file serving (cache busting)
|
|
43
203
|
let filePath = '.' + req.url.split('?')[0];
|
|
44
204
|
if (filePath === './') {
|
|
@@ -170,6 +330,12 @@ watchDirs.forEach(dir => {
|
|
|
170
330
|
clearTimeout(reloadTimeout);
|
|
171
331
|
}
|
|
172
332
|
|
|
333
|
+
// Reload API routes if api/ folder changed
|
|
334
|
+
if (dir === 'api' && filename.endsWith('.js')) {
|
|
335
|
+
console.log('š Reloading API routes...');
|
|
336
|
+
loadApiRoutes();
|
|
337
|
+
}
|
|
338
|
+
|
|
173
339
|
// For CSS changes, just reload without regenerating imports
|
|
174
340
|
if (filename.endsWith('.css')) {
|
|
175
341
|
reloadTimeout = setTimeout(() => {
|
|
@@ -190,6 +356,9 @@ console.log('\nā
Dev server running with live reload!');
|
|
|
190
356
|
console.log('š” Create/modify files and browser will auto-refresh');
|
|
191
357
|
console.log('ā¹ļø Press Ctrl+C to stop\n');
|
|
192
358
|
|
|
359
|
+
// Load API routes
|
|
360
|
+
loadApiRoutes();
|
|
361
|
+
|
|
193
362
|
// Start server
|
|
194
363
|
startServer(PORT);
|
|
195
364
|
|
|
@@ -31,11 +31,9 @@ function scanDirectory(dir, extensions = []) {
|
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
// Scan for files
|
|
34
|
-
const apiRoutes = scanDirectory('api', ['.js']);
|
|
35
34
|
const components = scanDirectory('components', ['.jsx', '.js']);
|
|
36
35
|
const pages = scanDirectory('pages', ['.jsx', '.js']);
|
|
37
36
|
|
|
38
|
-
console.log(`š” Found ${apiRoutes.length} API routes`);
|
|
39
37
|
console.log(`š§© Found ${components.length} components`);
|
|
40
38
|
console.log(`š Found ${pages.length} pages\n`);
|
|
41
39
|
|
|
@@ -48,14 +46,6 @@ scriptTags += ' <script type="text/babel" src="modules/router.js"></script>\n
|
|
|
48
46
|
scriptTags += ' <script type="text/babel" src="modules/state.js"></script>\n';
|
|
49
47
|
scriptTags += ' <script type="text/babel" src="modules/api.js"></script>\n\n';
|
|
50
48
|
|
|
51
|
-
if (apiRoutes.length > 0) {
|
|
52
|
-
scriptTags += ' <!-- Load API routes -->\n';
|
|
53
|
-
apiRoutes.forEach(route => {
|
|
54
|
-
scriptTags += ` <script type="text/babel" src="${route}"></script>\n`;
|
|
55
|
-
});
|
|
56
|
-
scriptTags += '\n';
|
|
57
|
-
}
|
|
58
|
-
|
|
59
49
|
if (components.length > 0) {
|
|
60
50
|
scriptTags += ' <!-- Load shared components -->\n';
|
|
61
51
|
components.forEach(component => {
|