create-aziz-app 3.1.1 → 3.1.3

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.
Files changed (33) hide show
  1. package/README.md +658 -207
  2. package/bin/create-aziz-app.js +302 -351
  3. package/package.json +1 -1
  4. package/template/README.md +14 -279
  5. package/template/_framework/api.js +219 -219
  6. package/template/_framework/boot.js +33 -33
  7. package/template/_framework/hooks.js +367 -291
  8. package/template/_framework/next-response.js +128 -128
  9. package/template/_framework/route-match.js +145 -145
  10. package/template/_framework/router.js +465 -259
  11. package/template/_framework/runtime.js +264 -264
  12. package/template/app/layout.js +3 -5
  13. package/template/app/page.js +9 -32
  14. package/template/docs/01-getting-started.md +151 -151
  15. package/template/docs/02-project-structure.md +109 -109
  16. package/template/docs/03-pages-and-layouts.md +140 -140
  17. package/template/docs/04-routing.md +120 -120
  18. package/template/docs/05-hooks.md +169 -169
  19. package/template/docs/06-api-routes.md +210 -210
  20. package/template/docs/07-api-client.md +151 -151
  21. package/template/docs/08-styling.md +94 -94
  22. package/template/docs/09-imports-and-modules.md +97 -97
  23. package/template/docs/10-database-and-models.md +154 -154
  24. package/template/docs/11-authentication.md +171 -171
  25. package/template/docs/12-production-and-deployment.md +121 -121
  26. package/template/docs/13-internals.md +179 -179
  27. package/template/docs/README.md +67 -67
  28. package/template/index.html +20 -20
  29. package/template/package.json +16 -16
  30. package/template/scripts/dev-server.js +493 -381
  31. package/template/scripts/lib/api-router.js +525 -525
  32. package/template/scripts/lib/js-transform.js +213 -141
  33. package/template/scripts/lib/module-resolver.js +132 -132
