maxserver 0.0.15 → 0.0.16

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
@@ -11,58 +11,63 @@
11
11
  I am simplifying and improving things, that it will work for everyone plugn play.
12
12
 
13
13
 
14
- Ready node server setup based on **Fastify** to speedup api development.
14
+ Ready node server setup based on **Fastify** to speed up backend development.
15
+ maxserver stands for **maximized simplicity** and **minimum boilerplate**.
15
16
 
16
17
  - **Auto Routes**: auto imports and registers routes and schemas
17
18
  - **Auto Docs**: auto generates docs based on schemas
18
- - **Preconfigures JWT auth, Cores, Helmet**
19
+ - **Preconfigures essentials**: jwt auth, cors, helmet
19
20
  - **Auto Connect MongoDB** (optional)
20
21
  - **Dev server**
21
22
  <br><br>
22
23
 
23
- - Dependencies: original fastify packages + scalar/fastify-api-reference (doc generator)
24
- - The source is simple and short. Everyone shall be able to read, understand and modify if needed.
25
-
26
24
 
27
25
  ## Install
28
26
 
29
27
  ### Setup ready project
28
+ ```js
30
29
  npx maxserver [appname]
30
+ ```
31
31
 
32
- ### Install
32
+ ### Or install as packge
33
+ ```js
33
34
  npm install maxserver
35
+ ```
36
+ <br>
34
37
 
35
38
  ## Setup
36
39
  ```js
37
40
  import maxserver from "maxserver";
38
41
 
39
- const server = await maxserver();
40
- await server.listen();
42
+ const server = await maxserver({
43
+ port: 3000,
44
+ secret: "your_secret"
45
+ });
41
46
 
42
- console.log("Server running at ", server.getAddress());
47
+ await server.start();
43
48
  export default server;
44
-
45
49
  ```
46
- ---
50
+ <br>
47
51
 
48
52
  ## ⚙️ Configure
49
- Quick configure your server by passing options to the **maxserver()** or define them in your .env file.
50
- You can also pass any fastify options.
53
+ Configs can be passed to the init call to **maxserver()** or set in your .env file.
54
+ If you define options in env, use all upper case letters.
55
+ Any fastify options can be passed to maxserver() too.
56
+
51
57
 
52
58
  | Variable | Default | Description |
53
59
  | :--- | :--- | :--- |
54
- | `PORT` | `3000` | Server port |
55
- | `JWT_SECRET` | *-* | Enables JWT auth (private-by-default routes) |
56
- | `MONGODB_URI` | *-* | Enables MongoDB auto-connect + global `db` |
57
- | `DOCS` | `true` | Set `false` to disable docs UI at `/docs` |
58
- | `STATIC_DIR` | *(optional)* | Serve static files (example: `./public`) |
59
- | `CORS_ORIGIN` | `*` | Allowed CORS origins |
60
-
61
- ---
62
-
63
- ## 🗂️ Project Structure
60
+ | `port` | `3000` | Server port |
61
+ | `secret` | *-* | Secret used for jwt and cookies |
62
+ | `cors` | `*` | Allowed CORS origins, default all allowed |
63
+ | `docs` | `true` | Set `false` to disable auto generated docs` |
64
+ | `mongodb` | *-* | MongoDB URI, if set auto-connects db |
65
+ | `public` | `false` | Set `true` to expose the server publicly (binds to `0.0.0.0`) |
66
+ | `static` | *-* | If set, serves this directory statically |
67
+ <br><br>
64
68
 
65
- **1 route = 1 handler file + 1 schema file**
69
+ ## 🗂️ Project Structure
70
+ Our golden rule: **1 route = 1 handler file + 1 schema file**
66
71
 
67
72
  Example:
68
73
 
@@ -75,12 +80,11 @@ src/
75
80
  hello.schema.js
76
81
  ...
77
82
  ```
78
-
79
- ---
83
+ <br>
80
84
 
81
85
  ## 🛣️ Handlers
82
86
 
83
- ### 1) Define method + path
87
+ #### 1) Define method + path
84
88
  Start each route file with a comment to define the path.
