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 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](https://img.shields.io/npm/l/nibula?color=blue)](LICENSE)
5
5
  ![Eleventy](https://img.shields.io/badge/built%20on-11ty-black?logo=eleventy)
6
6
 
7
- **Nibula** is an open source static site generator built on top of [Eleventy](https://www.11ty.dev/), with one clear mission: make the jump from small hand-written practice sites to a real project setup as gentle as possible.
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
- If you've only ever written HTML, CSS, and a bit of JavaScript, moving to a "framework" usually feels like starting over: new syntax, new rules, new folder structures, and a pile of documentation before you can even see a page on screen. Nibula is designed to avoid exactly that. You keep working with the **three languages that matter — HTML, CSS, and JavaScript** — and the tool quietly handles the tedious parts around them. The goal is simple: even someone with little experience should always know *where to put their hands*.
12
+ 📖 **[Read the documentation](https://rhaastrake.github.io/nibula/docs/)**
10
13
 
11
- ### ✨ Why choose Nibula?
14
+ ## 🎯 Why choose Nibula
12
15
 
13
- It's a great fit for **showcase and brochure-style websites** (portfolios, landing pages, small business sites), when you want something clean and fast without dragging in a framework that takes weeks to learn and handle.
16
+ ### Beginner friendly
14
17
 
15
- Building a website from scratch means putting together a lot of moving parts: SEO, dependency setup, deployment and server configuration, project structure. Nibula takes care of all of that for you, so you can focus on your content — while still picking up conventions and working habits that are common to the most widely used frameworks, Angular among them.
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
- ### 📌 Other things worth knowing
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
- - 🔎 **SEO made simple** — managed from one central place; `sitemap`, `llms.txt`, and `robots.txt` are generated automatically
20
- - 🖱️ **A helpful CLI** — create a page with one command instead of hand-writing ten separate files
21
- - ⚙️ **Server configs handled for you** — `.htaccess` and `web.config` are generated automatically, and an `nginx.conf` is provided so that anyone comfortable with nginx already has what they need to run the site outside of shared hosting
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
- ### 🤔 When not to choose it?
32
+ ### Ready to publish
28
33
 
29
- - **Heavily stateful interfaces** — dashboards, admin panels, editors, anything that keeps changing under the user's hands. A single-page application with React, Vue, or Angular is the right tool there.
30
- - **Pages generated at request time** — catalogues that depend on the logged-in user, personalised pricing, content that changes by the minute. Nibula builds pages beforehand, not during the visit.
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
- ## ⚙️ Backend included
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
- Essential server-side functionality comes built in — no extra setup required. At project creation you **choose your backend: Node.js or PHP**. Both expose the **same REST API** — same routing, `X-Api-Key` auth, CORS and rate limiting — so you can even switch later without rewriting your endpoints. See [docs/Backend.md](docs/Backend.md) for details.
44
+ ### Nothing is hidden
35
45
 
36
- ## 🧭 Customizable, but with sensible defaults
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
- Nibula ships with a clean, opinionated layout so beginners are never lost. But it isn't a cage: as long as you follow a few small conventions and the defined paths, you're free to customize the subpaths of your **components, backend endpoints, and JS/SCSS modules** however you like.
51
+ ### Backend included
39
52
 
40
- ## 📋 Prerequisites
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
- ### Required
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
- * **Node.js**: v18.0.0 or higher
45
- * **Composer** *(only if you choose the PHP backend)*: latest version
46
- * **Better Nunjucks** — VS Code extension by **Ed Heltzel**
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
- ### Recommended
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
- * **Material Icon Theme** — VS Code extension by **Philipp Kief**
72
+ - **Node.js** — v18.0.0 or higher
73
+ - **Composer** — latest version, only if you pick the PHP backend
51
74
 
52
- ## 📦 Installation
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 (alternatives: `nbl`, `nibula`).
83
+ This gives you the `nib` command, with `nbl` and `nibula` as aliases.
61
84
 
62
- ## 🚀 Create a project
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 is interactive: you choose the **language** (JavaScript/TypeScript), the **CSS framework**, and the **backend** (Node.js or PHP). All dependencies are installed automatically — and if you pick **Node**, the PHP/Composer step is skipped, so you don't need Composer at all.
71
-
72
- Once the dependencies are installed, move into the project folder and start the dev server (`localhost:8080`):
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
- ## 📄 Managing pages
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 **outside** a project, in the folder where you want the new one created.
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 your new project |
96
- | `nib run` | Start the dev server and build the output folder at runtime |
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 to the latest version (applies from your next project on) |
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
- ## 🙌 Wrapping up
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: if something breaks, if the documentation isn't clear, or if a feature is missing, open an [issue](https://github.com/Rhaastrake/Nibula/issues) — even just to tell me what you built with it.
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 repository is the easiest way to help other people find it.
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 [buying me a coffee](https://paypal.me/rhaastrake) ☕🫡
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",
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.2",
58
- "@11ty/eleventy-img": "^6.0.4",
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.1",
64
- "esbuild": "^0.27.3",
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.77.0",
70
- "uikit": "^3.25.13"
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
- &copy; {{ site.legal.copyright.year }} {{ site.site_name }} - {{ site.legal.copyright.text }}
19
+ &copy; {{ site.legal.copyright.year }} {{ site.siteName }} - {{ site.legal.copyright.text }}
20
20
  </p>
21
21
  </div>
22
22
  </footer>
@@ -1,7 +1,7 @@
1
1
  <header>
2
2
  <nav>
3
3
  <a class="logo" href="/">
4
- <img src="{{ site.logo }}"> {{ site.site_name }}
4
+ <img src="{{ site.logo }}"> {{ site.siteName }}
5
5
  </a>
6
6
  </nav>
7
7
  </header>
@@ -16,7 +16,7 @@
16
16
  </svg>
17
17
  </span>
18
18
  </a>
19
- <a href="https://github.com/rhaastrake/nibula/tree/main/docs" class="card" target="_blank" rel="noopener noreferrer" style="flex: 1; min-width: 250px;">
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
- "site_name": "Site name",
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": "Name and surname",
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.site_name }}
5
+ # {{ site.siteName }}
6
6
 
7
7
  > {{ site.description }}
8
8
 
9
- Built by {{ site.author }} — {{ site.url }}
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.site_name }}">
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.site_name | dump }},
65
+ "name": {{ site.siteName | dump }},
65
66
  "logo": {
66
67
  "@type": "ImageObject",
67
68
  "url": {{ (site.url + site.logo) | dump }}
@@ -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` |