kaelum 1.1.0 β†’ 1.3.0

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 CHANGED
@@ -1,134 +1,248 @@
1
1
  # Kaelum
2
2
 
3
- **Kaelum** is a minimalist Node.js framework designed to simplify the creation of web pages and REST APIs, especially for beginners and students. Inspired by Python's clean syntax and powered by Express.js, Kaelum automates project setup and server configuration with an intuitive CLI.
3
+ **Kaelum.JS** β€” Minimalist Node.js framework to simplify creation of web pages and REST APIs.
4
+ Designed for students and developers who want a fast, opinionated project scaffold and a small, friendly API that encapsulates common Express.js boilerplate.
4
5
 
5
- ## πŸ“¦ Installation
6
+ ---
7
+
8
+ ## πŸš€ Quick start
6
9
 
7
- You can create a new project with Kaelum using:
10
+ Create a new project (interactive):
8
11
 
9
12
  ```bash
10
13
  npx kaelum create
11
14
  ````
12
15
 
13
- This command initializes a Kaelum project with a pre-configured structure based on the MVC model, including:
16
+ Or create non-interactively (project name + template):
14
17
 
15
- * Static file serving
16
- * Template-ready HTML/CSS homepage
17
- * Built-in route system
18
- * Public folder and initial structure
18
+ ```bash
19
+ npx kaelum create my-app --template web
20
+ # or
21
+ npx kaelum create my-api --template api
22
+ ```
19
23
 
20
- Then install dependencies and start your app:
24
+ Then:
21
25
 
22
26
  ```bash
23
- cd my-project
27
+ cd my-app
24
28
  npm install
25
29
  npm start
26
30
  ```
27
31
 
28
- > **Note:** No need to install Kaelum globally. `npx` handles it automatically!
32
+ > No need to install Kaelum globally β€” `npx` handles execution.
29
33
 
30
34
  ---
31
35
 
32
- ## 🧠 Why Kaelum?
36
+ ## πŸ“¦ What Kaelum provides
33
37
 
34
- * πŸ“‚ Minimalist MVC folder structure
35
- * βš™οΈ Auto-configured Express setup
36
- * πŸ”’ Built-in support for CORS and Helmet
37
- * 🧱 Easy route management
38
- * πŸ§ͺ Great for learning and building quick prototypes
38
+ * CLI that scaffolds a ready-to-run project (Web or API template) using an opinionated **MVC** structure.
39
+ * Thin abstraction layer over **Express.js** that:
40
+
41
+ * automates JSON / URL-encoded parsing by default,
42
+ * automatically configures common security middlewares via `setConfig` (CORS, Helmet),
43
+ * exposes a small, easy-to-learn API for routes, middleware and configuration.
44
+ * Small set of helpers for common tasks: `start`, `addRoute`, `apiRoute`, `setConfig`, `static`, `redirect`, `healthCheck`, `useErrorHandler`, and more.
45
+
46
+ Kaelum aims to reduce the initial setup burden while keeping flexibility for advanced users.
39
47
 
40
48
  ---
41
49
 
42
- ## πŸ“ Web Template Structure
50
+ ## πŸ“ Template structures
43
51
 
44
- After running `npx kaelum create`, the web template structure looks like this:
52
+ ### Web template (created by `npx kaelum create <name>` with template `web`)
45
53
 
46
54
  ```
47
55
  my-web-app/
48
- β”œβ”€β”€ public/ # Static files (e.g., CSS, JS)
56
+ β”œβ”€β”€ public/ # Static files (CSS, JS)
49
57
  β”‚ └── style.css
50
58
  β”œβ”€β”€ views/ # HTML templates
51
59
  β”‚ └── index.html
52
- β”œβ”€β”€ controllers/ # Page controller logic
60
+ β”œβ”€β”€ controllers/ # Controller logic (MVC)
53
61
  β”‚ └── .gitkeep
54
62
  β”œβ”€β”€ middlewares/ # Custom middlewares