85
89
  That comment is what the route loader uses to auto-register the route.
86
90
 
@@ -88,13 +92,9 @@ That comment is what the route loader uses to auto-register the route.
88
92
  // GET /teams/:id
89
93
  ```
90
94
 
95
+ #### 2) Export default handler
91
96
 
92
97
 
93
- ### 2) Default export handler
94
-
95
-
96
- ### Example
97
-
98
98
  ```js
99
99
  // GET /teams/:id
100
100
 
@@ -106,89 +106,75 @@ export default async function (req, res) {
106
106
  }
107
107
  ```
108
108
 
109
- ---
109
+ <br>
110
+ <br>
111
+
110
112
 
111
- ## 🧾 Define Schemas
112
- Create a sibling file ending in **`.schema.js`**.
113
- The schema is offcourse a jsonschema.
114
- This file will be auto registered.
113
+ ## 🧾 Schemas
114
+ Create a sibling file ending with **`.schema.js`**, so it will be auto registered.
115
+ For example: **hello.js** and **hello.schema.js**
115
116
 
116
- Schemas:
117
- - validate inputs
118
- - generate OpenAPI docs
119
- - control route options for example (public/private)
117
+ Besides the basic validation fields we can set fields like summary and description,
118
+ which will appear in the docs. Mostly you don't need to write schemas yourself, chat gpt and gemini do it excelently.
120
119
 
121
120
 
122
- **`Important - use default export`**
123
121
  ```js
