create-aziz-app 2.0.14 → 3.0.0
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 +227 -0
- package/bin/create-aziz-app.js +9 -9
- package/package.json +10 -11
- package/template/README.md +862 -138
- package/template/_framework/boot.js +36 -0
- package/template/_framework/hooks.js +207 -0
- package/template/{modules → _framework}/next-response.js +5 -13
- package/template/_framework/router.js +279 -0
- package/template/_framework/runtime.js +86 -0
- package/template/api/createUser.js +1 -1
- package/template/api/hello.js +8 -17
- package/template/api/loginUser.js +1 -1
- package/template/api/users.js +38 -134
- package/template/app/about/page.js +45 -0
- package/template/app/layout.js +23 -0
- package/template/app/page.js +54 -0
- package/template/app/test/page.js +55 -0
- package/template/components/Footer.js +10 -0
- package/template/components/Navbar.js +30 -0
- package/template/index.html +24 -0
- package/template/package.json +5 -14
- package/template/scripts/dev-server.js +330 -302
- package/template/scripts/live-reload-client.js +0 -1
- package/template/.vscode/settings.json +0 -15
- package/template/api/getUser.js +0 -60
- package/template/api/getUserWithDB.js +0 -65
- package/template/app.config.js +0 -41
- package/template/app.html +0 -55
- package/template/components/Counter.jsx +0 -36
- package/template/components/Footer.jsx +0 -9
- package/template/components/Navbar.jsx +0 -17
- package/template/docs/API-GUIDE.md +0 -196
- package/template/docs/API-ROUTES.md +0 -538
- package/template/docs/AUTO-IMPORT.md +0 -99
- package/template/docs/AUTO-REGISTRATION.md +0 -248
- package/template/docs/CSS-FRAMEWORKS.md +0 -221
- package/template/docs/DEV-SERVER.md +0 -184
- package/template/docs/GETTING-STARTED.md +0 -205
- package/template/docs/HOW-IT-WORKS.md +0 -362
- package/template/docs/NEXTJS-APP-ROUTER.md +0 -536
- package/template/docs/NEXTJS-COMPARISON.md +0 -473
- package/template/docs/QUICK-START.md +0 -111
- package/template/docs/STYLING.md +0 -269
- package/template/docs/TROUBLESHOOTING.md +0 -282
- package/template/modules/README.md +0 -129
- package/template/modules/api.js +0 -44
- package/template/modules/auto-import.js +0 -130
- package/template/modules/router.js +0 -218
- package/template/modules/state.js +0 -248
- package/template/package-lock.json +0 -13
- package/template/pages/About.jsx +0 -48
- package/template/pages/ApiDemo.jsx +0 -209
- package/template/pages/Contact.jsx +0 -25
- package/template/pages/Example.jsx +0 -59
- package/template/pages/Home.jsx +0 -39
- package/template/pages/HooksDemo.jsx +0 -194
- package/template/pages/test.jsx +0 -72
- package/template/scripts/build.js +0 -81
- package/template/scripts/generate-imports.js +0 -88
- package/template/tailwind.config.js +0 -17
package/template/README.md
CHANGED
|
@@ -1,212 +1,936 @@
|
|
|
1
|
-
# Aziz
|
|
1
|
+
# ⚡ Aziz.js
|
|
2
2
|
|
|
3
|
-
A
|
|
3
|
+
A lightweight Next.js clone with file-based routing, React hooks, server-side API routes, and ES module imports — **zero dependencies, instant startup**.
|
|
4
4
|
|
|
5
5
|
```
|
|
6
|
-
Next.js:
|
|
7
|
-
Aziz
|
|
6
|
+
Next.js: ~400KB + 20 dependencies + 10s startup
|
|
7
|
+
Aziz.js: ~50KB + 0 dependencies + <1s startup
|
|
8
8
|
```
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
---
|
|
11
11
|
|
|
12
|
+
## Quick Start
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
npx create-aziz-app my-app
|
|
16
|
+
cd my-app
|
|
17
|
+
npm run dev
|
|
12
18
|
```
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
19
|
+
|
|
20
|
+
Open [http://localhost:3000](http://localhost:3000) — that's it.
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## Project Structure
|
|
25
|
+
|
|
26
|
+
```
|
|
27
|
+
my-app/
|
|
28
|
+
├── index.html ← entry point (don't touch)
|
|
16
29
|
│
|
|
17
|
-
├──
|
|
18
|
-
│ ├──
|
|
19
|
-
│ ├──
|
|
20
|
-
│ ├──
|
|
21
|
-
│ └──
|
|
30
|
+
├── app/ ← your pages and layouts
|
|
31
|
+
│ ├── layout.js ← root layout (navbar, footer, etc.)
|
|
32
|
+
│ ├── page.js ← route: /
|
|
33
|
+
│ ├── about/
|
|
34
|
+
│ │ └── page.js ← route: /about
|
|
35
|
+
│ └── blog/
|
|
36
|
+
│ └── [slug]/
|
|
37
|
+
│ └── page.js ← route: /blog/:slug (dynamic)
|
|
22
38
|
│
|
|
23
|
-
├──
|
|
24
|
-
│ ├──
|
|
25
|
-
│
|
|
26
|
-
│ └── build.js # Build script
|
|
39
|
+
├── api/ ← server-side API routes (Node.js)
|
|
40
|
+
│ ├── users.js ← route: /api/users
|
|
41
|
+
│ └── hello.js ← route: /api/hello
|
|
27
42
|
│
|
|
28
|
-
├──
|
|
29
|
-
│ ├──
|
|
30
|
-
│
|
|
31
|
-
│ ├── GETTING-STARTED.md # Getting started guide
|
|
32
|
-
│ └── QUICK-START.md # Quick start guide
|
|
43
|
+
├── components/ ← shared UI components
|
|
44
|
+
│ ├── Navbar.js
|
|
45
|
+
│ └── Footer.js
|
|
33
46
|
│
|
|
34
|
-
├──
|
|
35
|
-
│
|
|
36
|
-
│ └── users.js # Example: /api/users
|
|
47
|
+
├── lib/ ← utilities, database connections
|
|
48
|
+
│ └── db.js
|
|
37
49
|
│
|
|
38
|
-
├──
|
|
39
|
-
│
|
|
40
|
-
│ ├── Footer.jsx
|
|
41
|
-
│ └── Counter.jsx
|
|
50
|
+
├── models/ ← data models
|
|
51
|
+
│ └── User.js
|
|
42
52
|
│
|
|
43
|
-
└──
|
|
44
|
-
|
|
45
|
-
├── About.jsx # Route: #/about
|
|
46
|
-
├── Contact.jsx # Route: #/contact
|
|
47
|
-
├── test.jsx # Route: #/test
|
|
48
|
-
└── ApiDemo.jsx # Route: #/api-demo
|
|
53
|
+
└── styles/
|
|
54
|
+
└── globals.css
|
|
49
55
|
```
|
|
50
56
|
|
|
51
|
-
|
|
57
|
+
---
|
|
52
58
|
|
|
53
|
-
|
|
59
|
+
## Pages
|
|
54
60
|
|
|
55
|
-
|
|
56
|
-
|
|
61
|
+
Every `page.js` inside `app/` becomes a route. Export a default function called `Page`.
|
|
62
|
+
|
|
63
|
+
```js
|
|
64
|
+
// app/about/page.js → route: /about
|
|
65
|
+
/** @jsx h */
|
|
66
|
+
import { h } from '/_framework/runtime.js';
|
|
67
|
+
|
|
68
|
+
export default function Page() {
|
|
69
|
+
return (
|
|
70
|
+
<div>
|
|
71
|
+
<h1>About</h1>
|
|
72
|
+
<p>Hello from the about page!</p>
|
|
73
|
+
</div>
|
|
74
|
+
);
|
|
75
|
+
}
|
|
57
76
|
```
|
|
58
77
|
|
|
59
|
-
|
|
60
|
-
- Start HTTP server on `http://localhost:3000/`
|
|
61
|
-
- Watch for file changes in `api/`, `components/`, and `pages/`
|
|
62
|
-
- Auto-generate imports when you create new files
|
|
63
|
-
- Auto-reload on changes
|
|
78
|
+
### Route mapping
|
|
64
79
|
|
|
65
|
-
|
|
80
|
+
| File | Route |
|
|
81
|
+
|------|-------|
|
|
82
|
+
| `app/page.js` | `/` |
|
|
83
|
+
| `app/about/page.js` | `/about` |
|
|
84
|
+
| `app/blog/page.js` | `/blog` |
|
|
85
|
+
| `app/blog/[slug]/page.js` | `/blog/:slug` |
|
|
86
|
+
| `app/blog/[...slug]/page.js` | `/blog/*` (catch-all) |
|
|
66
87
|
|
|
88
|
+
### Creating a new page
|
|
89
|
+
|
|
90
|
+
Just create the file — no registration, no imports to add:
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
# Creates route /dashboard
|
|
94
|
+
mkdir app/dashboard
|
|
95
|
+
touch app/dashboard/page.js
|
|
67
96
|
```
|
|
68
|
-
|
|
97
|
+
|
|
98
|
+
```js
|
|
99
|
+
// app/dashboard/page.js
|
|
100
|
+
/** @jsx h */
|
|
101
|
+
import { h } from '/_framework/runtime.js';
|
|
102
|
+
|
|
103
|
+
export default function Page() {
|
|
104
|
+
return <h1>Dashboard</h1>;
|
|
105
|
+
}
|
|
69
106
|
```
|
|
70
107
|
|
|
71
|
-
|
|
108
|
+
---
|
|
109
|
+
|
|
110
|
+
## Layouts
|
|
111
|
+
|
|
112
|
+
`layout.js` wraps all pages at the same level and below. The root `app/layout.js` wraps every page in your app.
|
|
113
|
+
|
|
114
|
+
```js
|
|
115
|
+
// app/layout.js
|
|
116
|
+
/** @jsx h */
|
|
117
|
+
import { h } from '/_framework/runtime.js';
|
|
118
|
+
import Navbar from '/components/Navbar.js';
|
|
119
|
+
import Footer from '/components/Footer.js';
|
|
120
|
+
|
|
121
|
+
export default function Layout({ children }) {
|
|
122
|
+
return (
|
|
123
|
+
<div>
|
|
124
|
+
<Navbar />
|
|
125
|
+
<main>{children}</main>
|
|
126
|
+
<Footer />
|
|
127
|
+
</div>
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
```
|
|
72
131
|
|
|
73
|
-
|
|
132
|
+
### Nested layouts
|
|
74
133
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
// pages/Profile.jsx
|
|
134
|
+
```js
|
|
135
|
+
// app/dashboard/layout.js — wraps all /dashboard/* pages
|
|
78
136
|
/** @jsx h */
|
|
137
|
+
import { h } from '/_framework/runtime.js';
|
|
138
|
+
import Sidebar from '/components/Sidebar.js';
|
|
139
|
+
|
|
140
|
+
export default function Layout({ children }) {
|
|
141
|
+
return (
|
|
142
|
+
<div style={{ display: 'flex' }}>
|
|
143
|
+
<Sidebar />
|
|
144
|
+
<div style={{ flex: 1 }}>{children}</div>
|
|
145
|
+
</div>
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
---
|
|
151
|
+
|
|
152
|
+
## Navigation
|
|
153
|
+
|
|
154
|
+
### navigate()
|
|
155
|
+
|
|
156
|
+
```js
|
|
157
|
+
import { navigate } from '/_framework/router.js';
|
|
158
|
+
|
|
159
|
+
navigate('/about');
|
|
160
|
+
navigate('/blog/my-post');
|
|
161
|
+
navigate('/');
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
### Link component
|
|
165
|
+
|
|
166
|
+
```js
|
|
167
|
+
import { Link } from '/_framework/router.js';
|
|
168
|
+
|
|
169
|
+
<Link href="/about">About</Link>
|
|
170
|
+
<Link href="/blog/hello" className="nav-link">Blog Post</Link>
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
### useRouter hook
|
|
174
|
+
|
|
175
|
+
```js
|
|
176
|
+
import { useRouter } from '/_framework/router.js';
|
|
177
|
+
|
|
178
|
+
export default function Page() {
|
|
179
|
+
const router = useRouter();
|
|
79
180
|
|
|
80
|
-
// Auto-registers as "#/profile" - no registerPage() needed!
|
|
81
|
-
function ProfilePage() {
|
|
82
|
-
const [name, setName] = useState('');
|
|
83
|
-
|
|
84
181
|
return (
|
|
85
182
|
<div>
|
|
86
|
-
<
|
|
87
|
-
<
|
|
88
|
-
|
|
89
|
-
onInput={(e) => setName(e.target.value)}
|
|
90
|
-
placeholder="Your name"
|
|
91
|
-
/>
|
|
92
|
-
<p>Hello, {name}!</p>
|
|
183
|
+
<p>Current path: {router.pathname}</p>
|
|
184
|
+
<button onClick={() => router.push('/about')}>Go to About</button>
|
|
185
|
+
<button onClick={() => router.back()}>Back</button>
|
|
93
186
|
</div>
|
|
94
187
|
);
|
|
95
188
|
}
|
|
96
189
|
```
|
|
97
190
|
|
|
98
|
-
|
|
191
|
+
### useParams — dynamic routes
|
|
192
|
+
|
|
193
|
+
```js
|
|
194
|
+
// app/blog/[slug]/page.js
|
|
195
|
+
import { useParams } from '/_framework/router.js';
|
|
196
|
+
|
|
197
|
+
export default function Page() {
|
|
198
|
+
const { slug } = useParams();
|
|
199
|
+
return <h1>Post: {slug}</h1>;
|
|
200
|
+
}
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
### useSearchParams — query strings
|
|
204
|
+
|
|
205
|
+
```js
|
|
206
|
+
// URL: /search?q=hello&page=2
|
|
207
|
+
import { useSearchParams } from '/_framework/router.js';
|
|
208
|
+
|
|
209
|
+
export default function Page() {
|
|
210
|
+
const searchParams = useSearchParams();
|
|
211
|
+
const query = searchParams.get('q'); // "hello"
|
|
212
|
+
const page = searchParams.get('page'); // "2"
|
|
213
|
+
|
|
214
|
+
return <p>Searching for: {query}</p>;
|
|
215
|
+
}
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
---
|
|
99
219
|
|
|
100
|
-
|
|
220
|
+
## React Hooks
|
|
101
221
|
|
|
102
|
-
|
|
222
|
+
All hooks work exactly like React — no imports needed in pages.
|
|
103
223
|
|
|
104
|
-
###
|
|
105
|
-
|
|
224
|
+
### useState
|
|
225
|
+
|
|
226
|
+
```js
|
|
106
227
|
const [count, setCount] = useState(0);
|
|
107
|
-
const [
|
|
228
|
+
const [user, setUser] = useState(null);
|
|
229
|
+
const [items, setItems] = useState([]);
|
|
108
230
|
|
|
231
|
+
// Functional update
|
|
232
|
+
setCount(prev => prev + 1);
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
### useEffect
|
|
236
|
+
|
|
237
|
+
```js
|
|
238
|
+
// Run on mount
|
|
109
239
|
useEffect(() => {
|
|
110
|
-
console.log('
|
|
240
|
+
console.log('mounted');
|
|
111
241
|
}, []);
|
|
112
242
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
243
|
+
// Run when value changes
|
|
244
|
+
useEffect(() => {
|
|
245
|
+
document.title = `Count: ${count}`;
|
|
246
|
+
}, [count]);
|
|
247
|
+
|
|
248
|
+
// Cleanup
|
|
249
|
+
useEffect(() => {
|
|
250
|
+
const timer = setInterval(() => tick(), 1000);
|
|
251
|
+
return () => clearInterval(timer);
|
|
252
|
+
}, []);
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
### useMemo
|
|
256
|
+
|
|
257
|
+
```js
|
|
258
|
+
const expensiveResult = useMemo(() => {
|
|
259
|
+
return items.filter(i => i.active).length;
|
|
260
|
+
}, [items]);
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
### useCallback
|
|
264
|
+
|
|
265
|
+
```js
|
|
266
|
+
const handleClick = useCallback(() => {
|
|
267
|
+
doSomething(id);
|
|
268
|
+
}, [id]);
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
### useRef
|
|
272
|
+
|
|
273
|
+
```js
|
|
274
|
+
const inputRef = useRef(null);
|
|
275
|
+
|
|
276
|
+
// In JSX
|
|
277
|
+
<input ref={inputRef} type="text" />
|
|
278
|
+
|
|
279
|
+
// Access DOM element
|
|
280
|
+
inputRef.current.focus();
|
|
117
281
|
```
|
|
118
282
|
|
|
119
|
-
|
|
283
|
+
### useReducer
|
|
120
284
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
return
|
|
285
|
+
```js
|
|
286
|
+
const reducer = (state, action) => {
|
|
287
|
+
switch (action.type) {
|
|
288
|
+
case 'increment': return { count: state.count + 1 };
|
|
289
|
+
case 'decrement': return { count: state.count - 1 };
|
|
290
|
+
case 'reset': return { count: 0 };
|
|
291
|
+
default: return state;
|
|
127
292
|
}
|
|
128
293
|
};
|
|
129
294
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
295
|
+
const [state, dispatch] = useReducer(reducer, { count: 0 });
|
|
296
|
+
|
|
297
|
+
dispatch({ type: 'increment' });
|
|
298
|
+
dispatch({ type: 'reset' });
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
### useContext
|
|
302
|
+
|
|
303
|
+
```js
|
|
304
|
+
// Create context
|
|
305
|
+
const ThemeContext = createContext('light');
|
|
306
|
+
|
|
307
|
+
// Provide value (in layout or parent)
|
|
308
|
+
<ThemeContext.Provider value="dark">
|
|
309
|
+
{children}
|
|
310
|
+
</ThemeContext.Provider>
|
|
311
|
+
|
|
312
|
+
// Consume in any child
|
|
313
|
+
const theme = useContext(ThemeContext);
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
### useLocalStorage
|
|
317
|
+
|
|
318
|
+
```js
|
|
319
|
+
// Persists across page refreshes
|
|
320
|
+
const [name, setName] = useLocalStorage('userName', '');
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
### useFetch
|
|
324
|
+
|
|
325
|
+
```js
|
|
326
|
+
const { data, loading, error } = useFetch('/api/users');
|
|
327
|
+
|
|
328
|
+
if (loading) return <p>Loading...</p>;
|
|
329
|
+
if (error) return <p>Error: {error}</p>;
|
|
330
|
+
|
|
331
|
+
return <pre>{JSON.stringify(data, null, 2)}</pre>;
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
---
|
|
335
|
+
|
|
336
|
+
## API Routes
|
|
337
|
+
|
|
338
|
+
API routes run on the **server** (Node.js). Every `.js` file in `api/` becomes an endpoint.
|
|
339
|
+
|
|
340
|
+
### Basic example
|
|
341
|
+
|
|
342
|
+
```js
|
|
343
|
+
// api/hello.js → GET /api/hello
|
|
344
|
+
const { NextResponse } = require('../modules/next-response');
|
|
345
|
+
|
|
346
|
+
async function GET(request) {
|
|
347
|
+
const { searchParams } = new URL(request.url);
|
|
348
|
+
const name = searchParams.get('name') || 'World';
|
|
349
|
+
|
|
350
|
+
return NextResponse.json({ message: `Hello, ${name}!` });
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
module.exports = { GET };
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
### CRUD example
|
|
357
|
+
|
|
358
|
+
```js
|
|
359
|
+
// api/users.js → /api/users
|
|
360
|
+
const { NextResponse } = require('../modules/next-response');
|
|
361
|
+
|
|
362
|
+
async function GET(request) {
|
|
363
|
+
const { searchParams } = new URL(request.url);
|
|
364
|
+
const id = searchParams.get('id');
|
|
365
|
+
|
|
366
|
+
if (id) {
|
|
367
|
+
const user = await User.findById(id);
|
|
368
|
+
if (!user) return NextResponse.json({ error: 'Not found' }, { status: 404 });
|
|
369
|
+
return NextResponse.json(user);
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
const users = await User.find();
|
|
373
|
+
return NextResponse.json(users);
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
async function POST(request) {
|
|
377
|
+
const body = await request.json();
|
|
378
|
+
const { name, email } = body;
|
|
379
|
+
|
|
380
|
+
if (!name || !email) {
|
|
381
|
+
return NextResponse.json({ error: 'Name and email required' }, { status: 400 });
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
const user = await User.create({ name, email });
|
|
385
|
+
return NextResponse.json(user, { status: 201 });
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
async function DELETE(request) {
|
|
389
|
+
const { searchParams } = new URL(request.url);
|
|
390
|
+
const id = searchParams.get('id');
|
|
391
|
+
|
|
392
|
+
await User.findByIdAndDelete(id);
|
|
393
|
+
return NextResponse.json({ message: 'Deleted' });
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
module.exports = { GET, POST, DELETE };
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
### Request object
|
|
400
|
+
|
|
401
|
+
```js
|
|
402
|
+
async function POST(request) {
|
|
403
|
+
// Parse body
|
|
404
|
+
const body = await request.json();
|
|
405
|
+
|
|
406
|
+
// Query params
|
|
407
|
+
const { searchParams } = new URL(request.url);
|
|
408
|
+
const page = searchParams.get('page');
|
|
409
|
+
|
|
410
|
+
// Headers
|
|
411
|
+
const token = request.headers.authorization;
|
|
412
|
+
|
|
413
|
+
// Method
|
|
414
|
+
console.log(request.method); // "POST"
|
|
415
|
+
}
|
|
416
|
+
```
|
|
417
|
+
|
|
418
|
+
### NextResponse
|
|
419
|
+
|
|
420
|
+
```js
|
|
421
|
+
// JSON response
|
|
422
|
+
return NextResponse.json({ data: 'value' });
|
|
423
|
+
|
|
424
|
+
// With status code
|
|
425
|
+
return NextResponse.json({ error: 'Not found' }, { status: 404 });
|
|
426
|
+
|
|
427
|
+
// With custom headers
|
|
428
|
+
return NextResponse.json(data, {
|
|
429
|
+
status: 200,
|
|
430
|
+
headers: { 'X-Custom': 'value' }
|
|
431
|
+
});
|
|
432
|
+
|
|
433
|
+
// Redirect
|
|
434
|
+
return NextResponse.redirect('/login');
|
|
435
|
+
|
|
436
|
+
// Plain text
|
|
437
|
+
return NextResponse.text('Hello');
|
|
438
|
+
```
|
|
439
|
+
|
|
440
|
+
### HTTP status codes
|
|
441
|
+
|
|
442
|
+
| Code | Meaning |
|
|
443
|
+
|------|---------|
|
|
444
|
+
| `200` | OK |
|
|
445
|
+
| `201` | Created |
|
|
446
|
+
| `400` | Bad Request |
|
|
447
|
+
| `401` | Unauthorized |
|
|
448
|
+
| `403` | Forbidden |
|
|
449
|
+
| `404` | Not Found |
|
|
450
|
+
| `409` | Conflict |
|
|
451
|
+
| `500` | Server Error |
|
|
452
|
+
|
|
453
|
+
---
|
|
454
|
+
|
|
455
|
+
## Imports
|
|
456
|
+
|
|
457
|
+
Use ES module `import` statements anywhere. The dev server resolves everything automatically.
|
|
458
|
+
|
|
459
|
+
### Framework imports
|
|
460
|
+
|
|
461
|
+
```js
|
|
462
|
+
import { h } from '/_framework/runtime.js';
|
|
463
|
+
import { navigate, Link, useRouter } from '/_framework/router.js';
|
|
464
|
+
```
|
|
465
|
+
|
|
466
|
+
### Absolute path imports
|
|
467
|
+
|
|
468
|
+
```js
|
|
469
|
+
import Navbar from '/components/Navbar.js';
|
|
470
|
+
import connectDB from '/lib/db.js';
|
|
471
|
+
import User from '/models/User.js';
|
|
472
|
+
```
|
|
473
|
+
|
|
474
|
+
### @/ alias (same as absolute)
|
|
475
|
+
|
|
476
|
+
```js
|
|
477
|
+
import connectDB from '@/lib/db';
|
|
478
|
+
import User from '@/models/User';
|
|
479
|
+
import Button from '@/components/Button';
|
|
480
|
+
```
|
|
481
|
+
|
|
482
|
+
### npm packages (bare imports)
|
|
483
|
+
|
|
484
|
+
```js
|
|
485
|
+
// Install first: npm install mongoose
|
|
486
|
+
import mongoose from 'mongoose';
|
|
487
|
+
|
|
488
|
+
// Install first: npm install bcryptjs
|
|
489
|
+
import bcrypt from 'bcryptjs';
|
|
490
|
+
|
|
491
|
+
// Install first: npm install jsonwebtoken
|
|
492
|
+
import jwt from 'jsonwebtoken';
|
|
493
|
+
```
|
|
494
|
+
|
|
495
|
+
The dev server resolves bare imports from `node_modules` automatically — no bundler config needed.
|
|
496
|
+
|
|
497
|
+
---
|
|
498
|
+
|
|
499
|
+
## Components
|
|
500
|
+
|
|
501
|
+
Components are just functions that return JSX. Always add `/** @jsx h */` at the top.
|
|
502
|
+
|
|
503
|
+
```js
|
|
504
|
+
// components/Button.js
|
|
505
|
+
/** @jsx h */
|
|
506
|
+
import { h } from '/_framework/runtime.js';
|
|
507
|
+
|
|
508
|
+
export default function Button({ onClick, children, variant = 'primary' }) {
|
|
509
|
+
return (
|
|
510
|
+
<button
|
|
511
|
+
class={`btn btn-${variant}`}
|
|
512
|
+
onClick={onClick}
|
|
513
|
+
>
|
|
514
|
+
{children}
|
|
515
|
+
</button>
|
|
516
|
+
);
|
|
517
|
+
}
|
|
518
|
+
```
|
|
519
|
+
|
|
520
|
+
```js
|
|
521
|
+
// Using the component
|
|
522
|
+
import Button from '/components/Button.js';
|
|
523
|
+
|
|
524
|
+
<Button onClick={() => alert('hi')} variant="danger">
|
|
525
|
+
Delete
|
|
526
|
+
</Button>
|
|
527
|
+
```
|
|
528
|
+
|
|
529
|
+
### Props
|
|
530
|
+
|
|
531
|
+
```js
|
|
532
|
+
// Strings
|
|
533
|
+
<Component title="Hello" />
|
|
534
|
+
|
|
535
|
+
// Numbers
|
|
536
|
+
<Component count={42} />
|
|
537
|
+
|
|
538
|
+
// Booleans
|
|
539
|
+
<Component disabled={true} />
|
|
540
|
+
<Component disabled />
|
|
541
|
+
|
|
542
|
+
// Objects / arrays
|
|
543
|
+
<Component style={{ color: 'red' }} />
|
|
544
|
+
<Component items={[1, 2, 3]} />
|
|
545
|
+
|
|
546
|
+
// Functions
|
|
547
|
+
<Component onClick={() => doSomething()} />
|
|
548
|
+
|
|
549
|
+
// Children
|
|
550
|
+
<Component>
|
|
551
|
+
<p>Child content</p>
|
|
552
|
+
</Component>
|
|
133
553
|
```
|
|
134
554
|
|
|
135
|
-
###
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
555
|
+
### JSX rules
|
|
556
|
+
|
|
557
|
+
```js
|
|
558
|
+
// Use class instead of className
|
|
559
|
+
<div class="container">
|
|
140
560
|
|
|
141
|
-
|
|
561
|
+
// Use onInput for text inputs (not onChange)
|
|
562
|
+
<input onInput={(e) => setValue(e.target.value)} />
|
|
142
563
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
- Refresh browser to see changes
|
|
564
|
+
// Self-closing tags need /
|
|
565
|
+
<input type="text" />
|
|
566
|
+
<br />
|
|
147
567
|
|
|
148
|
-
|
|
568
|
+
// Expressions in {}
|
|
569
|
+
<p>{count > 0 ? 'positive' : 'zero'}</p>
|
|
149
570
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
- **[Troubleshooting](docs/TROUBLESHOOTING.md)** - Fix common issues
|
|
571
|
+
// Lists
|
|
572
|
+
{items.map(item => <li key={item.id}>{item.name}</li>)}
|
|
573
|
+
|
|
574
|
+
// Conditional rendering
|
|
575
|
+
{isLoggedIn && <Dashboard />}
|
|
576
|
+
{error ? <Error msg={error} /> : <Content />}
|
|
577
|
+
```
|
|
158
578
|
|
|
159
|
-
|
|
579
|
+
---
|
|
580
|
+
|
|
581
|
+
## Database
|
|
582
|
+
|
|
583
|
+
### MongoDB with Mongoose
|
|
160
584
|
|
|
161
585
|
```bash
|
|
162
|
-
npm
|
|
163
|
-
|
|
586
|
+
npm install mongoose
|
|
587
|
+
```
|
|
588
|
+
|
|
589
|
+
```js
|
|
590
|
+
// lib/db.js
|
|
591
|
+
import mongoose from 'mongoose';
|
|
592
|
+
|
|
593
|
+
let isConnected = false;
|
|
594
|
+
|
|
595
|
+
export default async function connectDB() {
|
|
596
|
+
if (isConnected) return;
|
|
597
|
+
await mongoose.connect(process.env.MONGODB_URI);
|
|
598
|
+
isConnected = true;
|
|
599
|
+
console.log('✅ MongoDB connected');
|
|
600
|
+
}
|
|
164
601
|
```
|
|
165
602
|
|
|
166
|
-
|
|
603
|
+
```js
|
|
604
|
+
// models/User.js
|
|
605
|
+
import mongoose from 'mongoose';
|
|
167
606
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
607
|
+
const userSchema = new mongoose.Schema({
|
|
608
|
+
name: { type: String, required: true },
|
|
609
|
+
email: { type: String, required: true, unique: true },
|
|
610
|
+
password: { type: String, required: true },
|
|
611
|
+
role: { type: String, enum: ['user', 'admin'], default: 'user' }
|
|
612
|
+
}, { timestamps: true });
|
|
173
613
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
- Use JSX syntax with `/** @jsx h */`
|
|
177
|
-
- Import automatically
|
|
614
|
+
export default mongoose.models.User || mongoose.model('User', userSchema);
|
|
615
|
+
```
|
|
178
616
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
617
|
+
```js
|
|
618
|
+
// api/users.js
|
|
619
|
+
const { NextResponse } = require('../modules/next-response');
|
|
620
|
+
const connectDB = require('../lib/db');
|
|
621
|
+
const User = require('../models/User');
|
|
183
622
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
- Returns `[value, setValue]`
|
|
187
|
-
- Triggers re-render on change
|
|
623
|
+
async function GET(request) {
|
|
624
|
+
await connectDB();
|
|
188
625
|
|
|
189
|
-
|
|
626
|
+
const { searchParams } = new URL(request.url);
|
|
627
|
+
const email = searchParams.get('email');
|
|
190
628
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
629
|
+
if (email) {
|
|
630
|
+
const user = await User.findOne({ email }).select('-password');
|
|
631
|
+
if (!user) return NextResponse.json({ error: 'User not found' }, { status: 404 });
|
|
632
|
+
return NextResponse.json(user);
|
|
633
|
+
}
|
|
195
634
|
|
|
196
|
-
|
|
635
|
+
const users = await User.find().select('-password');
|
|
636
|
+
return NextResponse.json(users);
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
module.exports = { GET };
|
|
640
|
+
```
|
|
197
641
|
|
|
198
|
-
|
|
199
|
-
- **`scripts/`** - Build tools (don't modify)
|
|
200
|
-
- **`docs/`** - Documentation (read these!)
|
|
201
|
-
- **`api/`** - Your API routes (add your endpoints here)
|
|
202
|
-
- **`components/`** - Your components (add reusable UI here)
|
|
203
|
-
- **`pages/`** - Your pages (add new pages here)
|
|
642
|
+
### Environment variables
|
|
204
643
|
|
|
205
|
-
|
|
644
|
+
Create a `.env` file in your project root:
|
|
645
|
+
|
|
646
|
+
```bash
|
|
647
|
+
MONGODB_URI=mongodb://localhost:27017/myapp
|
|
648
|
+
JWT_SECRET=your_secret_key
|
|
649
|
+
API_KEY=your_api_key
|
|
650
|
+
```
|
|
651
|
+
|
|
652
|
+
Access in API routes:
|
|
653
|
+
|
|
654
|
+
```js
|
|
655
|
+
const uri = process.env.MONGODB_URI;
|
|
656
|
+
const secret = process.env.JWT_SECRET;
|
|
657
|
+
```
|
|
658
|
+
|
|
659
|
+
> **Note:** `.env` files are not loaded automatically. Install `dotenv` and add `require('dotenv').config()` at the top of `scripts/dev-server.js`, or set variables in your terminal before running `npm run dev`.
|
|
660
|
+
|
|
661
|
+
---
|
|
662
|
+
|
|
663
|
+
## Calling APIs from Pages
|
|
664
|
+
|
|
665
|
+
```js
|
|
666
|
+
// Simple fetch
|
|
667
|
+
const response = await fetch('/api/users');
|
|
668
|
+
const users = await response.json();
|
|
669
|
+
|
|
670
|
+
// POST request
|
|
671
|
+
const response = await fetch('/api/users', {
|
|
672
|
+
method: 'POST',
|
|
673
|
+
headers: { 'Content-Type': 'application/json' },
|
|
674
|
+
body: JSON.stringify({ name: 'Aziz', email: 'aziz@example.com' })
|
|
675
|
+
});
|
|
676
|
+
const result = await response.json();
|
|
677
|
+
|
|
678
|
+
// With error handling
|
|
679
|
+
async function createUser(data) {
|
|
680
|
+
try {
|
|
681
|
+
const res = await fetch('/api/createUser', {
|
|
682
|
+
method: 'POST',
|
|
683
|
+
headers: { 'Content-Type': 'application/json' },
|
|
684
|
+
body: JSON.stringify(data)
|
|
685
|
+
});
|
|
686
|
+
|
|
687
|
+
const result = await res.json();
|
|
688
|
+
|
|
689
|
+
if (!res.ok) {
|
|
690
|
+
throw new Error(result.error || 'Request failed');
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
return result;
|
|
694
|
+
} catch (err) {
|
|
695
|
+
console.error(err);
|
|
696
|
+
throw err;
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
```
|
|
700
|
+
|
|
701
|
+
### Full login example
|
|
702
|
+
|
|
703
|
+
```js
|
|
704
|
+
export default function Page() {
|
|
705
|
+
const [email, setEmail] = useState('');
|
|
706
|
+
const [password, setPassword] = useState('');
|
|
707
|
+
const [error, setError] = useState('');
|
|
708
|
+
const [loading, setLoading] = useState(false);
|
|
709
|
+
|
|
710
|
+
const handleLogin = async (e) => {
|
|
711
|
+
e.preventDefault();
|
|
712
|
+
setLoading(true);
|
|
713
|
+
setError('');
|
|
714
|
+
|
|
715
|
+
try {
|
|
716
|
+
const res = await fetch('/api/loginUser', {
|
|
717
|
+
method: 'POST',
|
|
718
|
+
headers: { 'Content-Type': 'application/json' },
|
|
719
|
+
body: JSON.stringify({ email, password })
|
|
720
|
+
});
|
|
721
|
+
|
|
722
|
+
const result = await res.json();
|
|
723
|
+
|
|
724
|
+
if (!res.ok) {
|
|
725
|
+
setError(result.error);
|
|
726
|
+
return;
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
localStorage.setItem('user', JSON.stringify(result));
|
|
730
|
+
navigate(result.role === 'admin' ? '/admin' : '/dashboard');
|
|
731
|
+
|
|
732
|
+
} catch (err) {
|
|
733
|
+
setError('Something went wrong. Try again.');
|
|
734
|
+
} finally {
|
|
735
|
+
setLoading(false);
|
|
736
|
+
}
|
|
737
|
+
};
|
|
738
|
+
|
|
739
|
+
return (
|
|
740
|
+
<div style={{ maxWidth: '400px', margin: '80px auto', padding: '0 16px' }}>
|
|
741
|
+
<h1>Login</h1>
|
|
742
|
+
<form onSubmit={handleLogin}>
|
|
743
|
+
<input
|
|
744
|
+
type="email"
|
|
745
|
+
value={email}
|
|
746
|
+
onInput={(e) => setEmail(e.target.value)}
|
|
747
|
+
placeholder="Email"
|
|
748
|
+
required
|
|
749
|
+
/>
|
|
750
|
+
<input
|
|
751
|
+
type="password"
|
|
752
|
+
value={password}
|
|
753
|
+
onInput={(e) => setPassword(e.target.value)}
|
|
754
|
+
placeholder="Password"
|
|
755
|
+
required
|
|
756
|
+
/>
|
|
757
|
+
{error && <p style={{ color: 'red' }}>{error}</p>}
|
|
758
|
+
<button type="submit" class="btn btn-primary" disabled={loading}>
|
|
759
|
+
{loading ? 'Logging in...' : 'Login'}
|
|
760
|
+
</button>
|
|
761
|
+
</form>
|
|
762
|
+
</div>
|
|
763
|
+
);
|
|
764
|
+
}
|
|
765
|
+
```
|
|
766
|
+
|
|
767
|
+
---
|
|
768
|
+
|
|
769
|
+
## Styling
|
|
770
|
+
|
|
771
|
+
### globals.css
|
|
772
|
+
|
|
773
|
+
Edit `styles/globals.css` for your global styles. Built-in utility classes:
|
|
774
|
+
|
|
775
|
+
```css
|
|
776
|
+
/* Cards */
|
|
777
|
+
.card { background: white; border-radius: 10px; padding: 24px; box-shadow: 0 2px 8px rgba(0,0,0,0.08); margin-bottom: 20px; }
|
|
778
|
+
|
|
779
|
+
/* Buttons */
|
|
780
|
+
.btn { display: inline-block; padding: 10px 22px; border: none; border-radius: 8px; cursor: pointer; }
|
|
781
|
+
.btn-primary { background: #61dafb; color: #282c34; font-weight: bold; }
|
|
782
|
+
.btn-danger { background: #ff6b6b; color: white; }
|
|
783
|
+
.btn-grey { background: #ccc; color: #333; }
|
|
784
|
+
|
|
785
|
+
/* Counter */
|
|
786
|
+
.count-display { font-size: 64px; font-weight: bold; text-align: center; }
|
|
787
|
+
.counter-actions { display: flex; gap: 12px; justify-content: center; }
|
|
788
|
+
```
|
|
789
|
+
|
|
790
|
+
### Inline styles
|
|
791
|
+
|
|
792
|
+
```js
|
|
793
|
+
<div style={{ padding: '20px', color: 'red', fontSize: '16px' }}>
|
|
794
|
+
Hello
|
|
795
|
+
</div>
|
|
796
|
+
```
|
|
797
|
+
|
|
798
|
+
### Tailwind CSS
|
|
799
|
+
|
|
800
|
+
When creating your app, choose Tailwind CSS in the setup wizard:
|
|
801
|
+
|
|
802
|
+
```bash
|
|
803
|
+
npx create-aziz-app my-app
|
|
804
|
+
# Choose: Tailwind CSS
|
|
805
|
+
```
|
|
806
|
+
|
|
807
|
+
Or add it manually to `index.html`:
|
|
808
|
+
|
|
809
|
+
```html
|
|
810
|
+
<script src="https://cdn.tailwindcss.com"></script>
|
|
811
|
+
```
|
|
812
|
+
|
|
813
|
+
Then use Tailwind classes directly:
|
|
814
|
+
|
|
815
|
+
```js
|
|
816
|
+
<div class="flex items-center gap-4 p-6 bg-white rounded-lg shadow">
|
|
817
|
+
<h1 class="text-2xl font-bold text-gray-800">Hello</h1>
|
|
818
|
+
</div>
|
|
819
|
+
```
|
|
820
|
+
|
|
821
|
+
---
|
|
822
|
+
|
|
823
|
+
## Dev Server
|
|
206
824
|
|
|
207
|
-
Start the dev server and start building:
|
|
208
825
|
```bash
|
|
209
826
|
npm run dev
|
|
210
827
|
```
|
|
211
828
|
|
|
212
|
-
|
|
829
|
+
The dev server:
|
|
830
|
+
- Serves your app at `http://localhost:3000`
|
|
831
|
+
- Watches `app/`, `api/`, `components/`, `lib/`, `models/`, `styles/` for changes
|
|
832
|
+
- Reloads the browser automatically on save (WebSocket live reload)
|
|
833
|
+
- Resolves bare imports from `node_modules`
|
|
834
|
+
- Loads API routes from `api/`
|
|
835
|
+
- Hot-reloads API routes when changed
|
|
836
|
+
|
|
837
|
+
### Custom port
|
|
838
|
+
|
|
839
|
+
```bash
|
|
840
|
+
PORT=4000 npm run dev
|
|
841
|
+
```
|
|
842
|
+
|
|
843
|
+
Or set it during project creation:
|
|
844
|
+
|
|
845
|
+
```bash
|
|
846
|
+
npx create-aziz-app my-app
|
|
847
|
+
# Enter port: 4000
|
|
848
|
+
```
|
|
849
|
+
|
|
850
|
+
### Live reload indicator
|
|
851
|
+
|
|
852
|
+
A small indicator appears in the bottom-right corner:
|
|
853
|
+
|
|
854
|
+
- 🟢 **Live Reload** — connected, watching for changes
|
|
855
|
+
- 🔵 **Reloading...** — change detected, refreshing
|
|
856
|
+
- 🟠 **Disconnected** — reconnecting...
|
|
857
|
+
|
|
858
|
+
Click it to manually reload.
|
|
859
|
+
|
|
860
|
+
---
|
|
861
|
+
|
|
862
|
+
## Comparison with Next.js
|
|
863
|
+
|
|
864
|
+
| Feature | Aziz.js | Next.js |
|
|
865
|
+
|---------|---------|---------|
|
|
866
|
+
| File-based routing | ✅ | ✅ |
|
|
867
|
+
| `page.js` convention | ✅ | ✅ |
|
|
868
|
+
| `layout.js` convention | ✅ | ✅ |
|
|
869
|
+
| Dynamic routes `[slug]` | ✅ | ✅ |
|
|
870
|
+
| Catch-all `[...slug]` | ✅ | ✅ |
|
|
871
|
+
| API routes | ✅ | ✅ |
|
|
872
|
+
| `NextResponse` | ✅ | ✅ |
|
|
873
|
+
| Named HTTP exports | ✅ | ✅ |
|
|
874
|
+
| React hooks | ✅ | ✅ |
|
|
875
|
+
| ES module imports | ✅ | ✅ |
|
|
876
|
+
| `@/` path alias | ✅ | ✅ |
|
|
877
|
+
| Live reload | ✅ | ✅ |
|
|
878
|
+
| SSR / SSG | ❌ | ✅ |
|
|
879
|
+
| Image optimization | ❌ | ✅ |
|
|
880
|
+
| TypeScript | ❌ | ✅ |
|
|
881
|
+
| Bundle size | **~50KB** | ~400KB+ |
|
|
882
|
+
| Dependencies | **0** | 20+ |
|
|
883
|
+
| Startup time | **< 1s** | 10–30s |
|
|
884
|
+
|
|
885
|
+
---
|
|
886
|
+
|
|
887
|
+
## FAQ
|
|
888
|
+
|
|
889
|
+
**Do I need to register pages manually?**
|
|
890
|
+
No. Just create `app/your-route/page.js` and it becomes a route automatically.
|
|
891
|
+
|
|
892
|
+
**Can I use npm packages?**
|
|
893
|
+
Yes. Run `npm install package-name` and import it normally:
|
|
894
|
+
```js
|
|
895
|
+
import mongoose from 'mongoose';
|
|
896
|
+
```
|
|
897
|
+
|
|
898
|
+
**Where do I put shared utilities?**
|
|
899
|
+
In `lib/`:
|
|
900
|
+
```js
|
|
901
|
+
// lib/utils.js
|
|
902
|
+
export function formatDate(date) {
|
|
903
|
+
return new Date(date).toLocaleDateString();
|
|
904
|
+
}
|
|
905
|
+
```
|
|
906
|
+
|
|
907
|
+
**How do I share state between pages?**
|
|
908
|
+
Use `useLocalStorage` for persistence, or create a context:
|
|
909
|
+
```js
|
|
910
|
+
const AuthContext = createContext(null);
|
|
911
|
+
```
|
|
912
|
+
|
|
913
|
+
**Can I use this in production?**
|
|
914
|
+
Aziz.js is designed for development and learning. For production apps with SEO requirements, consider Next.js. For simple SPAs and internal tools, Aziz.js works great.
|
|
915
|
+
|
|
916
|
+
**How do I add authentication?**
|
|
917
|
+
```js
|
|
918
|
+
// api/login.js
|
|
919
|
+
const { NextResponse } = require('../modules/next-response');
|
|
920
|
+
const jwt = require('jsonwebtoken'); // npm install jsonwebtoken
|
|
921
|
+
|
|
922
|
+
async function POST(request) {
|
|
923
|
+
const { email, password } = await request.json();
|
|
924
|
+
// verify credentials...
|
|
925
|
+
const token = jwt.sign({ userId: user._id }, process.env.JWT_SECRET);
|
|
926
|
+
return NextResponse.json({ token });
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
module.exports = { POST };
|
|
930
|
+
```
|
|
931
|
+
|
|
932
|
+
---
|
|
933
|
+
|
|
934
|
+
## License
|
|
935
|
+
|
|
936
|
+
MIT
|