55
- β”‚ └── example.js
56
- β”œβ”€β”€ routes.js # Route definitions
57
- β”œβ”€β”€ app.js # Server initialization
58
- └── package.json # Project metadata and dependencies
63
+ β”‚ └── logger.js
64
+ β”œβ”€β”€ routes.js # Route definitions (example uses Kaelum helpers)
65
+ β”œβ”€β”€ app.js # Server initialization (uses Kaelum API)
66
+ └── package.json
67
+ ```
68
+
69
+ ### API template (`--template api`)
70
+
71
+ ```
72
+ my-api-app/
73
+ β”œβ”€β”€ controllers/
74
+ β”‚ └── usersController.js
75
+ β”œβ”€β”€ middlewares/
76
+ β”‚ └── authMock.js
77
+ β”œβ”€β”€ routes.js
78
+ β”œβ”€β”€ app.js
79
+ └── package.json
59
80
  ```
60
81
 
61
82
  ---
62
83
 
63
- ## πŸš€ Features
84
+ ## 🧩 Core API (examples β€” CommonJS)
64
85
 
65
- Kaelum exposes simple utilities that make it easy to build a web server:
86
+ > Kaelum exposes a factory β€” use `require('kaelum')` and call it to get an app instance:
66
87
 
67
88
  ```js
68
89
  const kaelum = require('kaelum');
69
90
  const app = kaelum();
70
91
  ```
71
92
 
72
- ### 🌐 `addRoute(path, handlers)`
93
+ ### `app.setConfig(options)`
73
94
 
74
- Add routes with GET, POST, PUT, DELETE handlers in one place.
95
+ Enable/disable common features:
75
96
 
76
97
  ```js