124
122
  export default {
125
- tags: ["Teams"],
126
- summary: "Get team",
127
- description: "Returns a single team by identifier.",
128
123
 
129
- params: {
124
+ tags: ["Test"],
125
+ summary: "Post hello",
126
+ description: "Accepts a name and returns a greeting.",
127
+
128
+ body: {
130
129
  type: "object",
131
- required: ["id"],
130
+ required: ["name"],
132
131
  properties: {
133
- id: {
132
+ name: {
134
133
  type: "string",
135
- minLength: 24,
136
- example: "",
137
134
  },
138
135
  },
139
136
  },
140
137
 
141
- response: {
142
- 200: {
143
- type: "object",
144
- properties: {
145
- id: {
146
- type: "string",
147
- example: "507f1f77bcf86cd799439011",
148
- },
149
- name: { type: "string", example: "Team A" },
150
- },
151
- required: ["id", "name"],
152
- },
153
- },
138
+ ...
154
139
  };
155
140
  ```
156
141
 
157
- <br>
158
-
159
-
160
- ## 📚 API Docs
161
-
162
- - Open in your browser **`localhost:3000/docs`**
163
- - OpenAPI JSON: **`/openapi.json`**
142
+ **`‼️ Important use export default`**
164
143
 
165
144
  <br>
166
145
 
146
+ ## Route Options
147
+ Though we don't register routes manually, we don't set route options on the register call. If needed, route options can be set on the schema object.
148
+ For example **schema.auth = true** to enable authentication.
149
+ So the schema holds all configs about a root and handlers the pure logic.
167
150
 
151
+ <br>
168
152
 
153
+ ## 📚 API Docs
169
154
 
170
- ## 🔐 Authentication (JWT)
155
+ Open in your browser **`localhost:3000/docs`**
156
+ OpenAPI JSON: **`/openapi.json`**
171
157
 
172
- Enable auth by setting:
158
+ <br>
173
159
 
174
- - `JWT_SECRET`
175
160
 
176
- Behavior:
177
- - **Private by default**: routes require JWT (cookie or Bearer header)
178
- - Authenticated user identifier is available as **`req.userId`**
179
- - Make a route **public** by setting `config.public: true` in its schema
161
+ ## 🔐 Authentication
162
+ JWT header and cookie based auth is preconfigured.
163
+ To enable auth for a route set in it's schema **auth = true**
164
+ The authenticated user is available as **`req.userId`**
180
165
 
181
166
  ```js
167
+ // Inside schema
168
+
182
169
  export default {
183
- config: { public: true },
184
- // ...
170
+ auth: true
185
171
  };
186
172
  ```
187
173
 
188
174
  <br>
189
175
 
190
176
  ## 🍃 MongoDB
191
- Define in the env **`MONGODB_URI`** and it will auto-connect at server start and you get:
177
+ Set option **`MONGODB`** your mongodbURI and it will auto-connect at server start and you get:
192
178
 
193
179
  - global **`db`** (connected database handle)
194
180
  - global **`oid(string)`** (string → MongoDB `ObjectId`)
@@ -198,8 +184,19 @@ Define in the env **`MONGODB_URI`** and it will auto-connect at server start and
198
184
  | `db` | MongoDB database handle | Use it directly in handlers |
199
185
  | `oid(id)` | string → `ObjectId` | Avoid importing `ObjectId` everywhere |
200
186
 
187
+ ### Example
201
188
 
202
- ---
189
+ ```js
190
+ // Inside route handlers
191
+
192
+ export default async function (req, res) {
193
+
194
+ await db.feedback.insert(...)
195
+ }
196
+ ```
197
+
198
+
199
+ <br>
203
200
 
204
201
  ## 🧰 Error Handling
205
202
 
@@ -211,9 +208,13 @@ if (!user) throw createError(404, "User not found");
211
208
 
212
209
  Rule of thumb: make the message something you would want to see at 03:00 in logs.
213
210
 
214
- ---
211
+ <br>
215
212
 
213
+ ## About
214
+ - Dependencies: original fastify packages + scalar/fastify-api-reference
215
+ - The source is simple. Everyone can read, understand and modify if needed.
216
216
 
217
+ <br>
217
218
 
218
219
  ## 🛠️ Tips & Tools
219
220
 
@@ -230,5 +231,6 @@ In `.vscode/tasks.json`, enable the task with:
230
231
  "runOptions": { "runOn": "folderOpen" }
231
232
  ```
232
233
 
233
- ### 🤖 AI Assistants (Code Style)
234
- Copy **`RULES.md`** into your AI tool as system context, then ask it to generate routes + schemas.
234
+ ### 🤖 AI Assistants
235
+ Copy **`RULES.md`** into your AI tool as system context,
236
+ then ask it to generate routes + schemas.
package/bin/init.js CHANGED
@@ -15,17 +15,17 @@ function main() {
15
15
  }
16
16
 
17
17
  try {
18
- console.log(`🚀 Creating "${projectName}"...`);
18
+ console.log(`🚀 Setting up "${projectName}"...`);
19
19
  fs.cpSync(templateDir, targetDir, { recursive: true });
20
20
 
21
21
  fixDotfiles(targetDir);
22
22
  patchPackageJson(targetDir, projectName);
23
23
 
24
24
  process.chdir(targetDir);
25
- console.log("📦 Installing maxserver...");
25
+ console.log("📦 Installing maxserver");
26
26
  execSync("npm install maxserver@latest", { stdio: "inherit" });
27
27
 
28
- console.log("\n✅ Done! Your project is ready. 😊");
28
+ console.log(`\n✅ Install complete\n\ncd ${projectName}\nnpm run dev\n`);
29
29
  } catch (err) {
30
30
  console.error("❌ Init failed:", err.message);
31
31
  process.exit(1);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "maxserver",
3
- "version": "0.0.15",
3
+ "version": "0.0.16",
4
4
  "description": "Node server setup based fastify",
5
5
  "author": "Max Matinpalo",
6
6
  "type": "module",
package/src/getAddress.js CHANGED
@@ -1,29 +1,5 @@
1
1
  import os from "node:os";
2
2
 
3
- export function setupGetAddress(app) {
4
- app.decorate("getAddress", () => {
5
- const addr = app.server?.address();
6
- const protocol = app.initialConfig?.https ? "https" : "http";
7
-
8
- if (!addr) return null;
9
- if (typeof addr === "string") return addr;
10
-
11
- const isPublicBind = addr.address === "0.0.0.0" || addr.address === "::";
12
- const isLoopback = addr.address === "127.0.0.1" || addr.address === "::1";
13
-
14
- const envIp = String(process.env.PUBLIC_IP || "").trim() || null;
15
- const detectedIp = envIp || getLanIp();
16
-
17
- const ip = (isPublicBind && !isLoopback)
18
- ? (detectedIp || addr.address)
19
- : "localhost";
20
-
21
- const host = ip.includes(":") ? `[${ip}]` : ip;
22
-
23
- return `${protocol}://${host}:${addr.port}`;
24
- });
25
- }
26
-
27
3
  function getLanIp() {
28
4
  const nets = os.networkInterfaces();
29
5
 
@@ -35,3 +11,28 @@ function getLanIp() {
35
11
 
36
12
  return null;
37
13
  }
14
+
15
+
16
+ export function getAddress(app) {
17
+
18
+ const addr = app.server?.address();
19
+ const protocol = app.initialConfig?.https ? "https" : "http";
20
+
21
+ if (!addr) return null;
22
+ if (typeof addr === "string") return addr;
23
+
24
+ const isPublicBind = addr.address === "0.0.0.0" || addr.address === "::";
25
+ const isLoopback = addr.address === "127.0.0.1" || addr.address === "::1";
26
+
27
+ const envIp = String(process.env.PUBLIC_IP || "").trim() || null;
28
+ const detectedIp = envIp || getLanIp();
29
+
30
+ const ip = (isPublicBind && !isLoopback)
31
+ ? (detectedIp || addr.address)
32
+ : "localhost";
33
+
34
+ const host = ip.includes(":") ? `[${ip}]` : ip;
35
+
36
+ return `${protocol}://${host}:${addr.port}`;
37
+ }
38
+
package/src/index.js CHANGED
@@ -12,7 +12,7 @@ import {
12
12
  getHttpsOptions,
13
13
  } from "./setup.js";
14
14
 
15
- import { setupGetAddress } from "./getAddress.js";
15
+ import { getAddress } from "./getAddress.js";
16
16
 
17
17
 
18
18
  export default async function maxserver(config = {}) {
@@ -20,47 +20,52 @@ export default async function maxserver(config = {}) {
20
20
  const {
21
21
 
22
22
  // maxserver options
23
+ port = Number(process.env.PORT || 3000),
23
24
  secret = process.env.SECRET,
24
- cookieSecret = process.env.COOKIE_SECRET,
25
- mongodbUri = process.env.MONGODB_URI,
25
+ mongodb = process.env.MONGODB,
26
26
  docs = process.env.DOCS !== "false",
27
- staticDir = process.env.STATIC_DIR,
28
- corsOrigin = process.env.CORS_ORIGIN || "*",
27
+ cors = process.env.CORS || "*",
28
+ openapiInfo,
29
+ static: isStatic = process.env.STATIC,
30
+ public: isPublic = process.env.PUBLIC === "true",
29
31
 
30
32
  // everything else goes straight to Fastify
31
33
  ...fastifyOpts
32
34
 
33
35
  } = config;
34
36
 
35
- if (process.env.NODE_ENV == "development") {
36
- console.error("Please define secret in env !");
37
-
38
- }
39
-
40
- let maxserverConfig = {
41
- secret, mongodbUri, docs, staticDir, corsOrigin
37
+ const maxserverConfig = {
38
+ port, secret, mongodb, docs, cors,
39
+ static: isStatic,
40
+ public: isPublic
42
41
  };
43
42
 
44
- process.env.NODE_ENV;
45
-
46
-
47
-
48
43
 
49
- const app = Fastify({
44
+ let app;
45
+ try {
46
+ app = Fastify({
47
+ https: getHttpsOptions() || undefined,
48
+ trustProxy: true,
50
49
 
51
- https: getHttpsOptions() || undefined,
52
- trustProxy: true,
50
+ // Required to allow adding doc fields on schema
51
+ ajv: { customOptions: { strictSchema: false } },
52
+ ...fastifyOpts
53
+ });
54
+ } catch (err) {
55
+ console.error("❌ Fastify initialization failed:", err);
56
+ throw err;
57
+ }
53
58
 
54
- // To allow writing example value fields to schemas for doucumentation
55
- ajv: { customOptions: { strictSchema: false } },
56
- ...fastifyOpts,
57
- });
58
59
 
59
60
  app.decorate("maxserver", maxserverConfig);
60
61
 
62
+ app.decorate("start", async function () {
63
+ const port = this.maxserver.port ?? 3000;
64
+ const host = this.maxserver.public ? '0.0.0.0' : '127.0.0.1';
65
+ await this.listen({ port, host });
66
+ console.log('Server running at ', getAddress(this));
67
+ });
61
68
 
62
-
63
- setupGetAddress(app);
64
69
  await setupCookie(app);
65
70
  await setupHelmet(app);
66
71
  await setupCors(app);
@@ -70,13 +75,11 @@ export default async function maxserver(config = {}) {
70
75
  await setupDocs(app);
71
76
  await setupRoutes(app);
72
77
 
73
-
74
78
  global.createError = function (code, message) {
75
79
  const err = new Error(message);
76
80
  err.statusCode = code;
77
81
  return err;
78
82
  };
79
83
 
80
-
81
84
  return app;
82
85
  }
package/src/setup.js CHANGED
@@ -27,18 +27,21 @@ export async function setupHelmet(app) {
27
27
  }
28
28
 
29
29
 
30
+
31
+
30
32
  export async function setupCors(app) {
31
33
  const isProd = process.env.NODE_ENV === "production";
32
- const origin = app.maxserver.corsOrigin ?? true;
34
+ const origin = app.maxserver.cors ?? "*";
33
35
 
34
- if (isProd && origin === true) {
35
- app.log.warn("CORS origin not set, allowing all origins");
36
+ if (isProd && origin === "*") {
37
+ app.log.warn("CORS: allowing all origins (*) in production");
36
38
  }
37
39
 
38
40
  await app.register(cors, { origin });
39
41
  }
40
42
 
41
43
 
44
+
42
45
  export function getHttpsOptions() {
43
46
  const { TLS_KEY, TLS_CERT } = process.env;
44
47
  if (!TLS_KEY || !TLS_CERT) return null;
@@ -91,7 +94,7 @@ export async function setupJwt(app) {
91
94
 
92
95
 
93
96
  export async function setupMongo(app) {
94
- const url = app.maxserver.mongodbUri;
97
+ const url = app.maxserver.mongodb;
95
98
  if (!url) return;
96
99
  await app.register(mongodb, { url });
97
100
 
@@ -139,7 +142,7 @@ export async function setupDocs(app) {
139
142
 
140
143
 
141
144
  export async function setupStatic(app) {
142
- const dir = app.maxserver.staticDir;
145
+ const dir = app.maxserver.static;
143
146
  if (!dir) return;
144
147
 
145
148
  await app.register(fastifyStatic, {
package/templates/env CHANGED
@@ -1,24 +1,8 @@
1
- # Environment
2
- NODE_ENV = development
3
-
4
- # Server
5
- PORT = 3000
6
-
7
- # CORS(prod only, ignored in dev)
8
- # CORS_ORIGIN = https://app.example.com
9
-
10
- # JWT;
11
- # JWT_SECRET = supersecretkey
12
1
 
13
- # MongoDB;
14
- # MONGODB_URI = mongodb://127.0.0.1:27017/testdb
15
-
16
- # Static files
17
- # STATIC_DIR =./ public
18
-
19
- # API Docs in production
20
- DOCS = 1
2
+ NODE_ENV = development
21
3
 
22
- # Optional HTTPS(direct TLS, no nginx)
23
- # TLS_KEY = /etc/ssl / private / server.key
24
- # TLS_CERT = /etc/ssl / certs / server.crt;
4
+ # PORT
5
+ # CORS
6
+ # SECRET
7
+ # MONGODB
8
+ # STATIC
@@ -1,10 +1,9 @@
1
1
  import maxserver from "maxserver";
2
2
 
3
- const server = await maxserver();
4
-
5
- await server.listen({
6
- port: Number(process.env.PORT || 3000),
3
+ const server = await maxserver({
4
+ port: 3000,
5
+ secret: "your_secret"
7
6
  });
8
7
 
9
- console.log("Server running at ", server.getAddress());
8
+ await server.start();
10
9
  export default server;