@@ -1,171 +1,171 @@
1
- # 11 · Authentication
2
-
3
- There's no built-in auth. Because API routes run in Node and the browser client is flexible, you can add JWT-based, session-based (cookies), or OAuth flows yourself.
4
-
5
- ## Principles
6
-
7
- - **Don't trust the browser.** Auth must be checked in **API routes** (server), not just hidden in UI.
8
- - **Protect routes.** Return `401`/`403` from API handlers when unauthorized.
9
- - **Send credentials in requests.** Use `Authorization: Bearer <token>` or rely on `HttpOnly` cookies.
10
- - **Keep secrets in `.env`.** Never put `JWT_SECRET` in page/components.
11
-
12
- ## JWT (stateless) — minimal example
13
-
14
- ### 1. Install
15
-
16
- ```bash
17
- npm install jsonwebtoken
18
- ```
19
-
20
- ### 2. `api/login.js`
21
-
22
- ```js
23
- // api/login.js
24
- const { NextResponse } = require('../_framework/next-response');
25
- const jwt = require('jsonwebtoken');
26
-
27
- const SECRET = process.env.JWT_SECRET || 'change-me';
28
-
29
- module.exports = {
30
- POST: async (request) => {
31
- const { email, password } = await request.json();
32
- if (!email || !password) return NextResponse.json({ error: 'Missing fields' }, { status: 400 });
33
-
34
- // Fake user check for demo — replace with DB lookup + bcrypt
35
- if (email === 'demo@example.com' && password === 'demo') {
36
- const token = jwt.sign({ sub: 'u_123', email }, SECRET, { expiresIn: '7d' });
37
- return NextResponse.json({ token });
38
- }
39
- return NextResponse.json({ error: 'Invalid credentials' }, { status: 401 });
40
- },
41
- };
42
- ```
43
-
44
- ### 3. `api/me.js` (protected)
45
-
46
- ```js
47
- // api/me.js
48
- const { NextResponse } = require('../_framework/next-response');
49
- const jwt = require('jsonwebtoken');
50
-
51
- const SECRET = process.env.JWT_SECRET || 'change-me';
52
-
53
- function getUser(req) {
54
- const auth = req.headers.authorization || '';
55
- if (!auth.startsWith('Bearer ')) return null;
56
- const token = auth.slice(7);
57
- try {
58
- return jwt.verify(token, SECRET);
59
- } catch {
60
- return null;
61
- }
62
- }
63
-
64
- module.exports = {
65
- GET: async (request) => {
66
- const user = getUser(request);
67
- if (!user) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
68
- return NextResponse.json({ user });
69
- },
70
- };
71
- ```
72
-
73
- ### 4. Browser usage
74
-
75
- ```js
76
- async function login() {
77
- const res = await api.post('/api/login', { email: 'demo@example.com', password: 'demo' });
78
- localStorage.setItem('token', res.token);
79
- api.setHeaders({ Authorization: `Bearer ${res.token}` });
80
- }
81
-
82
- async function me() {
83
- const res = await api.get('/api/me');
84
- console.log(res.user);
85
- }
86
- ```
87
-
88
- If you prefer not to keep in `localStorage`, have the login endpoint set an **HttpOnly + Secure + SameSite** cookie via `NextResponse.cookies.set` and send it on every request (the browser does this automatically). Then read `request.cookies` on the server.
89
-
90
- ## Sessions with cookies (HttpOnly)
91
-
92
- ```js
93
- const sessions = new Map(); // in-memory; replace with Redis for multi-instance
94
-
95
- module.exports.POST = async (request) => {
96
- const { email, password } = await request.json();
97
- if (email !== 'demo@example.com' || password !== 'demo') {
98
- return NextResponse.json({ error: 'Invalid' }, { status: 401 });
99
- }
100
- const sid = crypto.randomUUID();
101
- sessions.set(sid, { user: { id: 1, email } });
102
- const res = NextResponse.json({ ok: true });
103
- res.cookies.set('sid', sid, { httpOnly: true, secure: true, sameSite: 'lax', maxAge: 86400 });
104
- return res;
105
- };
106
- ```
107
-
108
- Then check `request.cookies.sid` against `sessions`.
109
-
110
- ## Protecting pages (UI guard)
111
-
112
- You can gate the UI in a page. The server remains the source of truth.
113
-
114
- ```js
115
- export default function Dashboard() {
116
- const [ok, setOk] = useState(false);
117
-
118
- useEffect(() => {
119
- api.get('/api/me')
120
- .then(() => setOk(true))
121
- .catch(() => navigate('/login'));
122
- }, []);
123
-
124
- if (!ok) return <p>Checking auth…</p>;
125
- return <h1>Dashboard</h1>;
126
- }
127
- ```
128
-
129
- ## Protecting with roles
130
-
131
- Attach a `role` to the token/session and check it:
132
-
133
- ```js
134
- function requireRole(role) {
135
- return async (request) => {
136
- const user = getUser(request);
137
- if (!user) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
138
- if (user.role !== role) return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
139
- return NextResponse.json({ ok: true, user });
140
- };
141
- }
142
-
143
- module.exports.GET = requireRole('admin');
144
- ```
145
-
146
- ## Passwords
147
-
148
- **Never** compare plain text. Use `bcrypt` or `argon2`:
149
-
150
- ```bash
151
- npm install bcryptjs
152
- ```
153
-
154
- ```js
155
- const bcrypt = require('bcryptjs');
156
- const hash = await bcrypt.hash(password, 10);
157
- const ok = await bcrypt.compare(plain, hash);
158
- ```
159
-
160
- ## Quick checklist
161
-
162
- - [ ] Put secrets in `.env` (don't commit)
163
- - [ ] Verify auth on **every protected API** call
164
- - [ ] Return `401` for missing/invalid, `403` for insufficient
165
- - [ ] Use `HttpOnly` cookies for browser-only apps, or `Authorization` header for SPAs calling external
166
- - [ ] Don't assume client-side redirects are enough
167
- - [ ] Keep token expiry short, refresh if needed
168
-
169
- ## Continue
170
-
171
- → [Production & Deployment](./12-production-and-deployment.md)
1
+ # 11 · Authentication
2
+
3
+ There's no built-in auth. Because API routes run in Node and the browser client is flexible, you can add JWT-based, session-based (cookies), or OAuth flows yourself.
4
+
5
+ ## Principles
6
+
7
+ - **Don't trust the browser.** Auth must be checked in **API routes** (server), not just hidden in UI.
8
+ - **Protect routes.** Return `401`/`403` from API handlers when unauthorized.
9
+ - **Send credentials in requests.** Use `Authorization: Bearer <token>` or rely on `HttpOnly` cookies.
10
+ - **Keep secrets in `.env`.** Never put `JWT_SECRET` in page/components.
11
+
12
+ ## JWT (stateless) — minimal example
13
+
14
+ ### 1. Install
15
+
16
+ ```bash
17
+ npm install jsonwebtoken
18
+ ```
19
+
20
+ ### 2. `api/login.js`
21
+
22
+ ```js
23
+ // api/login.js
24
+ const { NextResponse } = require('../_framework/next-response');
25
+ const jwt = require('jsonwebtoken');
26
+
27
+ const SECRET = process.env.JWT_SECRET || 'change-me';
28
+
29
+ module.exports = {
30
+ POST: async (request) => {
31
+ const { email, password } = await request.json();
32
+ if (!email || !password) return NextResponse.json({ error: 'Missing fields' }, { status: 400 });
33
+
34
+ // Fake user check for demo — replace with DB lookup + bcrypt
35
+ if (email === 'demo@example.com' && password === 'demo') {
36
+ const token = jwt.sign({ sub: 'u_123', email }, SECRET, { expiresIn: '7d' });
37
+ return NextResponse.json({ token });
38
+ }
39
+ return NextResponse.json({ error: 'Invalid credentials' }, { status: 401 });
40
+ },
41
+ };
42
+ ```
43
+
44
+ ### 3. `api/me.js` (protected)
45
+
46
+ ```js
47
+ // api/me.js
48
+ const { NextResponse } = require('../_framework/next-response');
49
+ const jwt = require('jsonwebtoken');
50
+
51
+ const SECRET = process.env.JWT_SECRET || 'change-me';
52
+
53
+ function getUser(req) {
54
+ const auth = req.headers.authorization || '';
55
+ if (!auth.startsWith('Bearer ')) return null;
56
+ const token = auth.slice(7);
57
+ try {
58
+ return jwt.verify(token, SECRET);
59
+ } catch {
60
+ return null;
61
+ }
62
+ }
63
+
64
+ module.exports = {
65
+ GET: async (request) => {
66
+ const user = getUser(request);
67
+ if (!user) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
68
+ return NextResponse.json({ user });
69
+ },
70
+ };
71
+ ```
72
+
73
+ ### 4. Browser usage
74
+
75
+ ```js
76
+ async function login() {
77
+ const res = await api.post('/api/login', { email: 'demo@example.com', password: 'demo' });
78
+ localStorage.setItem('token', res.token);
79
+ api.setHeaders({ Authorization: `Bearer ${res.token}` });
80
+ }
81
+
82
+ async function me() {
83
+ const res = await api.get('/api/me');
84
+ console.log(res.user);
85
+ }
86
+ ```
87
+
88
+ If you prefer not to keep in `localStorage`, have the login endpoint set an **HttpOnly + Secure + SameSite** cookie via `NextResponse.cookies.set` and send it on every request (the browser does this automatically). Then read `request.cookies` on the server.
89
+
90
+ ## Sessions with cookies (HttpOnly)
91
+
92
+ ```js
93
+ const sessions = new Map(); // in-memory; replace with Redis for multi-instance
94
+
95
+ module.exports.POST = async (request) => {
96
+ const { email, password } = await request.json();
97
+ if (email !== 'demo@example.com' || password !== 'demo') {
98
+ return NextResponse.json({ error: 'Invalid' }, { status: 401 });
99
+ }
100
+ const sid = crypto.randomUUID();
101
+ sessions.set(sid, { user: { id: 1, email } });
102
+ const res = NextResponse.json({ ok: true });
103
+ res.cookies.set('sid', sid, { httpOnly: true, secure: true, sameSite: 'lax', maxAge: 86400 });
104
+ return res;
105
+ };
106
+ ```
107
+
108
+ Then check `request.cookies.sid` against `sessions`.
109
+
110
+ ## Protecting pages (UI guard)
111
+
112
+ You can gate the UI in a page. The server remains the source of truth.
113
+
114
+ ```js
115
+ export default function Dashboard() {
116
+ const [ok, setOk] = useState(false);
117
+
118
+ useEffect(() => {
119
+ api.get('/api/me')
120
+ .then(() => setOk(true))
121
+ .catch(() => navigate('/login'));
122
+ }, []);
123
+
124
+ if (!ok) return <p>Checking auth…</p>;
125
+ return <h1>Dashboard</h1>;
126
+ }
127
+ ```
128
+
129
+ ## Protecting with roles
130
+
131
+ Attach a `role` to the token/session and check it:
132
+
133
+ ```js
134
+ function requireRole(role) {
135
+ return async (request) => {
136
+ const user = getUser(request);
137
+ if (!user) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
138
+ if (user.role !== role) return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
139
+ return NextResponse.json({ ok: true, user });
140
+ };
141
+ }
142
+
143
+ module.exports.GET = requireRole('admin');
144
+ ```
145
+
146
+ ## Passwords
147
+
148
+ **Never** compare plain text. Use `bcrypt` or `argon2`:
149
+
150
+ ```bash
151
+ npm install bcryptjs
152
+ ```
153
+
154
+ ```js
155
+ const bcrypt = require('bcryptjs');
156
+ const hash = await bcrypt.hash(password, 10);
157
+ const ok = await bcrypt.compare(plain, hash);
158
+ ```
159
+
160
+ ## Quick checklist
161
+
162
+ - [ ] Put secrets in `.env` (don't commit)
163
+ - [ ] Verify auth on **every protected API** call
164
+ - [ ] Return `401` for missing/invalid, `403` for insufficient
165
+ - [ ] Use `HttpOnly` cookies for browser-only apps, or `Authorization` header for SPAs calling external
166
+ - [ ] Don't assume client-side redirects are enough
167
+ - [ ] Keep token expiry short, refresh if needed
168
+
169
+ ## Continue
170
+
171
+ → [Production & Deployment](./12-production-and-deployment.md)
@@ -1,121 +1,121 @@
1
- # 12 · Production & Deployment
2
-
3
- Aziz.js doesn't "bundle" to a huge build — it serves your source, compiles JSX on demand, and serves `/_modules/*` straight from `node_modules`. There's nothing to compile to a `dist/` directory.
4
-
5
- ## npm scripts
6
-
7
- ```json
8
- {
9
- "scripts": {
10
- "dev": "node scripts/dev-server.js",
11
- "build": "node scripts/dev-server.js --production",
12
- "start": "node scripts/dev-server.js --production"
13
- }
14
- }
15
- ```
16
-
17
- **Running with `--production` (or `NODE_ENV=production`) changes behavior:**
18
-
19
- - **No WebSocket live reload** server/client — the client script becomes an empty no-op.
20
- - **Cache headers**: HTML/JSON are `no-cache` (check ETag), most static assets get long-lived cache with `ETag` still checked.
21
- - **No verbose "Browser connected" / watcher spam** in the logs.
22
- - **Routes manifest still served** same way.
23
-
24
- ## Locally testing production
25
-
26
- ```bash
27
- npm run build # or npm start
28
- # Server runs at http://localhost:3000 with production headers
29
- ```
30
-
31
- Visiting `/_framework/routes.json` returns `"dev": false` in production.
32
-
33
- ## Deploying to a host
34
-
35
- Any host that can run **Node.js 18+** works. Examples: Render, Railway, Fly.io, Hetzner, DigitalOcean, Vercel (with a custom Node server), or a VPS.
36
-
37
- ### 1) Prepare the project
38
-
39
- ```bash
40
- cd my-app
41
- npm ci # clean install in CI/host
42
- ```
43
-
44
- ### 2) Environment variables
45
-
46
- Set anything your API routes need: `MONGODB_URI`, `JWT_SECRET`, `PORT` etc. The server reads `process.env.PORT`.
47
-
48
- ### 3) Start it
49
-
50
- On the host, run:
51
-
52
- ```bash
53
- NODE_ENV=production node scripts/dev-server.js
54
- ```
55
-
56
- Or `npm start` if you prefer.
57
-
58
- If the platform requires a specific `start` command and binds to `$PORT`, this is fine — the server already respects `process.env.PORT`.
59
-
60
- ### 4) Proxy / reverse proxy (optional but recommended)
61
-
62
- Put Nginx, Caddy, or your load balancer in front for:
63
-
64
- - HTTPS termination
65
- - Compression (gzip/brotli)
66
- - Caching static assets aggressively
67
- - Forwarding `X-Forwarded-For` / real IPs (the dev server doesn't trust proxy headers — treat as internal unless you add it)
68
-
69
- Caddy example:
70
-
71
- ```txt
72
- example.com {
73
- reverse_proxy localhost:3000
74
- encode zstd gzip
75
- }
76
- ```
77
-
78
- ## Static hosting?
79
-
80
- You **cannot** just drop `template/` into a static S3 bucket. API routes (`api/**` and `app/api/**/route.js`) run on the **Node server**. The browser needs the dev server to:
81
-
82
- - serve transformed JS (JSX → h())
83
- - serve the route manifest
84
- - resolve bare imports via `/_modules/*`
85
- - handle `/api/*`
86
-
87
- If you want a 100% static site, you'd need a separate SSR/bundler — that's not what this lightweight framework does.
88
-
89
- ## Security notes
90
-
91
- - **Path traversal**: the server confines all static file requests to the project root. Attempts to escape (e.g. `..%2f..%2fpackage.json`) return `403`.
92
- - **API CORS**: preflight `OPTIONS` and responses include permissive CORS headers — tighten if you only serve from one origin.
93
- - **JWT secrets**: never commit `.env`. Don't ship `node_modules` unnecessarily in git.
94
- - **Dependencies**: only `@babel/standalone` in `devDependencies` (needed to transform JSX on-the-fly). Your own deps go in `dependencies`.
95
- - **ETag**: conditional requests use strong-ish ETag based on `mtimeMs`+`size`; fine for this use case.
96
-
97
- ## What "production mode" doesn't do
98
-
99
- - No code splitting / tree shaking
100
- - No minification of JS/CSS (though you can pre-minify and reference extra files)
101
- - No image optimization
102
- - No prerendering/SSG
103
- - No `.env` auto-loading (you must set environment variables)
104
-
105
- This is by design for simplicity and speed. For most small–medium apps it's perfectly usable.
106
-
107
- ## Performance tips
108
-
109
- - **Lean deps**: avoid pulling huge libraries unless needed (the `/_modules` transform isn't magic; it still ships bytes).
110
- - **Memoize** in hooks (`useMemo`, `useCallback`) for expensive computations.
111
- - **Keep keys stable** in lists (`key={id}`).
112
- - **Use HTTP caching** via a proxy for immutable assets (`.woff2`, images) if you add them.
113
- - **Close DB connections** or use a proper pool (the examples above do a singleton pool).
114
-
115
- ## Health check (optional)
116
-
117
- You can hit `/api/hello` to quickly verify the server is up.
118
-
119
- ## Continue
120
-
121
- → [Internals](./13-internals.md)
1
+ # 12 · Production & Deployment
2
+
3
+ Aziz.js doesn't "bundle" to a huge build — it serves your source, compiles JSX on demand, and serves `/_modules/*` straight from `node_modules`. There's nothing to compile to a `dist/` directory.
4
+
5
+ ## npm scripts
6
+
7
+ ```json
8
+ {
9
+ "scripts": {
10
+ "dev": "node scripts/dev-server.js",
11
+ "build": "node scripts/dev-server.js --production",
12
+ "start": "node scripts/dev-server.js --production"
13
+ }
14
+ }
15
+ ```
16
+
17
+ **Running with `--production` (or `NODE_ENV=production`) changes behavior:**
18
+
19
+ - **No WebSocket live reload** server/client — the client script becomes an empty no-op.
20
+ - **Cache headers**: HTML/JSON are `no-cache` (check ETag), most static assets get long-lived cache with `ETag` still checked.
21
+ - **No verbose "Browser connected" / watcher spam** in the logs.
22
+ - **Routes manifest still served** same way.
23
+
24
+ ## Locally testing production
25
+
26
+ ```bash
27
+ npm run build # or npm start
28
+ # Server runs at http://localhost:3000 with production headers
29
+ ```
30
+
31
+ Visiting `/_framework/routes.json` returns `"dev": false` in production.
32
+
33
+ ## Deploying to a host
34
+
35
+ Any host that can run **Node.js 18+** works. Examples: Render, Railway, Fly.io, Hetzner, DigitalOcean, Vercel (with a custom Node server), or a VPS.
36
+
37
+ ### 1) Prepare the project
38
+
39
+ ```bash
40
+ cd my-app
41
+ npm ci # clean install in CI/host
42
+ ```
43
+
44
+ ### 2) Environment variables
45
+
46
+ Set anything your API routes need: `MONGODB_URI`, `JWT_SECRET`, `PORT` etc. The server reads `process.env.PORT`.
47
+
48
+ ### 3) Start it
49
+
50
+ On the host, run:
51
+
52
+ ```bash
53
+ NODE_ENV=production node scripts/dev-server.js
54
+ ```
55
+
56
+ Or `npm start` if you prefer.
57
+
58
+ If the platform requires a specific `start` command and binds to `$PORT`, this is fine — the server already respects `process.env.PORT`.
59
+
60
+ ### 4) Proxy / reverse proxy (optional but recommended)
61
+
62
+ Put Nginx, Caddy, or your load balancer in front for:
63
+
64
+ - HTTPS termination
65
+ - Compression (gzip/brotli)
66
+ - Caching static assets aggressively
67
+ - Forwarding `X-Forwarded-For` / real IPs (the dev server doesn't trust proxy headers — treat as internal unless you add it)
68
+
69
+ Caddy example:
70
+
71
+ ```txt
72
+ example.com {
73
+ reverse_proxy localhost:3000
74
+ encode zstd gzip
75
+ }
76
+ ```
77
+
78
+ ## Static hosting?
79
+
80
+ You **cannot** just drop `template/` into a static S3 bucket. API routes (`api/**` and `app/api/**/route.js`) run on the **Node server**. The browser needs the dev server to:
81
+
82
+ - serve transformed JS (JSX → h())
83
+ - serve the route manifest
84
+ - resolve bare imports via `/_modules/*`
85
+ - handle `/api/*`
86
+
87
+ If you want a 100% static site, you'd need a separate SSR/bundler — that's not what this lightweight framework does.
88
+
89
+ ## Security notes
90
+
91
+ - **Path traversal**: the server confines all static file requests to the project root. Attempts to escape (e.g. `..%2f..%2fpackage.json`) return `403`.
92
+ - **API CORS**: preflight `OPTIONS` and responses include permissive CORS headers — tighten if you only serve from one origin.
93
+ - **JWT secrets**: never commit `.env`. Don't ship `node_modules` unnecessarily in git.
94
+ - **Dependencies**: only `@babel/standalone` in `devDependencies` (needed to transform JSX on-the-fly). Your own deps go in `dependencies`.
95
+ - **ETag**: conditional requests use strong-ish ETag based on `mtimeMs`+`size`; fine for this use case.
96
+
97
+ ## What "production mode" doesn't do
98
+
99
+ - No code splitting / tree shaking
100
+ - No minification of JS/CSS (though you can pre-minify and reference extra files)
101
+ - No image optimization
102
+ - No prerendering/SSG
103
+ - No `.env` auto-loading (you must set environment variables)
104
+
105
+ This is by design for simplicity and speed. For most small–medium apps it's perfectly usable.
106
+
107
+ ## Performance tips
108
+
109
+ - **Lean deps**: avoid pulling huge libraries unless needed (the `/_modules` transform isn't magic; it still ships bytes).
110
+ - **Memoize** in hooks (`useMemo`, `useCallback`) for expensive computations.
111
+ - **Keep keys stable** in lists (`key={id}`).
112
+ - **Use HTTP caching** via a proxy for immutable assets (`.woff2`, images) if you add them.
113
+ - **Close DB connections** or use a proper pool (the examples above do a singleton pool).
114
+
115
+ ## Health check (optional)
116
+
117
+ You can hit `/api/hello` to quickly verify the server is up.
118
+
119
+ ## Continue
120
+
121
+ → [Internals](./13-internals.md)