77
- addRoute('/home', {
78
- get: (req, res) => res.send('GET: Welcome!'),
79
- post: (req, res) => res.send('POST: Data received!')
98
+ app.setConfig({
99
+ cors: true, // apply CORS (requires cors package in dependencies)
100
+ helmet: true, // apply Helmet
101
+ static: "public", // serve static files from "public"
102
+ bodyParser: true, // default: enabled (JSON + urlencoded)
103
+ logs: false, // enable request logging via morgan (if installed)
104
+ port: 3000 // prefered port (used when calling app.start() without port)
80
105
  });
81
106
  ```
82
107
 
83
- ### πŸ” `setMiddleware(middleware)`
108
+ * `setConfig` persists settings to the Kaelum config and will install/remove Kaelum-managed middlewares.
109
+ * Kaelum enables JSON/urlencoded parsing by default so beginners won't forget to parse request bodies.
110
+
111
+ ---
112
+
113
+ ### `app.start(port, callback)`
84
114
 
85
- Globally apply middleware to all routes.
115
+ Starts the HTTP server. If `port` is omitted, Kaelum reads `port` from `setConfig` or falls back to `3000`.
86
116
 
87
117
  ```js
88
- setMiddleware(require('helmet')());
118
+ app.start(3000, () => console.log('Running'));
89
119
  ```
90
120
 
91
- ### βš™οΈ `setConfig(options)`
121
+ ---
122
+
123
+ ### `app.addRoute(path, handlers)` and `app.apiRoute(resource, handlers)`
92
124
 
93
- You can enable common configurations using `setConfig`:
125
+ Register routes easily:
94
126
 
95
127
  ```js
96
- app.setConfig({
97
- cors: true,
98
- helmet: true,
128
+ app.addRoute('/home', {
129
+ get: (req, res) => res.send('Welcome!'),
130
+ post: (req, res) => res.send('Posted!')
131
+ });
132
+
133
+ // apiRoute builds RESTy resources with nested subpaths:
134
+ app.apiRoute('users', {
135
+ get: listUsers,
136
+ post: createUser,
137
+ '/:id': {
138
+ get: getUserById,
139
+ put: updateUser,
140
+ delete: deleteUser
141
+ }
99
142
  });
100
143
  ```
101
144
 
102
- Internally, Kaelum will automatically load and apply `cors` and `helmet` if enabled.
145
+ `addRoute` also accepts a single handler function (assumed `GET`).
146
+
147
+ ---
148
+
149
+ ### `app.setMiddleware(...)`
150
+
151
+ Flexible helper to register middleware(s):
152
+
153
+ ```js
154
+ // single middleware
155
+ app.setMiddleware(require('helmet')());
156
+
157
+ // array of middlewares
158
+ app.setMiddleware([mw1, mw2]);
159
+
160
+ // mount middleware on a path
161
+ app.setMiddleware('/admin', authMiddleware);
162
+ ```
163
+
164
+ ---
103
165
 
104
- ### πŸš€ `start(port)`
166
+ ### `app.redirect(from, to, status)`
105
167
 
106
- Start the server.
168
+ Register a redirect route:
107
169
 
108
170
  ```js
109
- start(3000);
171
+ app.redirect('/old-url', '/new-url', 302);
110
172
  ```
111
173
 
112
174
  ---
113
175
 
114
- ## πŸ‘¨β€πŸ’» Local Development (for contributors)
176
+ ### `app.healthCheck(path = '/health')`
115
177
 
116
- If you want to test or improve Kaelum locally:
178
+ Adds a health endpoint returning `{ status: 'OK', uptime, timestamp, pid }`.
179
+
180
+ ---
181
+
182
+ ### `app.useErrorHandler(options)`
183
+
184
+ Attach Kaelum's default JSON error handler:
185
+
186
+ ```js
187
+ app.useErrorHandler({ exposeStack: false });
188
+ ```
189
+
190
+ It will return standardized JSON for errors and log server-side errors (5xx) to `console.error`.
191
+
192
+ ---
193
+
194
+ ## πŸ”§ Local development & contributing
195
+
196
+ To develop Kaelum locally and test the CLI:
117
197
 
118
198
  ```bash
119
- git clone https://github.com/MatheusCampagnolo/kaelum.git
199
+ # clone
200
+ git clone https://github.com/<your-repo>/kaelum.git
120
201
  cd kaelum
202
+
203
+ # install & link locally
204
+ npm install
121
205
  npm link
206
+
207
+ # from any folder you can now run the CLI
208
+ npx kaelum create my-test --template web
209
+ # or
210
+ kaelum create my-test # if linked globally
122
211
  ```
123
212
 
124
- Now you can run the CLI from anywhere:
213
+ ---
125
214
 
126
- ```bash
127
- npx kaelum create
128
- ```
215
+
216
+ ## πŸ“ Why Kaelum?
217
+
218
+ * Reduces repetitive boilerplate required to start Node/Express web projects.
219
+ * Opinionated scaffolding (MVC) helps beginners adopt better structure.
220
+ * Keeps a small API surface: easy to teach and document.
221
+ * Extensible β€” `setConfig` and middleware helpers allow adding features without exposing Express internals.
222
+
223
+ ---
224
+
225
+ ## βœ… Current status
226
+
227
+ > Kaelum is under active development. The CLI scaffolds web and API templates and the framework already includes the MVP helpers (`start`, `addRoute`, `apiRoute`, `setConfig`, `static`, `redirect`, `healthCheck`, `useErrorHandler`) and security toggles for `cors` and `helmet`.
228
+
229
+ ---
230
+
231
+ ## πŸ“š Links
232
+
233
+ * GitHub: `https://github.com/MatheusCampagnolo/kaelum`
234
+ * npm: `https://www.npmjs.com/package/kaelum`
235
+
236
+ ---
237
+
238
+ ## 🧾 License
239
+
240
+ MIT β€” see [LICENSE](LICENSE).
129
241
 
130
242
  ---
131
243
 
132
- ## πŸ“„ License
244
+ ## ✍️ Notes for maintainers
133
245
 
134
- This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
246
+ * Templates include `package.json` configured as `commonjs` for now (uses `require`/`module.exports`).
247
+ * Update template `package.json` dependencies to reference Kaelum version when releasing new npm versions.
248
+ * We plan to add full JSDoc for the public API, unit tests (Jest + Supertest) and a docs site in future iterations.
package/cli/create.js CHANGED
@@ -6,47 +6,80 @@ const { copyTemplate } = require("./utils");
6
6
 
7
7
  const templatesDir = path.resolve(__dirname, "templates");
8
8
 
9
- async function createProject() {
9
+ /**
10
+ * createProject - create project from template
11
+ * @param {Object} defaults - optional { projectName, template }
12
+ */
13
+ async function createProject(defaults = {}) {
10
14
  console.log("πŸš€ Bem-vindo ao Kaelum CLI!");
11
15
 
12
- const answers = await inq.prompt([
13
- {
14
- type: "input",
15
- name: "projectName",
16
- message: "Qual serΓ‘ o nome do seu projeto?",
17
- validate: (input) => (input ? true : "O nome nΓ£o pode ser vazio."),
18
- },
19
- {
20
- type: "list",
21
- name: "template",
22
- message: "Qual template vocΓͺ deseja usar?",
23
- choices: ["web", "api"],
24
- },
25
- ]);
26
-
27
- const { projectName, template } = answers;
28
- const targetDir = path.resolve(process.cwd(), projectName);
29
- const templateDir = path.join(templatesDir, template);
30
-
31
- if (template === "api") {
32
- console.log(
33
- '\nπŸ“¦ O template API ainda estΓ‘ em desenvolvimento. Por favor, escolha o template "web".'
34
- );
35
- return;
36
- }
16
+ try {
17
+ // ensure templates dir exists
18
+ const templatesExists = await fs.pathExists(templatesDir);
19
+ if (!templatesExists) {
20
+ console.error("❌ Diretório de templates não encontrado no CLI.");
21
+ return;
22
+ }
37
23
 
38
- if (fs.existsSync(targetDir)) {
39
- console.error(
40
- `\n❌ A pasta "${projectName}" jÑ existe. Escolha outro nome ou apague a pasta existente.`
41
- );
42
- return;
43
- }
24
+ // gather answers (use defaults when present)
25
+ const answers = await inq.prompt([
26
+ {
27
+ type: "input",
28
+ name: "projectName",
29
+ message: "Qual serΓ‘ o nome do seu projeto?",
30
+ default: defaults.projectName || "",
31
+ validate: (input) => (input ? true : "O nome nΓ£o pode ser vazio."),
32
+ },
33
+ {
34
+ type: "list",
35
+ name: "template",
36
+ message: "Qual template vocΓͺ deseja usar?",
37
+ choices: async () => {
38
+ // list available template folders
39
+ try {
40
+ const items = await fs.readdir(templatesDir, {
41
+ withFileTypes: true,
42
+ });
43
+ return items.filter((i) => i.isDirectory()).map((i) => i.name);
44
+ } catch (e) {
45
+ return ["web", "api"];
46
+ }
47
+ },
48
+ default: defaults.template || "web",
49
+ },
50
+ ]);
51
+
52
+ const { projectName, template } = answers;
53
+ const targetDir = path.resolve(process.cwd(), projectName);
54
+ const templateDir = path.join(templatesDir, template);
44
55
 
45
- await copyTemplate(templateDir, targetDir);
56
+ // template existence check
57
+ const templateExists = await fs.pathExists(templateDir);
58
+ if (!templateExists) {
59
+ console.error(`\n❌ Template "${template}" não encontrado.`);
60
+ return;
61
+ }
46
62
 
47
- console.log(`\nβœ… Projeto "${projectName}" criado com sucesso!`);
48
- console.log(`➑️ Acesse a pasta: cd ${projectName}`);
49
- console.log(`➑️ Inicie o projeto com: npm install && npm start\n`);
63
+ if (await fs.pathExists(targetDir)) {
64
+ console.error(
65
+ `\n❌ A pasta "${projectName}" jÑ existe. Escolha outro nome ou apague a pasta existente.`
66
+ );
67
+ return;
68
+ }
69
+
70
+ // copy + update package.json
71
+ const result = await copyTemplate(templateDir, targetDir, projectName);
72
+ if (!result.ok) {
73
+ console.error(`\n❌ Erro ao copiar o template: ${result.error}`);
74
+ return;
75
+ }
76
+
77
+ console.log(`\nβœ… Projeto "${projectName}" criado com sucesso!`);
78
+ console.log(`➑️ Acesse a pasta: cd ${projectName}`);
79
+ console.log(`➑️ Inicie o projeto com: npm install && npm start\n`);
80
+ } catch (err) {
81
+ console.error("❌ Erro inesperado:", err.message || err);
82
+ }
50
83
  }
51
84
 
52
- module.exports = { createProject };
85
+ module.exports = { createProject };
package/cli/index.js CHANGED
@@ -1,11 +1,53 @@
1
- #!/usr/bin/env node
2
- const { createProject } = require('./create');
1
+ #!/usr/bin/env node
2
+ const { createProject } = require("./create");
3
+ const argv = process.argv.slice(2);
3
4
 
4
- const [,, command] = process.argv;
5
+ function printHelp() {
6
+ console.log(`Kaelum CLI
7
+ Usage:
8
+ kaelum create # interactive
9
+ kaelum create <name> # create using interactive template choice
10
+ kaelum create <name> --template web|api # non-interactive (name provided, template preselected)
11
+ kaelum help
12
+ `);
13
+ }
14
+
15
+ async function main() {
16
+ const [command, maybeName, maybeFlag, maybeTemplate] = argv;
17
+
18
+ if (
19
+ !command ||
20
+ command === "help" ||
21
+ command === "--help" ||
22
+ command === "-h"
23
+ ) {
24
+ printHelp();
25
+ return;
26
+ }
27
+
28
+ if (command === "create") {
29
+ // non-interactive shorthand: kaelum create my-app --template web
30
+ if (maybeName && maybeFlag === "--template" && maybeTemplate) {
31
+ await createProject({ projectName: maybeName, template: maybeTemplate });
32
+ return;
33
+ }
34
+
35
+ // non-interactive shorthand: kaelum create my-app (will still ask template)
36
+ if (maybeName && !maybeFlag) {
37
+ await createProject({ projectName: maybeName });
38
+ return;
39
+ }
40
+
41
+ // otherwise interactive flow
42
+ await createProject();
43
+ return;
44
+ }
5
45
 
6
- if (command === 'create') {
7
- createProject();
8
- } else {
9
46
  console.log(`Comando nΓ£o reconhecido: ${command}`);
10
- console.log(`Use: kaelum create`);
47
+ printHelp();
11
48
  }
49
+
50
+ main().catch((err) => {
51
+ console.error("Error running Kaelum CLI:", err);
52
+ process.exit(1);
53
+ });
@@ -0,0 +1,26 @@
1
+ // app.js - example API project generated by Kaelum (API template)
2
+ const kaelum = require("kaelum");
3
+
4
+ const app = kaelum();
5
+
6
+ // enable basic safety + logs via setConfig
7
+ app.setConfig({
8
+ cors: true,
9
+ helmet: true,
10
+ logs: true, // uses morgan internally
11
+ bodyParser: true,
12
+ });
13
+
14
+ // mount routes
15
+ const routes = require("./routes");
16
+ routes(app);
17
+
18
+ // health check
19
+ app.healthCheck("/health");
20
+
21
+ // use Kaelum generic error handler (JSON responses)
22
+ app.useErrorHandler({ exposeStack: false });
23
+
24
+ // start server
25
+ const PORT = process.env.PORT || 4000;
26
+ app.start(PORT);
@@ -0,0 +1,58 @@
1
+ // controllers/usersController.js
2
+ // Simple in-memory users controller for demonstration.
3
+
4
+ let _id = 1;
5
+ const users = [
6
+ { id: _id++, name: "Alice", email: "alice@example.com" },
7
+ { id: _id++, name: "Bob", email: "bob@example.com" },
8
+ ];
9
+
10
+ function getUsers(req, res) {
11
+ res.json({ data: users });
12
+ }
13
+
14
+ function createUser(req, res) {
15
+ const body = req.body || {};
16
+ if (!body.name || !body.email) {
17
+ return res
18
+ .status(400)
19
+ .json({ error: { message: "name and email required" } });
20
+ }
21
+ const user = { id: _id++, name: body.name, email: body.email };
22
+ users.push(user);
23
+ res.status(201).json({ data: user });
24
+ }
25
+
26
+ function getUserById(req, res) {
27
+ const id = Number(req.params.id);
28
+ const u = users.find((x) => x.id === id);
29
+ if (!u) return res.status(404).json({ error: { message: "User not found" } });
30
+ res.json({ data: u });
31
+ }
32
+
33
+ function updateUser(req, res) {
34
+ const id = Number(req.params.id);
35
+ const u = users.find((x) => x.id === id);
36
+ if (!u) return res.status(404).json({ error: { message: "User not found" } });
37
+ const body = req.body || {};
38
+ if (body.name) u.name = body.name;
39
+ if (body.email) u.email = body.email;
40
+ res.json({ data: u });
41
+ }
42
+
43
+ function deleteUser(req, res) {
44
+ const id = Number(req.params.id);
45
+ const idx = users.findIndex((x) => x.id === id);
46
+ if (idx === -1)
47
+ return res.status(404).json({ error: { message: "User not found" } });
48
+ const removed = users.splice(idx, 1)[0];
49
+ res.json({ data: removed });
50
+ }
51
+
52
+ module.exports = {
53
+ getUsers,
54
+ createUser,
55
+ getUserById,
56
+ updateUser,
57
+ deleteUser,
58
+ };
@@ -0,0 +1,13 @@
1
+ // middlewares/authMock.js
2
+ // Simple mock "authentication" middleware for demo purposes.
3
+ // Checks for header "x-api-key: secret" β€” if absent, returns 401.
4
+
5
+ module.exports = function (req, res, next) {
6
+ const key = req.headers["x-api-key"] || req.query.api_key;
7
+ if (!key || key !== "secret") {
8
+ return res
9
+ .status(401)
10
+ .json({ error: { message: "Unauthorized. Provide x-api-key: secret" } });
11
+ }
12
+ next();
13
+ };
@@ -0,0 +1,24 @@
1
+ {
2
+ "name": "kaelum-api-app",
3
+ "version": "0.1.0",
4
+ "description": "Exemplo de app API gerado pela CLI Kaelum",
5
+ "scripts": {
6
+ "start": "node app.js",
7
+ "dev": "nodemon app.js"
8
+ },
9
+ "keywords": [
10
+ "kaelum",
11
+ "api"
12
+ ],
13
+ "author": "",
14
+ "license": "MIT",
15
+ "dependencies": {
16
+ "kaelum": "^1.3.0",
17
+ "cors": "^2.8.5",
18
+ "helmet": "^6.0.0",
19
+ "morgan": "^1.10.0"
20
+ },
21
+ "devDependencies": {
22
+ "nodemon": "^2.0.22"
23
+ }
24
+ }
@@ -0,0 +1,34 @@
1
+ // routes.js - registers API endpoints using app.apiRoute
2
+ const {
3
+ getUsers,
4
+ createUser,
5
+ getUserById,
6
+ updateUser,
7
+ deleteUser,
8
+ } = require("./controllers/usersController");
9
+
10
+ const auth = require("./middlewares/authMock");
11
+
12
+ module.exports = function (app) {
13
+ // Global example: apply auth middleware on /users POST (create)
14
+ // also demonstrate per-path middleware usage via setMiddleware(path, middleware)
15
+ app.setMiddleware("/users", (req, res, next) => {
16
+ // a small wrapper to demonstrate both setMiddleware signature and route-level control
17
+ // allow GET without auth, require auth for POST/PUT/DELETE by checking method
18
+ if (["POST", "PUT", "DELETE"].includes(req.method)) {
19
+ return require("./middlewares/authMock")(req, res, next);
20
+ }
21
+ next();
22
+ });
23
+
24
+ // Resource routes using apiRoute
25
+ app.apiRoute("users", {
26
+ get: getUsers,
27
+ post: createUser,
28
+ "/:id": {
29
+ get: getUserById,
30
+ put: updateUser,
31
+ delete: deleteUser,
32
+ },
33
+ });
34
+ };
@@ -1,15 +1,26 @@
1
+ // app.js - example project generated by Kaelum (Web template)
1
2
  const kaelum = require("kaelum");
3
+
2
4
  const app = kaelum();
3
5
 
4
- // SetConfig para aplicar configuraΓ§Γ΅es de seguranΓ§a e middlewares
6
+ // Enable basic security + static serving via setConfig (uses Kaelum internals)
5
7
  app.setConfig({
6
8
  cors: true,
7
9
  helmet: true,
10
+ static: "public", // will serve ./public
11
+ bodyParser: true, // default enabled β€” explicit for clarity
8
12
  });
9
13
 
10
- // Importa e registra as rotas
14
+ // Register routes (routes.js uses Kaelum helpers)
11
15
  const routes = require("./routes");
12
16
  routes(app);
13
17
 
14
- // Inicia o servidor
15
- app.start(3000);
18
+ // optional: health check endpoint
19
+ app.healthCheck("/health");
20
+
21
+ // install Kaelum default error handler (returns JSON on errors)
22
+ app.useErrorHandler({ exposeStack: false });
23
+
24
+ // Start server (explicit port for template demo)
25
+ const PORT = process.env.PORT || 3000;
26
+ app.start(PORT);