nibula 1.6.0 → 1.6.2
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/.eleventy.js +82 -81
- package/CHANGELOG.md +395 -377
- package/LICENSE +169 -169
- package/NOTICE +4 -4
- package/README.md +110 -110
- package/bin/create.js +540 -505
- package/bin/delegate.js +58 -57
- package/bin/nibula.js +302 -302
- package/docs/Assistant CLI.md +65 -65
- package/docs/Backend.md +201 -201
- package/docs/Components.md +140 -140
- package/docs/Creating pages.md +69 -69
- package/docs/Deploy.md +210 -210
- package/docs/Head and SEO.md +139 -139
- package/docs/Javascript.md +126 -126
- package/docs/Styling with SCSS.md +141 -139
- package/nginx.conf +113 -113
- package/package.json +69 -69
- package/src/backend/.htaccess +6 -6
- package/src/backend/_core/composer.json +5 -5
- package/src/backend/_core/composer.lock +492 -492
- package/src/backend/_core/index.js +267 -267
- package/src/backend/_core/index.php +160 -160
- package/src/backend/_core/init.js +52 -52
- package/src/backend/_core/init.php +33 -33
- package/src/backend/_core/modules/RateLimiter.js +58 -58
- package/src/backend/_core/modules/RateLimiter.php +30 -30
- package/src/backend/_core/modules/Response.js +59 -59
- package/src/backend/_core/modules/Response.php +48 -48
- package/src/backend/api/protected/example-protected.js +23 -23
- package/src/backend/api/protected/example-protected.php +16 -16
- package/src/backend/api/public/example-public.js +24 -24
- package/src/backend/api/public/example-public.php +16 -16
- package/src/backend/database/Database.js +46 -46
- package/src/backend/database/Database.php +23 -23
- package/src/backend/example.config.js +37 -37
- package/src/backend/example.config.php +37 -37
- package/src/backend/package.json +17 -17
- package/src/backend/web.config +43 -43
- package/src/frontend/.htaccess +51 -51
- package/src/frontend/404.njk +17 -17
- package/src/frontend/assets/brand/favicon.svg +54 -54
- package/src/frontend/assets/brand/logo.svg +54 -54
- package/src/frontend/components/global/footer.njk +25 -25
- package/src/frontend/components/global/header.njk +6 -6
- package/src/frontend/components/welcome.njk +115 -115
- package/src/frontend/index.njk +8 -8
- package/src/frontend/js/global.js +10 -10
- package/src/frontend/js/modules/exampleModule.js +2 -2
- package/src/frontend/js/pages/404.js +8 -8
- package/src/frontend/js/pages/homepage.js +8 -8
- package/src/frontend/layouts/base.njk +135 -136
- package/src/frontend/layouts/page-components.njk +13 -13
- package/src/frontend/llms.njk +17 -17
- package/src/frontend/robots.njk +7 -7
- package/src/frontend/scss/_global.scss +46 -46
- package/src/frontend/scss/_root.scss +13 -13
- package/src/frontend/scss/modules/_animations.scss +24 -24
- package/src/frontend/scss/modules/_footer.scss +28 -28
- package/src/frontend/scss/modules/_header.scss +26 -26
- package/src/frontend/scss/modules/_mobile.scss +29 -29
- package/src/frontend/scss/modules/_typography.scss +18 -18
- package/src/frontend/scss/modules/frameworks/_bootstrap.scss +109 -109
- package/src/frontend/scss/modules/frameworks/_bulma.scss +108 -108
- package/src/frontend/scss/modules/frameworks/_foundation.scss +137 -137
- package/src/frontend/scss/modules/frameworks/_uikit.scss +109 -109
- package/src/frontend/scss/pages/404.scss +27 -27
- package/src/frontend/scss/pages/homepage.scss +22 -22
- package/src/frontend/sitemap.njk +17 -17
- package/src/frontend/ts/global.ts +11 -11
- package/src/frontend/ts/modules/exampleModule.ts +2 -2
- package/src/frontend/ts/pages/404.ts +8 -8
- package/src/frontend/ts/pages/homepage.ts +8 -8
- package/src/frontend/web.config +51 -51
- package/tools/assistant.js +122 -122
- package/tools/buildJs.js +39 -39
- package/tools/cleanOutput.js +23 -23
- package/tools/cli/prompt.js +40 -40
- package/tools/cli/ui.js +87 -87
- package/tools/config/messages.json +80 -80
- package/tools/config/settings.json +130 -130
- package/tools/lib/colors.js +17 -17
- package/tools/lib/files.js +75 -75
- package/tools/lib/logger.js +22 -22
- package/tools/lib/outputPath.js +141 -141
- package/tools/lib/pageActions.js +146 -146
- package/tools/lib/pageArtifacts.js +46 -46
- package/tools/lib/pageComponents.js +88 -88
- package/tools/lib/pagesData.js +87 -87
- package/tools/lib/paths.js +63 -63
- package/tools/lib/project.js +54 -54
- package/tools/lib/text.js +47 -47
- package/tools/lib/validation.js +40 -40
- package/tools/res/templates/template.js +7 -7
- package/tools/res/templates/template.njk +8 -8
- package/tools/res/templates/template.scss +22 -22
- package/tools/res/templates/template.ts +8 -8
- package/tsconfig.json +24 -24
- package/src/frontend/scss/pages/prova.scss +0 -23
package/docs/Assistant CLI.md
CHANGED
|
@@ -1,66 +1,66 @@
|
|
|
1
|
-
# Assistant CLI
|
|
2
|
-
|
|
3
|
-
> Examples use JavaScript, but everything applies equally to TypeScript. The only difference is the file extension (`.ts` instead of `.js`), that imports do **not** include the extension, and that paths use `src/frontend/ts/` instead of `src/frontend/js/`.
|
|
4
|
-
|
|
5
|
-
An interactive CLI to manage pages without touching files manually.
|
|
6
|
-
|
|
7
|
-
Run it from anywhere inside a project:
|
|
8
|
-
|
|
9
|
-
```
|
|
10
|
-
nib cli
|
|
11
|
-
```
|
|
12
|
-
|
|
13
|
-
## Menu
|
|
14
|
-
|
|
15
|
-
```
|
|
16
|
-
1. Create page
|
|
17
|
-
2. Remove page
|
|
18
|
-
3. Rename page
|
|
19
|
-
4. Configure output path
|
|
20
|
-
```
|
|
21
|
-
|
|
22
|
-
Use `CTRL/CMD + C` (or `CTRL + D`) to exit.
|
|
23
|
-
|
|
24
|
-
## Create page
|
|
25
|
-
|
|
26
|
-
Enter a page name in any format — the CLI converts it to kebab-case automatically.
|
|
27
|
-
|
|
28
|
-
For a page named `my-page`, the following files are created:
|
|
29
|
-
|
|
30
|
-
| File | Purpose |
|
|
31
|
-
|---|---|
|
|
32
|
-
| `src/frontend/scss/pages/myPage.scss` | SCSS entry point |
|
|
33
|
-
| `src/frontend/js/pages/myPage.js` | JS entry point |
|
|
34
|
-
| `src/frontend/routes/my-page.njk` | Nunjucks template |
|
|
35
|
-
|
|
36
|
-
It also adds an `elif` block in `page-components.njk` and a stub entry in `pages.json`:
|
|
37
|
-
|
|
38
|
-
```json
|
|
39
|
-
"myPage": {
|
|
40
|
-
"seo": {
|
|
41
|
-
"title": "My page",
|
|
42
|
-
"description": "Description",
|
|
43
|
-
"keywords": "",
|
|
44
|
-
"noindex": false,
|
|
45
|
-
"canonical": ""
|
|
46
|
-
},
|
|
47
|
-
"cdn": {
|
|
48
|
-
"css": [],
|
|
49
|
-
"js": []
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
```
|
|
53
|
-
|
|
54
|
-
## Remove page
|
|
55
|
-
|
|
56
|
-
Deletes all source files for the page and cleans up the output directory, `page-components.njk`, and `pages.json`.
|
|
57
|
-
|
|
58
|
-
## Rename page
|
|
59
|
-
|
|
60
|
-
Renames all three source files, updates the `elif` block in `page-components.njk`, and renames the record in `pages.json`. Nothing inside the record is touched — your SEO title, description and CDN links stay exactly as you wrote them.
|
|
61
|
-
|
|
62
|
-
## Configure output path
|
|
63
|
-
|
|
64
|
-
Updates the output directory across `.eleventy.js` and all relevant `package.json` scripts in one shot. The old output folder is deleted automatically.
|
|
65
|
-
|
|
1
|
+
# Assistant CLI
|
|
2
|
+
|
|
3
|
+
> Examples use JavaScript, but everything applies equally to TypeScript. The only difference is the file extension (`.ts` instead of `.js`), that imports do **not** include the extension, and that paths use `src/frontend/ts/` instead of `src/frontend/js/`.
|
|
4
|
+
|
|
5
|
+
An interactive CLI to manage pages without touching files manually.
|
|
6
|
+
|
|
7
|
+
Run it from anywhere inside a project:
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
nib cli
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Menu
|
|
14
|
+
|
|
15
|
+
```
|
|
16
|
+
1. Create page
|
|
17
|
+
2. Remove page
|
|
18
|
+
3. Rename page
|
|
19
|
+
4. Configure output path
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Use `CTRL/CMD + C` (or `CTRL + D`) to exit.
|
|
23
|
+
|
|
24
|
+
## Create page
|
|
25
|
+
|
|
26
|
+
Enter a page name in any format — the CLI converts it to kebab-case automatically.
|
|
27
|
+
|
|
28
|
+
For a page named `my-page`, the following files are created:
|
|
29
|
+
|
|
30
|
+
| File | Purpose |
|
|
31
|
+
|---|---|
|
|
32
|
+
| `src/frontend/scss/pages/myPage.scss` | SCSS entry point |
|
|
33
|
+
| `src/frontend/js/pages/myPage.js` | JS entry point |
|
|
34
|
+
| `src/frontend/routes/my-page.njk` | Nunjucks template |
|
|
35
|
+
|
|
36
|
+
It also adds an `elif` block in `page-components.njk` and a stub entry in `pages.json`:
|
|
37
|
+
|
|
38
|
+
```json
|
|
39
|
+
"myPage": {
|
|
40
|
+
"seo": {
|
|
41
|
+
"title": "My page",
|
|
42
|
+
"description": "Description",
|
|
43
|
+
"keywords": "",
|
|
44
|
+
"noindex": false,
|
|
45
|
+
"canonical": ""
|
|
46
|
+
},
|
|
47
|
+
"cdn": {
|
|
48
|
+
"css": [],
|
|
49
|
+
"js": []
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Remove page
|
|
55
|
+
|
|
56
|
+
Deletes all source files for the page and cleans up the output directory, `page-components.njk`, and `pages.json`.
|
|
57
|
+
|
|
58
|
+
## Rename page
|
|
59
|
+
|
|
60
|
+
Renames all three source files, updates the `elif` block in `page-components.njk`, and renames the record in `pages.json`. Nothing inside the record is touched — your SEO title, description and CDN links stay exactly as you wrote them.
|
|
61
|
+
|
|
62
|
+
## Configure output path
|
|
63
|
+
|
|
64
|
+
Updates the output directory across `.eleventy.js` and all relevant `package.json` scripts in one shot. The old output folder is deleted automatically.
|
|
65
|
+
|
|
66
66
|
> ⚠️ `homepage` and `404` are protected — they cannot be created, removed, or renamed via the CLI.
|
package/docs/Backend.md
CHANGED
|
@@ -1,202 +1,202 @@
|
|
|
1
|
-
# Backend
|
|
2
|
-
|
|
3
|
-
Nibula ships **two interchangeable backends** — PHP and Node.js. You pick one
|
|
4
|
-
when you scaffold a project with `nib new`, and **only that one is copied into
|
|
5
|
-
your project**: a Node project contains no PHP files and vice versa. Shared,
|
|
6
|
-
backend-agnostic files (SQL migrations, `.htaccess`, `web.config`) are always
|
|
7
|
-
included.
|
|
8
|
-
|
|
9
|
-
Both provide the same REST API: same routing, same `Response` envelope, same
|
|
10
|
-
`X-Api-Key` auth, same CORS model, same rate limiter. Only the syntax differs.
|
|
11
|
-
|
|
12
|
-
> **The examples below are in JavaScript (the Node backend).** If you chose PHP,
|
|
13
|
-
> everything works exactly the same way — same routing, same variables, same
|
|
14
|
-
> `Response` helper, same behaviour. Only the syntax differs.
|
|
15
|
-
|
|
16
|
-
For **where each backend can run** and how to deploy it, see `docs/Deploy.md`.
|
|
17
|
-
|
|
18
|
-
## Structure
|
|
19
|
-
|
|
20
|
-
The tree below shows both backends for reference; your project will have only
|
|
21
|
-
one side of each pair.
|
|
22
|
-
|
|
23
|
-
```
|
|
24
|
-
src/backend/
|
|
25
|
-
├── api/
|
|
26
|
-
│ ├── public/ # Endpoints accessible without an API key
|
|
27
|
-
│ └── protected/ # Endpoints requiring the X-Api-Key header
|
|
28
|
-
├── _core/ # Framework internals (routing, modules) — do not edit
|
|
29
|
-
│ ├── index.php # PHP front controller (PHP project)
|
|
30
|
-
│ ├── index.js # Node front controller + server (Node project)
|
|
31
|
-
│ ├── init.php / init.js
|
|
32
|
-
│ └── modules/ # Response, RateLimiter
|
|
33
|
-
├── database/
|
|
34
|
-
│ ├── Database.php # PDO singleton (PHP project)
|
|
35
|
-
│ ├── Database.js # mysql2 pool singleton (Node project)
|
|
36
|
-
│ └── migrations/ # shared
|
|
37
|
-
├── example.config.php / example.config.js # versioned template
|
|
38
|
-
└── config.php / config.js # generated on setup, never commit
|
|
39
|
-
```
|
|
40
|
-
|
|
41
|
-
> `config.php` / `config.js` are created automatically by `nib new` as a copy of
|
|
42
|
-
> the matching `example.config.*`, and are git-ignored so your secrets stay
|
|
43
|
-
> local. If `config.*` is missing (e.g. after a fresh clone), copy the example.
|
|
44
|
-
|
|
45
|
-
## How routing works
|
|
46
|
-
|
|
47
|
-
The file path inside `api/` maps directly to the URL. The `public` / `protected`
|
|
48
|
-
folder does **not** appear in the URL — it only decides whether the `X-Api-Key`
|
|
49
|
-
check applies:
|
|
50
|
-
|
|
51
|
-
- `api/public/example-public` → `/api/example-public` (no key)
|
|
52
|
-
- `api/protected/example-protected` → `/api/example-protected` (requires key)
|
|
53
|
-
|
|
54
|
-
Subfolders **do** appear: `api/public/users/list` → `/api/users/list`.
|
|
55
|
-
|
|
56
|
-
Extra URL segments become route parameters, resolved from the deepest match
|
|
57
|
-
backwards: `/api/posts/42` looks for `posts/42` first, then falls back to
|
|
58
|
-
`posts` with `42` as a parameter.
|
|
59
|
-
|
|
60
|
-
Every endpoint receives:
|
|
61
|
-
|
|
62
|
-
| Field | Description |
|
|
63
|
-
|---|---|
|
|
64
|
-
| `method` | HTTP method (`GET`, `POST`, `PUT`, `PATCH`, `DELETE`) |
|
|
65
|
-
| `requestParams` | Extra URL segments (e.g. `/api/posts/42` → `['42']`) |
|
|
66
|
-
|
|
67
|
-
The Node handler also gets `query`, `body` (parsed JSON), `rawBody`, `headers`,
|
|
68
|
-
`config`, `req`, `res`.
|
|
69
|
-
|
|
70
|
-
> ⚠️ If two files share the same path — one in `public/`, one in `protected/` —
|
|
71
|
-
> the public one wins, so the endpoint ends up **without** authentication. Don't
|
|
72
|
-
> duplicate names across the two folders.
|
|
73
|
-
|
|
74
|
-
## Creating an endpoint
|
|
75
|
-
|
|
76
|
-
Create a file anywhere inside `api/public/` or `api/protected/`. For protected
|
|
77
|
-
endpoints the API-key check runs automatically before your file does.
|
|
78
|
-
|
|
79
|
-
```js
|
|
80
|
-
module.exports = ({ method, requestParams, Response }) => {
|
|
81
|
-
if (method !== 'GET') Response.error('Method not allowed', 405);
|
|
82
|
-
const id = requestParams[0] ? parseInt(requestParams[0], 10) : null;
|
|
83
|
-
Response.success({ id });
|
|
84
|
-
};
|
|
85
|
-
```
|
|
86
|
-
|
|
87
|
-
## The Response helper
|
|
88
|
-
|
|
89
|
-
Same envelope in both backends.
|
|
90
|
-
|
|
91
|
-
```js
|
|
92
|
-
Response.success(data, code); // default 200
|
|
93
|
-
Response.error(message, code, details); // default 400
|
|
94
|
-
Response.noContent(); // 204
|
|
95
|
-
```
|
|
96
|
-
|
|
97
|
-
Shapes:
|
|
98
|
-
|
|
99
|
-
- success → `{ "status": "success", "data": ... }`
|
|
100
|
-
- error → `{ "status": "error", "message": "...", "code": ... }` (+ optional `details`)
|
|
101
|
-
|
|
102
|
-
## Configuration
|
|
103
|
-
|
|
104
|
-
`config.php` / `config.js` work like a `.env` file: they hold secrets and
|
|
105
|
-
environment settings that stay local and out of version control. Same keys in
|
|
106
|
-
both, PHP-array vs JS-object syntax.
|
|
107
|
-
|
|
108
|
-
```js
|
|
109
|
-
module.exports = {
|
|
110
|
-
// Default key for protected endpoints that don't have a specific key in CUSTOM_ENDPOINT_KEYS
|
|
111
|
-
GENERAL_API_KEY: 'DEFAULT_KEY',
|
|
112
|
-
|
|
113
|
-
// If you want to restrict access to protected endpoints to specific clients, define custom keys per endpoint.
|
|
114
|
-
// For subfolder endpoints, use the relative path ('subfolder/endpoint')
|
|
115
|
-
CUSTOM_ENDPOINT_KEYS: {
|
|
116
|
-
'subfolder/example-protected': 'custom-key',
|
|
117
|
-
},
|
|
118
|
-
|
|
119
|
-
GENERAL_ALLOWED_ORIGINS: [
|
|
120
|
-
'*',
|
|
121
|
-
// 'https://example.com',
|
|
122
|
-
],
|
|
123
|
-
|
|
124
|
-
CUSTOM_ENDPOINT_ORIGINS: {
|
|
125
|
-
'subfolder/example-protected': ['https://app.example.com'],
|
|
126
|
-
},
|
|
127
|
-
|
|
128
|
-
// Database configuration
|
|
129
|
-
DB_HOST: '127.0.0.1',
|
|
130
|
-
DB_NAME: 'example_db',
|
|
131
|
-
DB_USER: 'root',
|
|
132
|
-
DB_PASS: '',
|
|
133
|
-
|
|
134
|
-
// Environment: 'production' hides error details; anything else = debug.
|
|
135
|
-
APP_ENV: 'production',
|
|
136
|
-
};
|
|
137
|
-
```
|
|
138
|
-
|
|
139
|
-
### API keys
|
|
140
|
-
|
|
141
|
-
`GENERAL_API_KEY` is the fallback for all protected endpoints.
|
|
142
|
-
`CUSTOM_ENDPOINT_KEYS` assigns a different key to a specific endpoint, keyed by
|
|
143
|
-
its relative path (`'subfolder/endpoint'`).
|
|
144
|
-
|
|
145
|
-
> ⚠️ The key travels in the `X-Api-Key` header on every request. Use it only for
|
|
146
|
-
> server-to-server calls over HTTPS. Never embed it in frontend code, where it
|
|
147
|
-
> would be publicly visible.
|
|
148
|
-
|
|
149
|
-
### CORS
|
|
150
|
-
|
|
151
|
-
Mirrors the API-key model: `GENERAL_ALLOWED_ORIGINS` is the default list,
|
|
152
|
-
`CUSTOM_ENDPOINT_ORIGINS` overrides it per endpoint using the same relative path.
|
|
153
|
-
|
|
154
|
-
Origins must be exact (scheme + host, no trailing slash). A matching `Origin` is
|
|
155
|
-
reflected in `Access-Control-Allow-Origin` with `Vary: Origin`. An empty list
|
|
156
|
-
sends no CORS header — the most restrictive setting; same-origin requests still
|
|
157
|
-
work. `'*'` as the only element allows any origin (not recommended for protected
|
|
158
|
-
endpoints).
|
|
159
|
-
|
|
160
|
-
Resolution order: `CUSTOM_ENDPOINT_ORIGINS[path]` if present, otherwise
|
|
161
|
-
`GENERAL_ALLOWED_ORIGINS`.
|
|
162
|
-
|
|
163
|
-
### Error verbosity
|
|
164
|
-
|
|
165
|
-
`APP_ENV` decides what an unhandled exception tells the client.
|
|
166
|
-
|
|
167
|
-
| Value | Response to a 500 |
|
|
168
|
-
|---|---|
|
|
169
|
-
| `'production'` (default) | `{"status":"error","message":"Internal server error","code":500}` |
|
|
170
|
-
| anything else | the real exception message, plus `stack` (Node) or `file` and `line` (PHP) |
|
|
171
|
-
|
|
172
|
-
On PHP it does one extra thing: `'production'` also turns off `display_errors`
|
|
173
|
-
and `error_reporting`, so a stray warning can't be printed before the JSON and
|
|
174
|
-
corrupt the response body.
|
|
175
|
-
|
|
176
|
-
Set it to something like `'development'` while building, and back to
|
|
177
|
-
`'production'` before you publish. Missing key means `'production'`.
|
|
178
|
-
|
|
179
|
-
> This is a config key, **not** an environment variable. Setting `APP_ENV` in a
|
|
180
|
-
> shell or in a systemd unit has no effect — both backends read it from
|
|
181
|
-
> `config.js` / `config.php` only.
|
|
182
|
-
|
|
183
|
-
## Database
|
|
184
|
-
|
|
185
|
-
Both backends expose a connection singleton reading the same config —
|
|
186
|
-
`database/Database.js` (a `mysql2` pool) or `database/Database.php` (a PDO
|
|
187
|
-
singleton).
|
|
188
|
-
|
|
189
|
-
```js
|
|
190
|
-
const pool = Database.getInstance();
|
|
191
|
-
const [rows] = await pool.execute(sql, params);
|
|
192
|
-
```
|
|
193
|
-
|
|
194
|
-
`mysql2` is installed at scaffold time and loaded lazily, so the backend boots
|
|
195
|
-
fine without a database.
|
|
196
|
-
|
|
197
|
-
## Pre-built endpoints
|
|
198
|
-
|
|
199
|
-
| Route | Method | Description |
|
|
200
|
-
|---|---|---|
|
|
201
|
-
| `/api/example-public` | `GET` | Example endpoint requiring no key |
|
|
1
|
+
# Backend
|
|
2
|
+
|
|
3
|
+
Nibula ships **two interchangeable backends** — PHP and Node.js. You pick one
|
|
4
|
+
when you scaffold a project with `nib new`, and **only that one is copied into
|
|
5
|
+
your project**: a Node project contains no PHP files and vice versa. Shared,
|
|
6
|
+
backend-agnostic files (SQL migrations, `.htaccess`, `web.config`) are always
|
|
7
|
+
included.
|
|
8
|
+
|
|
9
|
+
Both provide the same REST API: same routing, same `Response` envelope, same
|
|
10
|
+
`X-Api-Key` auth, same CORS model, same rate limiter. Only the syntax differs.
|
|
11
|
+
|
|
12
|
+
> **The examples below are in JavaScript (the Node backend).** If you chose PHP,
|
|
13
|
+
> everything works exactly the same way — same routing, same variables, same
|
|
14
|
+
> `Response` helper, same behaviour. Only the syntax differs.
|
|
15
|
+
|
|
16
|
+
For **where each backend can run** and how to deploy it, see `docs/Deploy.md`.
|
|
17
|
+
|
|
18
|
+
## Structure
|
|
19
|
+
|
|
20
|
+
The tree below shows both backends for reference; your project will have only
|
|
21
|
+
one side of each pair.
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
src/backend/
|
|
25
|
+
├── api/
|
|
26
|
+
│ ├── public/ # Endpoints accessible without an API key
|
|
27
|
+
│ └── protected/ # Endpoints requiring the X-Api-Key header
|
|
28
|
+
├── _core/ # Framework internals (routing, modules) — do not edit
|
|
29
|
+
│ ├── index.php # PHP front controller (PHP project)
|
|
30
|
+
│ ├── index.js # Node front controller + server (Node project)
|
|
31
|
+
│ ├── init.php / init.js
|
|
32
|
+
│ └── modules/ # Response, RateLimiter
|
|
33
|
+
├── database/
|
|
34
|
+
│ ├── Database.php # PDO singleton (PHP project)
|
|
35
|
+
│ ├── Database.js # mysql2 pool singleton (Node project)
|
|
36
|
+
│ └── migrations/ # shared
|
|
37
|
+
├── example.config.php / example.config.js # versioned template
|
|
38
|
+
└── config.php / config.js # generated on setup, never commit
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
> `config.php` / `config.js` are created automatically by `nib new` as a copy of
|
|
42
|
+
> the matching `example.config.*`, and are git-ignored so your secrets stay
|
|
43
|
+
> local. If `config.*` is missing (e.g. after a fresh clone), copy the example.
|
|
44
|
+
|
|
45
|
+
## How routing works
|
|
46
|
+
|
|
47
|
+
The file path inside `api/` maps directly to the URL. The `public` / `protected`
|
|
48
|
+
folder does **not** appear in the URL — it only decides whether the `X-Api-Key`
|
|
49
|
+
check applies:
|
|
50
|
+
|
|
51
|
+
- `api/public/example-public` → `/api/example-public` (no key)
|
|
52
|
+
- `api/protected/example-protected` → `/api/example-protected` (requires key)
|
|
53
|
+
|
|
54
|
+
Subfolders **do** appear: `api/public/users/list` → `/api/users/list`.
|
|
55
|
+
|
|
56
|
+
Extra URL segments become route parameters, resolved from the deepest match
|
|
57
|
+
backwards: `/api/posts/42` looks for `posts/42` first, then falls back to
|
|
58
|
+
`posts` with `42` as a parameter.
|
|
59
|
+
|
|
60
|
+
Every endpoint receives:
|
|
61
|
+
|
|
62
|
+
| Field | Description |
|
|
63
|
+
|---|---|
|
|
64
|
+
| `method` | HTTP method (`GET`, `POST`, `PUT`, `PATCH`, `DELETE`) |
|
|
65
|
+
| `requestParams` | Extra URL segments (e.g. `/api/posts/42` → `['42']`) |
|
|
66
|
+
|
|
67
|
+
The Node handler also gets `query`, `body` (parsed JSON), `rawBody`, `headers`,
|
|
68
|
+
`config`, `req`, `res`.
|
|
69
|
+
|
|
70
|
+
> ⚠️ If two files share the same path — one in `public/`, one in `protected/` —
|
|
71
|
+
> the public one wins, so the endpoint ends up **without** authentication. Don't
|
|
72
|
+
> duplicate names across the two folders.
|
|
73
|
+
|
|
74
|
+
## Creating an endpoint
|
|
75
|
+
|
|
76
|
+
Create a file anywhere inside `api/public/` or `api/protected/`. For protected
|
|
77
|
+
endpoints the API-key check runs automatically before your file does.
|
|
78
|
+
|
|
79
|
+
```js
|
|
80
|
+
module.exports = ({ method, requestParams, Response }) => {
|
|
81
|
+
if (method !== 'GET') Response.error('Method not allowed', 405);
|
|
82
|
+
const id = requestParams[0] ? parseInt(requestParams[0], 10) : null;
|
|
83
|
+
Response.success({ id });
|
|
84
|
+
};
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## The Response helper
|
|
88
|
+
|
|
89
|
+
Same envelope in both backends.
|
|
90
|
+
|
|
91
|
+
```js
|
|
92
|
+
Response.success(data, code); // default 200
|
|
93
|
+
Response.error(message, code, details); // default 400
|
|
94
|
+
Response.noContent(); // 204
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Shapes:
|
|
98
|
+
|
|
99
|
+
- success → `{ "status": "success", "data": ... }`
|
|
100
|
+
- error → `{ "status": "error", "message": "...", "code": ... }` (+ optional `details`)
|
|
101
|
+
|
|
102
|
+
## Configuration
|
|
103
|
+
|
|
104
|
+
`config.php` / `config.js` work like a `.env` file: they hold secrets and
|
|
105
|
+
environment settings that stay local and out of version control. Same keys in
|
|
106
|
+
both, PHP-array vs JS-object syntax.
|
|
107
|
+
|
|
108
|
+
```js
|
|
109
|
+
module.exports = {
|
|
110
|
+
// Default key for protected endpoints that don't have a specific key in CUSTOM_ENDPOINT_KEYS
|
|
111
|
+
GENERAL_API_KEY: 'DEFAULT_KEY',
|
|
112
|
+
|
|
113
|
+
// If you want to restrict access to protected endpoints to specific clients, define custom keys per endpoint.
|
|
114
|
+
// For subfolder endpoints, use the relative path ('subfolder/endpoint')
|
|
115
|
+
CUSTOM_ENDPOINT_KEYS: {
|
|
116
|
+
'subfolder/example-protected': 'custom-key',
|
|
117
|
+
},
|
|
118
|
+
|
|
119
|
+
GENERAL_ALLOWED_ORIGINS: [
|
|
120
|
+
'*',
|
|
121
|
+
// 'https://example.com',
|
|
122
|
+
],
|
|
123
|
+
|
|
124
|
+
CUSTOM_ENDPOINT_ORIGINS: {
|
|
125
|
+
'subfolder/example-protected': ['https://app.example.com'],
|
|
126
|
+
},
|
|
127
|
+
|
|
128
|
+
// Database configuration
|
|
129
|
+
DB_HOST: '127.0.0.1',
|
|
130
|
+
DB_NAME: 'example_db',
|
|
131
|
+
DB_USER: 'root',
|
|
132
|
+
DB_PASS: '',
|
|
133
|
+
|
|
134
|
+
// Environment: 'production' hides error details; anything else = debug.
|
|
135
|
+
APP_ENV: 'production',
|
|
136
|
+
};
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### API keys
|
|
140
|
+
|
|
141
|
+
`GENERAL_API_KEY` is the fallback for all protected endpoints.
|
|
142
|
+
`CUSTOM_ENDPOINT_KEYS` assigns a different key to a specific endpoint, keyed by
|
|
143
|
+
its relative path (`'subfolder/endpoint'`).
|
|
144
|
+
|
|
145
|
+
> ⚠️ The key travels in the `X-Api-Key` header on every request. Use it only for
|
|
146
|
+
> server-to-server calls over HTTPS. Never embed it in frontend code, where it
|
|
147
|
+
> would be publicly visible.
|
|
148
|
+
|
|
149
|
+
### CORS
|
|
150
|
+
|
|
151
|
+
Mirrors the API-key model: `GENERAL_ALLOWED_ORIGINS` is the default list,
|
|
152
|
+
`CUSTOM_ENDPOINT_ORIGINS` overrides it per endpoint using the same relative path.
|
|
153
|
+
|
|
154
|
+
Origins must be exact (scheme + host, no trailing slash). A matching `Origin` is
|
|
155
|
+
reflected in `Access-Control-Allow-Origin` with `Vary: Origin`. An empty list
|
|
156
|
+
sends no CORS header — the most restrictive setting; same-origin requests still
|
|
157
|
+
work. `'*'` as the only element allows any origin (not recommended for protected
|
|
158
|
+
endpoints).
|
|
159
|
+
|
|
160
|
+
Resolution order: `CUSTOM_ENDPOINT_ORIGINS[path]` if present, otherwise
|
|
161
|
+
`GENERAL_ALLOWED_ORIGINS`.
|
|
162
|
+
|
|
163
|
+
### Error verbosity
|
|
164
|
+
|
|
165
|
+
`APP_ENV` decides what an unhandled exception tells the client.
|
|
166
|
+
|
|
167
|
+
| Value | Response to a 500 |
|
|
168
|
+
|---|---|
|
|
169
|
+
| `'production'` (default) | `{"status":"error","message":"Internal server error","code":500}` |
|
|
170
|
+
| anything else | the real exception message, plus `stack` (Node) or `file` and `line` (PHP) |
|
|
171
|
+
|
|
172
|
+
On PHP it does one extra thing: `'production'` also turns off `display_errors`
|
|
173
|
+
and `error_reporting`, so a stray warning can't be printed before the JSON and
|
|
174
|
+
corrupt the response body.
|
|
175
|
+
|
|
176
|
+
Set it to something like `'development'` while building, and back to
|
|
177
|
+
`'production'` before you publish. Missing key means `'production'`.
|
|
178
|
+
|
|
179
|
+
> This is a config key, **not** an environment variable. Setting `APP_ENV` in a
|
|
180
|
+
> shell or in a systemd unit has no effect — both backends read it from
|
|
181
|
+
> `config.js` / `config.php` only.
|
|
182
|
+
|
|
183
|
+
## Database
|
|
184
|
+
|
|
185
|
+
Both backends expose a connection singleton reading the same config —
|
|
186
|
+
`database/Database.js` (a `mysql2` pool) or `database/Database.php` (a PDO
|
|
187
|
+
singleton).
|
|
188
|
+
|
|
189
|
+
```js
|
|
190
|
+
const pool = Database.getInstance();
|
|
191
|
+
const [rows] = await pool.execute(sql, params);
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
`mysql2` is installed at scaffold time and loaded lazily, so the backend boots
|
|
195
|
+
fine without a database.
|
|
196
|
+
|
|
197
|
+
## Pre-built endpoints
|
|
198
|
+
|
|
199
|
+
| Route | Method | Description |
|
|
200
|
+
|---|---|---|
|
|
201
|
+
| `/api/example-public` | `GET` | Example endpoint requiring no key |
|
|
202
202
|
| `/api/example-protected` | `GET` | Example endpoint requiring `X-Api-Key` |
|