nibula 2.4.3 → 2.4.4
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/CHANGELOG.md +24 -0
- package/README.md +82 -53
- package/package.json +7 -7
- package/src/frontend/components/global/footer.njk +1 -1
- package/src/frontend/components/global/header.njk +1 -1
- package/src/frontend/components/welcome.njk +1 -2
- package/src/frontend/data/site.json +5 -2
- package/src/frontend/indexing/llms.njk +2 -2
- package/src/frontend/layouts/base.njk +5 -4
- package/docs/Assistant CLI.md +0 -70
- package/docs/Backend.md +0 -219
- package/docs/Components.md +0 -236
- package/docs/Creating pages.md +0 -52
- package/docs/Deploy.md +0 -279
- package/docs/Head and SEO.md +0 -198
- package/docs/JavaScript.md +0 -127
- package/docs/Project structure.md +0 -66
- package/docs/Styling with SCSS.md +0 -147
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,30 @@ All notable changes to Nibula are documented here.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [2.4.4] - 2026-08-26
|
|
9
|
+
|
|
10
|
+
### Changed
|
|
11
|
+
- **`site_name` is now `siteName` and `author` is an object** with `fullName` and
|
|
12
|
+
`website`. The author's site feeds the JSON-LD `Person.url` and the `llms.txt`
|
|
13
|
+
header, which previously reused the site URL for both.
|
|
14
|
+
- The documentation moved out of the repository and onto
|
|
15
|
+
[rhaastrake.github.io/Nibula](https://rhaastrake.github.io/Nibula/docs/). The
|
|
16
|
+
`docs/` folder now holds the built site and is excluded from the published
|
|
17
|
+
package, so `npm install` no longer carries the Markdown files.
|
|
18
|
+
- The README follows the same structure as the documentation's introduction, and
|
|
19
|
+
points at the site instead of the files that used to live in `docs/`.
|
|
20
|
+
|
|
21
|
+
### Fixed
|
|
22
|
+
- The documentation card in `welcome.njk` linked to `Nibula/tree/main/docs`,
|
|
23
|
+
which now holds the built site rather than readable Markdown.
|
|
24
|
+
- `welcome.njk` shipped with a leftover `{{ test.prova }}` referencing a data
|
|
25
|
+
file no scaffolded project has.
|
|
26
|
+
|
|
27
|
+
### Notes
|
|
28
|
+
- **Existing projects need two edits to `src/frontend/data/site.json`** if you
|
|
29
|
+
update their templates: rename `site_name` to `siteName`, and turn `author`
|
|
30
|
+
into `{ "fullName": "...", "website": "..." }`. Leaving the file as it is keeps
|
|
31
|
+
the old templates working — the two only have to move together.
|
|
8
32
|
|
|
9
33
|
## [2.4.3] 2026-08-24
|
|
10
34
|
|
package/README.md
CHANGED
|
@@ -4,52 +4,75 @@
|
|
|
4
4
|
[](LICENSE)
|
|
5
5
|

|
|
6
6
|
|
|
7
|
-
**Nibula** is an open source static site generator built on top of
|
|
7
|
+
**Nibula** is an open source static site generator built on top of
|
|
8
|
+
[Eleventy (11ty)](https://www.11ty.dev/), with one clear mission: make the jump
|
|
9
|
+
from small hand-written practice sites to a real project setup as gentle as
|
|
10
|
+
possible.
|
|
8
11
|
|
|
9
|
-
|
|
12
|
+
📖 **[Read the documentation](https://rhaastrake.github.io/nibula/docs/)**
|
|
10
13
|
|
|
11
|
-
|
|
14
|
+
## 🎯 Why choose Nibula
|
|
12
15
|
|
|
13
|
-
|
|
16
|
+
### Beginner friendly
|
|
14
17
|
|
|
15
|
-
|
|
18
|
+
If you've only ever written HTML, CSS and a bit of JavaScript, moving to a
|
|
19
|
+
framework usually feels like starting over. A new syntax, new rules, new folder
|
|
20
|
+
structures, and a pile of documentation to get through before you can even see a
|
|
21
|
+
page on screen.
|
|
16
22
|
|
|
17
|
-
|
|
23
|
+
Nibula is designed to avoid exactly that. You stay close to the three languages
|
|
24
|
+
you already know, and the folder structure stays small enough to hold in your
|
|
25
|
+
head — a handful of folders whose names say what's inside, instead of a
|
|
26
|
+
convention you have to study before it makes sense.
|
|
18
27
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
- 🎨 **Pick your CSS framework** — choose from 4 pre-installed options (or none), and switch later in a few guided steps
|
|
23
|
-
- 🔌 **Pick your backend** — Node.js or PHP, chosen at creation (choose carefully, based on where you plan to publish)
|
|
24
|
-
- 🧩 **Your own modules** — add your own SCSS and JS/TS modules freely and easily
|
|
25
|
-
- 🪶 **Lightweight by default** — SCSS frameworks can be filtered so you ship only what you actually use
|
|
28
|
+
Everything else is optional. Nunjucks adds loops and includes to your HTML,
|
|
29
|
+
Markdown files can carry the same logic, data files can feed a whole page — but
|
|
30
|
+
none of it is needed on day one.
|
|
26
31
|
|
|
27
|
-
###
|
|
32
|
+
### Ready to publish
|
|
28
33
|
|
|
29
|
-
|
|
30
|
-
|
|
34
|
+
The hard part of a first site usually isn't building it — it's everything that
|
|
35
|
+
comes after. Meta tags, a sitemap, a `robots.txt`, the server config your host
|
|
36
|
+
expects: four things nobody taught you, all at once, right when you thought you
|
|
37
|
+
were done.
|
|
31
38
|
|
|
32
|
-
|
|
39
|
+
Nibula writes them while you work. Fill in two data files and the SEO tags, the
|
|
40
|
+
sitemap and `llms.txt` build themselves from your pages. `.htaccess` and
|
|
41
|
+
`web.config` are already in the output folder, ready to upload, and an
|
|
42
|
+
`nginx.conf` is waiting if you have your own server.
|
|
33
43
|
|
|
34
|
-
|
|
44
|
+
### Nothing is hidden
|
|
35
45
|
|
|
36
|
-
|
|
46
|
+
Every one of those files lives in the project, in the folder you'd expect, in the
|
|
47
|
+
form you'd have written by hand. When you need to change something, you open it
|
|
48
|
+
and change it — there's no configuration layer to learn first, and no generated
|
|
49
|
+
code you're not supposed to touch.
|
|
37
50
|
|
|
38
|
-
|
|
51
|
+
### Backend included
|
|
39
52
|
|
|
40
|
-
|
|
53
|
+
At project creation you choose your backend: **Node.js or PHP**. Both expose the
|
|
54
|
+
same REST API — same routing, `X-Api-Key` auth, CORS and rate limiting — so a
|
|
55
|
+
contact form or a small API works on whatever hosting you end up with.
|
|
56
|
+
|
|
57
|
+
## 🤔 When not to use it
|
|
41
58
|
|
|
42
|
-
|
|
59
|
+
**Nibula builds static pages.** If your site needs users to log in, save
|
|
60
|
+
something, or see different content depending on who they are, you want a
|
|
61
|
+
framework with a server behind it — Next.js, Nuxt, Laravel.
|
|
43
62
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
63
|
+
**It doesn't scale to hundreds of pages** coming from a database or a CMS. The
|
|
64
|
+
assistant creates pages one at a time, which is right for a site with ten or
|
|
65
|
+
twenty of them and wrong for a catalogue.
|
|
47
66
|
|
|
48
|
-
|
|
67
|
+
**If you already work with React, Vue or Angular, stay there.** Nibula's whole
|
|
68
|
+
point is being a first step for people who don't.
|
|
69
|
+
|
|
70
|
+
## 📋 Prerequisites
|
|
49
71
|
|
|
50
|
-
|
|
72
|
+
- **Node.js** — v18.0.0 or higher
|
|
73
|
+
- **Composer** — latest version, only if you pick the PHP backend
|
|
51
74
|
|
|
52
|
-
##
|
|
75
|
+
## 🚀 Getting started
|
|
53
76
|
|
|
54
77
|
Install Nibula once, globally:
|
|
55
78
|
|
|
@@ -57,54 +80,60 @@ Install Nibula once, globally:
|
|
|
57
80
|
npm install -g nibula
|
|
58
81
|
```
|
|
59
82
|
|
|
60
|
-
This gives you the `nib` command
|
|
83
|
+
This gives you the `nib` command, with `nbl` and `nibula` as aliases.
|
|
61
84
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
From the folder where you keep your websites, run:
|
|
85
|
+
From the folder where you keep your websites:
|
|
65
86
|
|
|
66
87
|
```
|
|
67
88
|
nib new your-project
|
|
68
89
|
```
|
|
69
90
|
|
|
70
|
-
The scaffolder
|
|
71
|
-
|
|
72
|
-
|
|
91
|
+
The scaffolder asks you three things — the language, the CSS framework and the
|
|
92
|
+
backend — then installs everything for you. Move into the project and start the
|
|
93
|
+
dev server:
|
|
73
94
|
|
|
74
95
|
```
|
|
75
96
|
cd your-project
|
|
76
97
|
nib run
|
|
77
98
|
```
|
|
78
99
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
Instead of creating and wiring up multiple files by hand, let the interactive assistant do it for you. To create, remove, or rename pages and configure the output path, run:
|
|
82
|
-
|
|
83
|
-
```bash
|
|
84
|
-
nib cli
|
|
85
|
-
```
|
|
86
|
-
|
|
87
|
-
See [docs/Assistant CLI.md](docs/Assistant%20CLI.md) for details.
|
|
100
|
+
Your site is now at `localhost:8080`, and it rebuilds every time you save.
|
|
88
101
|
|
|
89
102
|
## 💻 Commands
|
|
90
103
|
|
|
91
|
-
Run these from anywhere inside a project — except `nib new`, which must be run
|
|
104
|
+
Run these from anywhere inside a project — except `nib new`, which must be run
|
|
105
|
+
**outside** a project, in the folder where you want the new one created.
|
|
92
106
|
|
|
93
107
|
| Command | Description |
|
|
94
108
|
|---|---|
|
|
95
|
-
| `nib new <name>` | Create
|
|
96
|
-
| `nib run` | Start the dev server and
|
|
109
|
+
| `nib new <name>` | Create a new project |
|
|
110
|
+
| `nib run` | Start the dev server and rebuild as you save |
|
|
97
111
|
| `nib cli` | Open the page-management assistant |
|
|
98
112
|
| `nib build` | Build the output folder |
|
|
99
113
|
| `nib clean` | Remove the output directory |
|
|
100
|
-
| `nib update` | Update Nibula
|
|
114
|
+
| `nib update` | Update Nibula (applies from your next project on) |
|
|
115
|
+
|
|
116
|
+
Creating a page by hand means writing three files and a data record that all have
|
|
117
|
+
to agree on the name. `nib cli` does it for you, and handles renaming and
|
|
118
|
+
removing too.
|
|
119
|
+
|
|
120
|
+
## 📖 Documentation
|
|
121
|
+
|
|
122
|
+
Everything else — the page structure, components, styling, the backend, SEO and
|
|
123
|
+
deployment — is at
|
|
124
|
+
**[rhaastrake.github.io/Nibula](https://rhaastrake.github.io/nibula/docs/)**.
|
|
101
125
|
|
|
102
|
-
##
|
|
126
|
+
## ⭐ Support Nibula
|
|
103
127
|
|
|
104
|
-
Thanks for giving Nibula a try.
|
|
128
|
+
Thanks for giving **Nibula** a try.
|
|
105
129
|
|
|
106
|
-
It's a one-person project, so every bit of feedback counts more than you'd think:
|
|
130
|
+
It's a one-person project, so every bit of feedback counts more than you'd think:
|
|
131
|
+
if something breaks, if the documentation isn't clear, or if a feature is
|
|
132
|
+
missing, open an [issue](https://github.com/Rhaastrake/nibula/issues).
|
|
107
133
|
|
|
108
|
-
If Nibula was useful to you, leaving a ⭐ on the
|
|
134
|
+
If **Nibula** was useful to you, leaving a ⭐ on the
|
|
135
|
+
[repository](https://github.com/Rhaastrake/nibula) is the easiest way to help
|
|
136
|
+
other people find it.
|
|
109
137
|
|
|
110
|
-
You can support the project by
|
|
138
|
+
You can also support the project by buying me a coffee on
|
|
139
|
+
[PayPal](https://paypal.me/rhaastrake) ☕🫡
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nibula",
|
|
3
|
-
"version": "2.4.
|
|
3
|
+
"version": "2.4.4",
|
|
4
4
|
"description": "A beginner-friendly static site generator built on Eleventy that stays close to vanilla HTML, CSS and JS — with a page-management CLI, an optional PHP backend, and SEO files generated for you.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"11ty",
|
|
@@ -54,19 +54,19 @@
|
|
|
54
54
|
"assistant": "node tools/assistant.js"
|
|
55
55
|
},
|
|
56
56
|
"devDependencies": {
|
|
57
|
-
"@11ty/eleventy": "^3.1.
|
|
58
|
-
"@11ty/eleventy-img": "^
|
|
57
|
+
"@11ty/eleventy": "^3.1.6",
|
|
58
|
+
"@11ty/eleventy-img": "^7.0.0",
|
|
59
59
|
"@11ty/eleventy-plugin-syntaxhighlight": "^5.0.2",
|
|
60
60
|
"bootstrap": "^5.3.8",
|
|
61
61
|
"bootstrap-icons": "^1.13.1",
|
|
62
62
|
"bulma": "^1.0.4",
|
|
63
|
-
"concurrently": "^9.2.
|
|
64
|
-
"esbuild": "^0.
|
|
63
|
+
"concurrently": "^9.2.4",
|
|
64
|
+
"esbuild": "^0.28.2",
|
|
65
65
|
"foundation-sites": "^6.9.0",
|
|
66
66
|
"glob": "^13.0.6",
|
|
67
67
|
"markdown-it-anchor": "^9.2.1",
|
|
68
68
|
"markdown-it-attrs": "^5.0.1",
|
|
69
|
-
"sass": "^1.
|
|
70
|
-
"uikit": "^3.25.
|
|
69
|
+
"sass": "^1.103.1",
|
|
70
|
+
"uikit": "^3.25.21"
|
|
71
71
|
}
|
|
72
72
|
}
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
|
|
17
17
|
<div class="copyright">
|
|
18
18
|
<p>
|
|
19
|
-
© {{ site.legal.copyright.year }} {{ site.
|
|
19
|
+
© {{ site.legal.copyright.year }} {{ site.siteName }} - {{ site.legal.copyright.text }}
|
|
20
20
|
</p>
|
|
21
21
|
</div>
|
|
22
22
|
</footer>
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
</svg>
|
|
17
17
|
</span>
|
|
18
18
|
</a>
|
|
19
|
-
<a href="https://github.
|
|
19
|
+
<a href="https://rhaastrake.github.io/nibula/docs/" class="card" target="_blank" rel="noopener noreferrer" style="flex: 1; min-width: 250px;">
|
|
20
20
|
<svg class="card-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
|
21
21
|
<path d="M2 3h6a4 4 0 0 1 4 4v14a3 3 0 0 0-3-3H2z"></path>
|
|
22
22
|
<path d="M22 3h-6a4 4 0 0 0-4 4v14a3 3 0 0 1 3-3h7z"></path>
|
|
@@ -31,7 +31,6 @@
|
|
|
31
31
|
</a>
|
|
32
32
|
</div>
|
|
33
33
|
</div>
|
|
34
|
-
<div class="prova">{{ test.prova }}</div>
|
|
35
34
|
<style>
|
|
36
35
|
.layout-container {
|
|
37
36
|
width: 100%;
|
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
{
|
|
2
|
-
"
|
|
2
|
+
"siteName": "Site name",
|
|
3
3
|
"title": "Site title",
|
|
4
4
|
"description": "Site description",
|
|
5
5
|
"keywords": "keyword1, keyword2, keyword3",
|
|
6
6
|
"domain": "yoursite.com",
|
|
7
7
|
"url": "https://yoursite.com",
|
|
8
8
|
"lang": "en",
|
|
9
|
-
"author":
|
|
9
|
+
"author": {
|
|
10
|
+
"fullName": "Name and surname",
|
|
11
|
+
"website": "https://example.com"
|
|
12
|
+
},
|
|
10
13
|
"theme": "dark",
|
|
11
14
|
"logo": "/assets/brand/logo.svg",
|
|
12
15
|
"legal": {
|
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
permalink: /llms.txt
|
|
3
3
|
eleventyExcludeFromCollections: true
|
|
4
4
|
---
|
|
5
|
-
# {{ site.
|
|
5
|
+
# {{ site.siteName }}
|
|
6
6
|
|
|
7
7
|
> {{ site.description }}
|
|
8
8
|
|
|
9
|
-
Built by {{ site.author }} — {{ site.
|
|
9
|
+
Built by {{ site.author.fullName }} — {{ site.author.website }}
|
|
10
10
|
|
|
11
11
|
## Pages
|
|
12
12
|
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
<title>{{ pageData.seo.title or title or site.title }}</title>
|
|
24
24
|
<meta name="description" content="{{ pageData.seo.description or site.description }}">
|
|
25
25
|
<meta name="keywords" content="{{ pageData.seo.keywords or site.keywords }}">
|
|
26
|
-
<meta name="author" content="{{ site.author }}">
|
|
26
|
+
<meta name="author" content="{{ site.author.fullName }}">
|
|
27
27
|
<meta name="theme-color" content="{{ site.theme }}">
|
|
28
28
|
|
|
29
29
|
{% if pageData.seo.noindex %}
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
<meta property="og:type" content="website">
|
|
37
37
|
<meta property="og:url" content="{{ site.url }}{{ page.url }}">
|
|
38
38
|
<meta property="og:image" content="{{ site.url }}{{ site.logo }}">
|
|
39
|
-
<meta property="og:site_name" content="{{ site.
|
|
39
|
+
<meta property="og:site_name" content="{{ site.siteName }}">
|
|
40
40
|
|
|
41
41
|
{# Twitter Card #}
|
|
42
42
|
<meta name="twitter:card" content="summary_large_image">
|
|
@@ -57,11 +57,12 @@
|
|
|
57
57
|
"url": {{ (site.url + page.url) | dump }},
|
|
58
58
|
"author": {
|
|
59
59
|
"@type": "Person",
|
|
60
|
-
"name": {{ site.author | dump }}
|
|
60
|
+
"name": {{ site.author.fullName | dump }},
|
|
61
|
+
"url": {{ site.author.website | dump }}
|
|
61
62
|
},
|
|
62
63
|
"publisher": {
|
|
63
64
|
"@type": "Organization",
|
|
64
|
-
"name": {{ site.
|
|
65
|
+
"name": {{ site.siteName | dump }},
|
|
65
66
|
"logo": {
|
|
66
67
|
"@type": "ImageObject",
|
|
67
68
|
"url": {{ (site.url + site.logo) | dump }}
|
package/docs/Assistant CLI.md
DELETED
|
@@ -1,70 +0,0 @@
|
|
|
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 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
|
-
The route is created with an empty body and a commented example — you decide which components go in it. See **Components** DOC file.
|
|
55
|
-
|
|
56
|
-
## Remove page
|
|
57
|
-
|
|
58
|
-
Deletes all source files for the page, cleans up the output directory, and removes the record from `pages.json`.
|
|
59
|
-
|
|
60
|
-
## Rename page
|
|
61
|
-
|
|
62
|
-
Renames all three source files, updates the `permalink` in the route's front matter, 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.
|
|
63
|
-
|
|
64
|
-
The includes you wrote inside the route move with the file, since the route itself is renamed rather than rewritten.
|
|
65
|
-
|
|
66
|
-
## Configure output path
|
|
67
|
-
|
|
68
|
-
Updates the output directory across `.eleventy.js` and all relevant `package.json` scripts in one shot. The old output folder is deleted automatically.
|
|
69
|
-
|
|
70
|
-
> ⚠️ `homepage` and `404` are protected — they cannot be created, removed, or renamed via the CLI.
|
package/docs/Backend.md
DELETED
|
@@ -1,219 +0,0 @@
|
|
|
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
|
-
### Installing Composer packages (PHP)
|
|
46
|
-
|
|
47
|
-
Backend dependencies live in `src/backend/_core`, next to `composer.json`.
|
|
48
|
-
Install from there, not from the project root:
|
|
49
|
-
|
|
50
|
-
```bash
|
|
51
|
-
cd src/backend/_core
|
|
52
|
-
composer require vendor/package
|
|
53
|
-
```
|
|
54
|
-
|
|
55
|
-
`_core/init.php` already requires Composer's autoloader, so an installed package
|
|
56
|
-
is usable in your endpoints straight away — there is no extra `require` to add.
|
|
57
|
-
|
|
58
|
-
Packages land in `src/backend/_core/vendor/`, which is git-ignored. After a fresh
|
|
59
|
-
clone that folder is missing and the backend won't start at all: run
|
|
60
|
-
`composer install` from `src/backend/_core` to restore it.
|
|
61
|
-
|
|
62
|
-
## How routing works
|
|
63
|
-
|
|
64
|
-
The file path inside `api/` maps directly to the URL. The `public` / `protected`
|
|
65
|
-
folder does **not** appear in the URL — it only decides whether the `X-Api-Key`
|
|
66
|
-
check applies:
|
|
67
|
-
|
|
68
|
-
- `api/public/example-public` → `/api/example-public` (no key)
|
|
69
|
-
- `api/protected/example-protected` → `/api/example-protected` (requires key)
|
|
70
|
-
|
|
71
|
-
Subfolders **do** appear: `api/public/users/list` → `/api/users/list`.
|
|
72
|
-
|
|
73
|
-
Extra URL segments become route parameters, resolved from the deepest match
|
|
74
|
-
backwards: `/api/posts/42` looks for `posts/42` first, then falls back to
|
|
75
|
-
`posts` with `42` as a parameter.
|
|
76
|
-
|
|
77
|
-
Every endpoint receives:
|
|
78
|
-
|
|
79
|
-
| Field | Description |
|
|
80
|
-
|---|---|
|
|
81
|
-
| `method` | HTTP method (`GET`, `POST`, `PUT`, `PATCH`, `DELETE`) |
|
|
82
|
-
| `requestParams` | Extra URL segments (e.g. `/api/posts/42` → `['42']`) |
|
|
83
|
-
|
|
84
|
-
The Node handler also gets `query`, `body` (parsed JSON), `rawBody`, `headers`,
|
|
85
|
-
`config`, `req`, `res`.
|
|
86
|
-
|
|
87
|
-
> ⚠️ If two files share the same path — one in `public/`, one in `protected/` —
|
|
88
|
-
> the public one wins, so the endpoint ends up **without** authentication. Don't
|
|
89
|
-
> duplicate names across the two folders.
|
|
90
|
-
|
|
91
|
-
## Creating an endpoint
|
|
92
|
-
|
|
93
|
-
Create a file anywhere inside `api/public/` or `api/protected/`. For protected
|
|
94
|
-
endpoints the API-key check runs automatically before your file does.
|
|
95
|
-
|
|
96
|
-
```js
|
|
97
|
-
module.exports = ({ method, requestParams, Response }) => {
|
|
98
|
-
if (method !== 'GET') Response.error('Method not allowed', 405);
|
|
99
|
-
const id = requestParams[0] ? parseInt(requestParams[0], 10) : null;
|
|
100
|
-
Response.success({ id });
|
|
101
|
-
};
|
|
102
|
-
```
|
|
103
|
-
|
|
104
|
-
## The Response helper
|
|
105
|
-
|
|
106
|
-
Same envelope in both backends.
|
|
107
|
-
|
|
108
|
-
```js
|
|
109
|
-
Response.success(data, code); // default 200
|
|
110
|
-
Response.error(message, code, details); // default 400
|
|
111
|
-
Response.noContent(); // 204
|
|
112
|
-
```
|
|
113
|
-
|
|
114
|
-
Shapes:
|
|
115
|
-
|
|
116
|
-
- success → `{ "status": "success", "data": ... }`
|
|
117
|
-
- error → `{ "status": "error", "message": "...", "code": ... }` (+ optional `details`)
|
|
118
|
-
|
|
119
|
-
## Configuration
|
|
120
|
-
|
|
121
|
-
`config.php` / `config.js` work like a `.env` file: they hold secrets and
|
|
122
|
-
environment settings that stay local and out of version control. Same keys in
|
|
123
|
-
both, PHP-array vs JS-object syntax.
|
|
124
|
-
|
|
125
|
-
```js
|
|
126
|
-
module.exports = {
|
|
127
|
-
// Default key for protected endpoints that don't have a specific key in CUSTOM_ENDPOINT_KEYS
|
|
128
|
-
GENERAL_API_KEY: 'DEFAULT_KEY',
|
|
129
|
-
|
|
130
|
-
// If you want to restrict access to protected endpoints to specific clients, define custom keys per endpoint.
|
|
131
|
-
// For subfolder endpoints, use the relative path ('subfolder/endpoint')
|
|
132
|
-
CUSTOM_ENDPOINT_KEYS: {
|
|
133
|
-
'subfolder/example-protected': 'custom-key',
|
|
134
|
-
},
|
|
135
|
-
|
|
136
|
-
GENERAL_ALLOWED_ORIGINS: [
|
|
137
|
-
'*',
|
|
138
|
-
// 'https://example.com',
|
|
139
|
-
],
|
|
140
|
-
|
|
141
|
-
CUSTOM_ENDPOINT_ORIGINS: {
|
|
142
|
-
'subfolder/example-protected': ['https://app.example.com'],
|
|
143
|
-
},
|
|
144
|
-
|
|
145
|
-
// Database configuration
|
|
146
|
-
DB_HOST: '127.0.0.1',
|
|
147
|
-
DB_NAME: 'example_db',
|
|
148
|
-
DB_USER: 'root',
|
|
149
|
-
DB_PASS: '',
|
|
150
|
-
|
|
151
|
-
// Environment: 'production' hides error details; anything else = debug.
|
|
152
|
-
APP_ENV: 'production',
|
|
153
|
-
};
|
|
154
|
-
```
|
|
155
|
-
|
|
156
|
-
### API keys
|
|
157
|
-
|
|
158
|
-
`GENERAL_API_KEY` is the fallback for all protected endpoints.
|
|
159
|
-
`CUSTOM_ENDPOINT_KEYS` assigns a different key to a specific endpoint, keyed by
|
|
160
|
-
its relative path (`'subfolder/endpoint'`).
|
|
161
|
-
|
|
162
|
-
> ⚠️ The key travels in the `X-Api-Key` header on every request. Use it only for
|
|
163
|
-
> server-to-server calls over HTTPS. Never embed it in frontend code, where it
|
|
164
|
-
> would be publicly visible.
|
|
165
|
-
|
|
166
|
-
### CORS
|
|
167
|
-
|
|
168
|
-
Mirrors the API-key model: `GENERAL_ALLOWED_ORIGINS` is the default list,
|
|
169
|
-
`CUSTOM_ENDPOINT_ORIGINS` overrides it per endpoint using the same relative path.
|
|
170
|
-
|
|
171
|
-
Origins must be exact (scheme + host, no trailing slash). A matching `Origin` is
|
|
172
|
-
reflected in `Access-Control-Allow-Origin` with `Vary: Origin`. An empty list
|
|
173
|
-
sends no CORS header — the most restrictive setting; same-origin requests still
|
|
174
|
-
work. `'*'` as the only element allows any origin (not recommended for protected
|
|
175
|
-
endpoints).
|
|
176
|
-
|
|
177
|
-
Resolution order: `CUSTOM_ENDPOINT_ORIGINS[path]` if present, otherwise
|
|
178
|
-
`GENERAL_ALLOWED_ORIGINS`.
|
|
179
|
-
|
|
180
|
-
### Error verbosity
|
|
181
|
-
|
|
182
|
-
`APP_ENV` decides what an unhandled exception tells the client.
|
|
183
|
-
|
|
184
|
-
| Value | Response to a 500 |
|
|
185
|
-
|---|---|
|
|
186
|
-
| `'production'` (default) | `{"status":"error","message":"Internal server error","code":500}` |
|
|
187
|
-
| anything else | the real exception message, plus `stack` (Node) or `file` and `line` (PHP) |
|
|
188
|
-
|
|
189
|
-
On PHP it does one extra thing: `'production'` also turns off `display_errors`
|
|
190
|
-
and `error_reporting`, so a stray warning can't be printed before the JSON and
|
|
191
|
-
corrupt the response body.
|
|
192
|
-
|
|
193
|
-
Set it to something like `'development'` while building, and back to
|
|
194
|
-
`'production'` before you publish. Missing key means `'production'`.
|
|
195
|
-
|
|
196
|
-
> This is a config key, **not** an environment variable. Setting `APP_ENV` in a
|
|
197
|
-
> shell or in a systemd unit has no effect — both backends read it from
|
|
198
|
-
> `config.js` / `config.php` only.
|
|
199
|
-
|
|
200
|
-
## Database
|
|
201
|
-
|
|
202
|
-
Both backends expose a connection singleton reading the same config —
|
|
203
|
-
`database/Database.js` (a `mysql2` pool) or `database/Database.php` (a PDO
|
|
204
|
-
singleton).
|
|
205
|
-
|
|
206
|
-
```js
|
|
207
|
-
const pool = Database.getInstance();
|
|
208
|
-
const [rows] = await pool.execute(sql, params);
|
|
209
|
-
```
|
|
210
|
-
|
|
211
|
-
`mysql2` is installed at scaffold time and loaded lazily, so the backend boots
|
|
212
|
-
fine without a database.
|
|
213
|
-
|
|
214
|
-
## Pre-built endpoints
|
|
215
|
-
|
|
216
|
-
| Route | Method | Description |
|
|
217
|
-
|---|---|---|
|
|
218
|
-
| `/api/example-public` | `GET` | Example endpoint requiring no key |
|
|
219
|
-
| `/api/example-protected` | `GET` | Example endpoint requiring `X-Api-Key` |
|