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.
- package/README.md +658 -207
- package/bin/create-aziz-app.js +302 -351
- package/package.json +1 -1
- package/template/README.md +14 -279
- package/template/_framework/api.js +219 -219
- package/template/_framework/boot.js +33 -33
- package/template/_framework/hooks.js +367 -291
- package/template/_framework/next-response.js +128 -128
- package/template/_framework/route-match.js +145 -145
- package/template/_framework/router.js +465 -259
- package/template/_framework/runtime.js +264 -264
- package/template/app/layout.js +3 -5
- package/template/app/page.js +9 -32
- package/template/docs/01-getting-started.md +151 -151
- package/template/docs/02-project-structure.md +109 -109
- package/template/docs/03-pages-and-layouts.md +140 -140
- package/template/docs/04-routing.md +120 -120
- package/template/docs/05-hooks.md +169 -169
- package/template/docs/06-api-routes.md +210 -210
- package/template/docs/07-api-client.md +151 -151
- package/template/docs/08-styling.md +94 -94
- package/template/docs/09-imports-and-modules.md +97 -97
- package/template/docs/10-database-and-models.md +154 -154
- package/template/docs/11-authentication.md +171 -171
- package/template/docs/12-production-and-deployment.md +121 -121
- package/template/docs/13-internals.md +179 -179
- package/template/docs/README.md +67 -67
- package/template/index.html +20 -20
- package/template/package.json +16 -16
- package/template/scripts/dev-server.js +493 -381
- package/template/scripts/lib/api-router.js +525 -525
- package/template/scripts/lib/js-transform.js +213 -141
- package/template/scripts/lib/module-resolver.js +132 -132
|
@@ -1,210 +1,210 @@
|
|
|
1
|
-
# 06 · API Routes
|
|
2
|
-
|
|
3
|
-
API routes are the **server side** of your app — real Node.js handlers you call over HTTP. They're how you fetch data, handle forms, and run database queries without exposing secrets in the browser.
|
|
4
|
-
|
|
5
|
-
## Where routes live
|
|
6
|
-
|
|
7
|
-
Two equivalent layouts, both producing `/api/**` URLs:
|
|
8
|
-
|
|
9
|
-
```
|
|
10
|
-
api/users.js → /api/users (classic)
|
|
11
|
-
api/users/[id].js → /api/users/:id
|
|
12
|
-
app/api/users/route.js → /api/users (Next.js style)
|
|
13
|
-
app/api/users/[id]/route.js → /api/users/:id
|
|
14
|
-
```
|
|
15
|
-
|
|
16
|
-
- **Classic:** `api/<name>.js` → `/api/<name>`; folders nest: `api/posts/[id]/comments.js`.
|
|
17
|
-
- **Next style:** any `route.js` under `app/api/` → the folder path mapped to `/api/...`.
|
|
18
|
-
|
|
19
|
-
Both can coexist. Use whichever reads better.
|
|
20
|
-
|
|
21
|
-
## Your first handler
|
|
22
|
-
|
|
23
|
-
Export functions named after HTTP methods:
|
|
24
|
-
|
|
25
|
-
```js
|
|
26
|
-
// api/hello.js → /api/hello
|
|
27
|
-
module.exports = {
|
|
28
|
-
GET: async (request) => {
|
|
29
|
-
const name = request.query.get('name') || 'world';
|
|
30
|
-
return { hello: name };
|
|
31
|
-
},
|
|
32
|
-
|
|
33
|
-
POST: async (request) => {
|
|
34
|
-
const body = await request.json();
|
|
35
|
-
return { created: true, ...body };
|
|
36
|
-
},
|
|
37
|
-
};
|
|
38
|
-
```
|
|
39
|
-
|
|
40
|
-
**Return a value — don't manage a raw socket.** The framework turns it into the HTTP response:
|
|
41
|
-
|
|
42
|
-
| You return | Response |
|
|
43
|
-
|------------|----------|
|
|
44
|
-
| `{ ok: true }` / `[]` | `200` + JSON |
|
|
45
|
-
| `'some text'` | `200` + `text/plain` |
|
|
46
|
-
| `NextResponse.json(data, { status: 400 })` | `400` + your headers |
|
|
47
|
-
| `NextResponse.redirect('/login')` | `307` + `Location` |
|
|
48
|
-
| `undefined` / `null` | `500` + a helpful error (you forgot to return) |
|
|
49
|
-
|
|
50
|
-
## The `request` object
|
|
51
|
-
|
|
52
|
-
```js
|
|
53
|
-
GET: async (request) => {
|
|
54
|
-
request.method // 'GET'
|
|
55
|
-
request.url // full URL
|
|
56
|
-
request.params // { id: '42' } from [id] segments
|
|
57
|
-
request.query // URLSearchParams → get('page'), getAll('tag')
|
|
58
|
-
request.headers // raw Node headers (lowercase keys)
|
|
59
|
-
request.cookies // { token: '…' } parsed from Cookie header
|
|
60
|
-
request.rawBody // raw body string
|
|
61
|
-
request.body // parsed JSON if content-type was JSON
|
|
62
|
-
|
|
63
|
-
await request.json() // parse JSON body (throws 400 on invalid)
|
|
64
|
-
await request.text() // raw text
|
|
65
|
-
await request.formData()// urlencoded → { field: 'value' }
|
|
66
|
-
}
|
|
67
|
-
```
|
|
68
|
-
|
|
69
|
-
### Dynamic and catch-all params
|
|
70
|
-
|
|
71
|
-
```js
|
|
72
|
-
// api/users/[id].js
|
|
73
|
-
GET: async (request) => request.params.id // /api/users/7 → "7"
|
|
74
|
-
|
|
75
|
-
// api/files/[...path].js
|
|
76
|
-
GET: async (request) => request.params.path // /api/files/a/b → ['a','b']
|
|
77
|
-
```
|
|
78
|
-
|
|
79
|
-
The literal prefix is validated — `api/files/[...path]` won't match `/api/other/x`.
|
|
80
|
-
|
|
81
|
-
### Query strings
|
|
82
|
-
|
|
83
|
-
```js
|
|
84
|
-
// GET /api/search?q=aziz&tag=a&tag=b
|
|
85
|
-
request.query.get('q') // "aziz"
|
|
86
|
-
request.query.getAll('tag') // ["a", "b"]
|
|
87
|
-
```
|
|
88
|
-
|
|
89
|
-
### Cookies
|
|
90
|
-
|
|
91
|
-
```js
|
|
92
|
-
// Cookie: token=abc; theme=dark
|
|
93
|
-
request.cookies.token // "abc"
|
|
94
|
-
request.cookies.theme // "dark"
|
|
95
|
-
```
|
|
96
|
-
|
|
97
|
-
## `NextResponse`
|
|
98
|
-
|
|
99
|
-
Imported from the framework (same API as the Next.js App Router):
|
|
100
|
-
|
|
101
|
-
```js
|
|
102
|
-
const { NextResponse } = require('../_framework/next-response');
|
|
103
|
-
|
|
104
|
-
GET: async () => NextResponse.json({ users: [] }, { status: 200 });
|
|
105
|
-
|
|
106
|
-
POST: async () => NextResponse.json({ error: 'Bad input' }, { status: 400 });
|
|
107
|
-
|
|
108
|
-
GET: async () => NextResponse.text('hello');
|
|
109
|
-
|
|
110
|
-
GET: async () => NextResponse.html('<h1>Hi</h1>');
|
|
111
|
-
|
|
112
|
-
GET: async () => NextResponse.redirect('/login', 307);
|
|
113
|
-
|
|
114
|
-
GET: async () => NextResponse.json({ err: 'nope' }, { status: 500 });
|
|
115
|
-
```
|
|
116
|
-
|
|
117
|
-
### Cookies with `NextResponse`
|
|
118
|
-
|
|
119
|
-
```js
|
|
120
|
-
GET: async () => {
|
|
121
|
-
const res = NextResponse.json({ ok: true });
|
|
122
|
-
res.cookies.set('session', 'abc123', {
|
|
123
|
-
httpOnly: true,
|
|
124
|
-
secure: true,
|
|
125
|
-
maxAge: 60 * 60 * 24 * 7, // 7 days (seconds)
|
|
126
|
-
path: '/',
|
|
127
|
-
});
|
|
128
|
-
res.cookies.delete('old-cookie');
|
|
129
|
-
return res;
|
|
130
|
-
};
|
|
131
|
-
```
|
|
132
|
-
|
|
133
|
-
Headers are emitted as `Set-Cookie` (multiple allowed).
|
|
134
|
-
|
|
135
|
-
## Error handling
|
|
136
|
-
|
|
137
|
-
Throw — the framework maps it to a status:
|
|
138
|
-
|
|
139
|
-
```js
|
|
140
|
-
const err = new Error('User not found');
|
|
141
|
-
err.status = 404;
|
|
142
|
-
throw err; // → 404 { error: 'User not found' }
|
|
143
|
-
|
|
144
|
-
throw new SyntaxError('…'); // → 400
|
|
145
|
-
throw new Error('boom'); // → 500
|
|
146
|
-
```
|
|
147
|
-
|
|
148
|
-
`await request.json()` on a malformed body throws a `SyntaxError` with `status = 400` automatically.
|
|
149
|
-
|
|
150
|
-
Unhandled errors return `500` with `{"error": "…"}` and are logged to the server console.
|
|
151
|
-
|
|
152
|
-
## Method not allowed → 405
|
|
153
|
-
|
|
154
|
-
If a route exists but doesn't export your method:
|
|
155
|
-
|
|
156
|
-
```
|
|
157
|
-
DELETE /api/hello → 405
|
|
158
|
-
Allow: GET
|
|
159
|
-
{ "error": "Method DELETE not allowed", "allowed": ["GET"] }
|
|
160
|
-
```
|
|
161
|
-
|
|
162
|
-
`HEAD` falls back to your `GET` handler automatically (response body suppressed). `OPTIONS` returns `204` with an `Allow` header and CORS preflight headers — useful for cross-origin clients.
|
|
163
|
-
|
|
164
|
-
## CORS
|
|
165
|
-
|
|
166
|
-
API responses automatically include:
|
|
167
|
-
|
|
168
|
-
```
|
|
169
|
-
Access-Control-Allow-Origin: *
|
|
170
|
-
Access-Control-Allow-Methods: GET,POST,PUT,PATCH,DELETE,HEAD,OPTIONS
|
|
171
|
-
Access-Control-Allow-Headers: Content-Type, Authorization
|
|
172
|
-
```
|
|
173
|
-
|
|
174
|
-
## Native ESM (`.mjs`)
|
|
175
|
-
|
|
176
|
-
Server files are CommonJS by default. Use the `.mjs` extension for native ESM:
|
|
177
|
-
|
|
178
|
-
```js
|
|
179
|
-
// api/todos.mjs
|
|
180
|
-
import { NextResponse } from '../_framework/next-response.js';
|
|
181
|
-
|
|
182
|
-
export async function GET() {
|
|
183
|
-
return NextResponse.json({ todos: [] });
|
|
184
|
-
}
|
|
185
|
-
```
|
|
186
|
-
|
|
187
|
-
You can also `export` directly from a `.js` file — the loader falls back to ESM if `require()` reports `ERR_REQUIRE_ESM`.
|
|
188
|
-
|
|
189
|
-
## Legacy `(req, res)` handlers
|
|
190
|
-
|
|
191
|
-
Express/Next-pages style still works — export a function and write the response yourself:
|
|
192
|
-
|
|
193
|
-
```js
|
|
194
|
-
// api/legacy.js
|
|
195
|
-
module.exports = function (req, res) {
|
|
196
|
-
res.status(200).json({ legacy: true, method: req.method });
|
|
197
|
-
};
|
|
198
|
-
```
|
|
199
|
-
|
|
200
|
-
Available on `res`: `status(code)`, `json(data)`, `send(data)`, `setHeader(k, v)`, `redirect(url, code?)`. The `request` passed in is enriched with `params`, `cookies`, `query`, and `nextUrl`.
|
|
201
|
-
|
|
202
|
-
The framework falls back to a `500` if a legacy handler never sends a response.
|
|
203
|
-
|
|
204
|
-
## Hot reload
|
|
205
|
-
|
|
206
|
-
Editing files under `api/`, `lib/`, or `models/` triggers a reload: the server clears the require cache for project files (not `node_modules`) and re-imports your routes. Save → the next request sees new code.
|
|
207
|
-
|
|
208
|
-
## Continue
|
|
209
|
-
|
|
210
|
-
→ [API Client](./07-api-client.md)
|
|
1
|
+
# 06 · API Routes
|
|
2
|
+
|
|
3
|
+
API routes are the **server side** of your app — real Node.js handlers you call over HTTP. They're how you fetch data, handle forms, and run database queries without exposing secrets in the browser.
|
|
4
|
+
|
|
5
|
+
## Where routes live
|
|
6
|
+
|
|
7
|
+
Two equivalent layouts, both producing `/api/**` URLs:
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
api/users.js → /api/users (classic)
|
|
11
|
+
api/users/[id].js → /api/users/:id
|
|
12
|
+
app/api/users/route.js → /api/users (Next.js style)
|
|
13
|
+
app/api/users/[id]/route.js → /api/users/:id
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
- **Classic:** `api/<name>.js` → `/api/<name>`; folders nest: `api/posts/[id]/comments.js`.
|
|
17
|
+
- **Next style:** any `route.js` under `app/api/` → the folder path mapped to `/api/...`.
|
|
18
|
+
|
|
19
|
+
Both can coexist. Use whichever reads better.
|
|
20
|
+
|
|
21
|
+
## Your first handler
|
|
22
|
+
|
|
23
|
+
Export functions named after HTTP methods:
|
|
24
|
+
|
|
25
|
+
```js
|
|
26
|
+
// api/hello.js → /api/hello
|
|
27
|
+
module.exports = {
|
|
28
|
+
GET: async (request) => {
|
|
29
|
+
const name = request.query.get('name') || 'world';
|
|
30
|
+
return { hello: name };
|
|
31
|
+
},
|
|
32
|
+
|
|
33
|
+
POST: async (request) => {
|
|
34
|
+
const body = await request.json();
|
|
35
|
+
return { created: true, ...body };
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
**Return a value — don't manage a raw socket.** The framework turns it into the HTTP response:
|
|
41
|
+
|
|
42
|
+
| You return | Response |
|
|
43
|
+
|------------|----------|
|
|
44
|
+
| `{ ok: true }` / `[]` | `200` + JSON |
|
|
45
|
+
| `'some text'` | `200` + `text/plain` |
|
|
46
|
+
| `NextResponse.json(data, { status: 400 })` | `400` + your headers |
|
|
47
|
+
| `NextResponse.redirect('/login')` | `307` + `Location` |
|
|
48
|
+
| `undefined` / `null` | `500` + a helpful error (you forgot to return) |
|
|
49
|
+
|
|
50
|
+
## The `request` object
|
|
51
|
+
|
|
52
|
+
```js
|
|
53
|
+
GET: async (request) => {
|
|
54
|
+
request.method // 'GET'
|
|
55
|
+
request.url // full URL
|
|
56
|
+
request.params // { id: '42' } from [id] segments
|
|
57
|
+
request.query // URLSearchParams → get('page'), getAll('tag')
|
|
58
|
+
request.headers // raw Node headers (lowercase keys)
|
|
59
|
+
request.cookies // { token: '…' } parsed from Cookie header
|
|
60
|
+
request.rawBody // raw body string
|
|
61
|
+
request.body // parsed JSON if content-type was JSON
|
|
62
|
+
|
|
63
|
+
await request.json() // parse JSON body (throws 400 on invalid)
|
|
64
|
+
await request.text() // raw text
|
|
65
|
+
await request.formData()// urlencoded → { field: 'value' }
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### Dynamic and catch-all params
|
|
70
|
+
|
|
71
|
+
```js
|
|
72
|
+
// api/users/[id].js
|
|
73
|
+
GET: async (request) => request.params.id // /api/users/7 → "7"
|
|
74
|
+
|
|
75
|
+
// api/files/[...path].js
|
|
76
|
+
GET: async (request) => request.params.path // /api/files/a/b → ['a','b']
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
The literal prefix is validated — `api/files/[...path]` won't match `/api/other/x`.
|
|
80
|
+
|
|
81
|
+
### Query strings
|
|
82
|
+
|
|
83
|
+
```js
|
|
84
|
+
// GET /api/search?q=aziz&tag=a&tag=b
|
|
85
|
+
request.query.get('q') // "aziz"
|
|
86
|
+
request.query.getAll('tag') // ["a", "b"]
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### Cookies
|
|
90
|
+
|
|
91
|
+
```js
|
|
92
|
+
// Cookie: token=abc; theme=dark
|
|
93
|
+
request.cookies.token // "abc"
|
|
94
|
+
request.cookies.theme // "dark"
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## `NextResponse`
|
|
98
|
+
|
|
99
|
+
Imported from the framework (same API as the Next.js App Router):
|
|
100
|
+
|
|
101
|
+
```js
|
|
102
|
+
const { NextResponse } = require('../_framework/next-response');
|
|
103
|
+
|
|
104
|
+
GET: async () => NextResponse.json({ users: [] }, { status: 200 });
|
|
105
|
+
|
|
106
|
+
POST: async () => NextResponse.json({ error: 'Bad input' }, { status: 400 });
|
|
107
|
+
|
|
108
|
+
GET: async () => NextResponse.text('hello');
|
|
109
|
+
|
|
110
|
+
GET: async () => NextResponse.html('<h1>Hi</h1>');
|
|
111
|
+
|
|
112
|
+
GET: async () => NextResponse.redirect('/login', 307);
|
|
113
|
+
|
|
114
|
+
GET: async () => NextResponse.json({ err: 'nope' }, { status: 500 });
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### Cookies with `NextResponse`
|
|
118
|
+
|
|
119
|
+
```js
|
|
120
|
+
GET: async () => {
|
|
121
|
+
const res = NextResponse.json({ ok: true });
|
|
122
|
+
res.cookies.set('session', 'abc123', {
|
|
123
|
+
httpOnly: true,
|
|
124
|
+
secure: true,
|
|
125
|
+
maxAge: 60 * 60 * 24 * 7, // 7 days (seconds)
|
|
126
|
+
path: '/',
|
|
127
|
+
});
|
|
128
|
+
res.cookies.delete('old-cookie');
|
|
129
|
+
return res;
|
|
130
|
+
};
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
Headers are emitted as `Set-Cookie` (multiple allowed).
|
|
134
|
+
|
|
135
|
+
## Error handling
|
|
136
|
+
|
|
137
|
+
Throw — the framework maps it to a status:
|
|
138
|
+
|
|
139
|
+
```js
|
|
140
|
+
const err = new Error('User not found');
|
|
141
|
+
err.status = 404;
|
|
142
|
+
throw err; // → 404 { error: 'User not found' }
|
|
143
|
+
|
|
144
|
+
throw new SyntaxError('…'); // → 400
|
|
145
|
+
throw new Error('boom'); // → 500
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
`await request.json()` on a malformed body throws a `SyntaxError` with `status = 400` automatically.
|
|
149
|
+
|
|
150
|
+
Unhandled errors return `500` with `{"error": "…"}` and are logged to the server console.
|
|
151
|
+
|
|
152
|
+
## Method not allowed → 405
|
|
153
|
+
|
|
154
|
+
If a route exists but doesn't export your method:
|
|
155
|
+
|
|
156
|
+
```
|
|
157
|
+
DELETE /api/hello → 405
|
|
158
|
+
Allow: GET
|
|
159
|
+
{ "error": "Method DELETE not allowed", "allowed": ["GET"] }
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
`HEAD` falls back to your `GET` handler automatically (response body suppressed). `OPTIONS` returns `204` with an `Allow` header and CORS preflight headers — useful for cross-origin clients.
|
|
163
|
+
|
|
164
|
+
## CORS
|
|
165
|
+
|
|
166
|
+
API responses automatically include:
|
|
167
|
+
|
|
168
|
+
```
|
|
169
|
+
Access-Control-Allow-Origin: *
|
|
170
|
+
Access-Control-Allow-Methods: GET,POST,PUT,PATCH,DELETE,HEAD,OPTIONS
|
|
171
|
+
Access-Control-Allow-Headers: Content-Type, Authorization
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
## Native ESM (`.mjs`)
|
|
175
|
+
|
|
176
|
+
Server files are CommonJS by default. Use the `.mjs` extension for native ESM:
|
|
177
|
+
|
|
178
|
+
```js
|
|
179
|
+
// api/todos.mjs
|
|
180
|
+
import { NextResponse } from '../_framework/next-response.js';
|
|
181
|
+
|
|
182
|
+
export async function GET() {
|
|
183
|
+
return NextResponse.json({ todos: [] });
|
|
184
|
+
}
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
You can also `export` directly from a `.js` file — the loader falls back to ESM if `require()` reports `ERR_REQUIRE_ESM`.
|
|
188
|
+
|
|
189
|
+
## Legacy `(req, res)` handlers
|
|
190
|
+
|
|
191
|
+
Express/Next-pages style still works — export a function and write the response yourself:
|
|
192
|
+
|
|
193
|
+
```js
|
|
194
|
+
// api/legacy.js
|
|
195
|
+
module.exports = function (req, res) {
|
|
196
|
+
res.status(200).json({ legacy: true, method: req.method });
|
|
197
|
+
};
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
Available on `res`: `status(code)`, `json(data)`, `send(data)`, `setHeader(k, v)`, `redirect(url, code?)`. The `request` passed in is enriched with `params`, `cookies`, `query`, and `nextUrl`.
|
|
201
|
+
|
|
202
|
+
The framework falls back to a `500` if a legacy handler never sends a response.
|
|
203
|
+
|
|
204
|
+
## Hot reload
|
|
205
|
+
|
|
206
|
+
Editing files under `api/`, `lib/`, or `models/` triggers a reload: the server clears the require cache for project files (not `node_modules`) and re-imports your routes. Save → the next request sees new code.
|
|
207
|
+
|
|
208
|
+
## Continue
|
|
209
|
+
|
|
210
|
+
→ [API Client](./07-api-client.md)
|