maxserver 0.0.14 → 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 +86 -89
- package/bin/init.js +3 -3
- package/package.json +1 -1
- package/src/getAddress.js +25 -24
- package/src/index.js +51 -15
- package/src/setup.js +58 -54
- package/templates/env +6 -22
- package/templates/server.js +4 -5
package/README.md
CHANGED
|
@@ -11,63 +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
|
|
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
|
|
19
|
+
- **Preconfigures essentials**: jwt auth, cors, helmet
|
|
19
20
|
- **Auto Connect MongoDB** (optional)
|
|
20
21
|
- **Dev server**
|
|
21
22
|
<br><br>
|
|
22
23
|
|
|
23
24
|
|
|
24
|
-
- Dependencies: original fastify packages + scalar/fastify-api-reference (doc generator)
|
|
25
|
-
- The source is simple and short. Everyone shall be able to read, understand and modify if needed.
|
|
26
|
-
|
|
27
|
-
|
|
28
25
|
## Install
|
|
29
26
|
|
|
30
27
|
### Setup ready project
|
|
28
|
+
```js
|
|
31
29
|
npx maxserver [appname]
|
|
30
|
+
```
|
|
32
31
|
|
|
33
|
-
###
|
|
32
|
+
### Or install as packge
|
|
33
|
+
```js
|
|
34
34
|
npm install maxserver
|
|
35
|
+
```
|
|
36
|
+
<br>
|
|
35
37
|
|
|
36
38
|
## Setup
|
|
37
39
|
```js
|
|
38
40
|
import maxserver from "maxserver";
|
|
39
|
-
|
|
40
|
-
const
|
|
41
|
-
port:
|
|
41
|
+
|
|
42
|
+
const server = await maxserver({
|
|
43
|
+
port: 3000,
|
|
44
|
+
secret: "your_secret"
|
|
42
45
|
});
|
|
43
46
|
|
|
44
|
-
|
|
47
|
+
await server.start();
|
|
45
48
|
export default server;
|
|
46
49
|
```
|
|
50
|
+
<br>
|
|
47
51
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
---
|
|
52
|
+
## ⚙️ Configure
|
|
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.
|
|
53
56
|
|
|
54
|
-
## ⚙️ Configuration
|
|
55
|
-
Configure the server by setting variabels in your .env file.
|
|
56
57
|
|
|
57
58
|
| Variable | Default | Description |
|
|
58
59
|
| :--- | :--- | :--- |
|
|
59
|
-
| `
|
|
60
|
-
| `
|
|
61
|
-
| `
|
|
62
|
-
| `
|
|
63
|
-
| `
|
|
64
|
-
| `
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
## 🗂️ 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>
|
|
69
68
|
|
|
70
|
-
|
|
69
|
+
## 🗂️ Project Structure
|
|
70
|
+
Our golden rule: **1 route = 1 handler file + 1 schema file**
|
|
71
71
|
|
|
72
72
|
Example:
|
|
73
73
|
|
|
@@ -80,12 +80,11 @@ src/
|
|
|
80
80
|
hello.schema.js
|
|
81
81
|
...
|
|
82
82
|
```
|
|
83
|
-
|
|
84
|
-
---
|
|
83
|
+
<br>
|
|
85
84
|
|
|
86
85
|
## 🛣️ Handlers
|
|
87
86
|
|
|
88
|
-
|
|
87
|
+
#### 1) Define method + path
|
|
89
88
|
Start each route file with a comment to define the path.
|
|
90
89
|
That comment is what the route loader uses to auto-register the route.
|
|
91
90
|
|
|
@@ -93,13 +92,9 @@ That comment is what the route loader uses to auto-register the route.
|
|
|
93
92
|
// GET /teams/:id
|
|
94
93
|
```
|
|
95
94
|
|
|
95
|
+
#### 2) Export default handler
|
|
96
96
|
|
|
97
97
|
|
|
98
|
-
### 2) Default export handler
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
### Example
|
|
102
|
-
|
|
103
98
|
```js
|
|
104
99
|
// GET /teams/:id
|
|
105
100
|
|
|
@@ -111,89 +106,75 @@ export default async function (req, res) {
|
|
|
111
106
|
}
|
|
112
107
|
```
|
|
113
108
|
|
|
114
|
-
|
|
109
|
+
<br>
|
|
110
|
+
<br>
|
|
111
|
+
|
|
115
112
|
|
|
116
|
-
## 🧾
|
|
117
|
-
Create a sibling file ending
|
|
118
|
-
|
|
119
|
-
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**
|
|
120
116
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
- generate OpenAPI docs
|
|
124
|
-
- 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.
|
|
125
119
|
|
|
126
120
|
|
|
127
|
-
**`Important - use default export`**
|
|
128
121
|
```js
|
|
129
122
|
export default {
|
|
130
|
-
tags: ["Teams"],
|
|
131
|
-
summary: "Get team",
|
|
132
|
-
description: "Returns a single team by identifier.",
|
|
133
123
|
|
|
134
|
-
|
|
124
|
+
tags: ["Test"],
|
|
125
|
+
summary: "Post hello",
|
|
126
|
+
description: "Accepts a name and returns a greeting.",
|
|
127
|
+
|
|
128
|
+
body: {
|
|
135
129
|
type: "object",
|
|
136
|
-
required: ["
|
|
130
|
+
required: ["name"],
|
|
137
131
|
properties: {
|
|
138
|
-
|
|
132
|
+
name: {
|
|
139
133
|
type: "string",
|
|
140
|
-
minLength: 24,
|
|
141
|
-
example: "",
|
|
142
134
|
},
|
|
143
135
|
},
|
|
144
136
|
},
|
|
145
137
|
|
|
146
|
-
|
|
147
|
-
200: {
|
|
148
|
-
type: "object",
|
|
149
|
-
properties: {
|
|
150
|
-
id: {
|
|
151
|
-
type: "string",
|
|
152
|
-
example: "507f1f77bcf86cd799439011",
|
|
153
|
-
},
|
|
154
|
-
name: { type: "string", example: "Team A" },
|
|
155
|
-
},
|
|
156
|
-
required: ["id", "name"],
|
|
157
|
-
},
|
|
158
|
-
},
|
|
138
|
+
...
|
|
159
139
|
};
|
|
160
140
|
```
|
|
161
141
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
## 📚 API Docs
|
|
166
|
-
|
|
167
|
-
- Open in your browser **`localhost:3000/docs`**
|
|
168
|
-
- OpenAPI JSON: **`/openapi.json`**
|
|
142
|
+
**`‼️ Important use export default`**
|
|
169
143
|
|
|
170
144
|
<br>
|
|
171
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.
|
|
172
150
|
|
|
151
|
+
<br>
|
|
173
152
|
|
|
153
|
+
## 📚 API Docs
|
|
174
154
|
|
|
175
|
-
|
|
155
|
+
Open in your browser **`localhost:3000/docs`**
|
|
156
|
+
OpenAPI JSON: **`/openapi.json`**
|
|
176
157
|
|
|
177
|
-
|
|
158
|
+
<br>
|
|
178
159
|
|
|
179
|
-
- `JWT_SECRET`
|
|
180
160
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
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`**
|
|
185
165
|
|
|
186
166
|
```js
|
|
167
|
+
// Inside schema
|
|
168
|
+
|
|
187
169
|
export default {
|
|
188
|
-
|
|
189
|
-
// ...
|
|
170
|
+
auth: true
|
|
190
171
|
};
|
|
191
172
|
```
|
|
192
173
|
|
|
193
174
|
<br>
|
|
194
175
|
|
|
195
176
|
## 🍃 MongoDB
|
|
196
|
-
|
|
177
|
+
Set option **`MONGODB`** your mongodbURI and it will auto-connect at server start and you get:
|
|
197
178
|
|
|
198
179
|
- global **`db`** (connected database handle)
|
|
199
180
|
- global **`oid(string)`** (string → MongoDB `ObjectId`)
|
|
@@ -203,8 +184,19 @@ Define in the env **`MONGODB_URI`** and it will auto-connect at server start and
|
|
|
203
184
|
| `db` | MongoDB database handle | Use it directly in handlers |
|
|
204
185
|
| `oid(id)` | string → `ObjectId` | Avoid importing `ObjectId` everywhere |
|
|
205
186
|
|
|
187
|
+
### Example
|
|
188
|
+
|
|
189
|
+
```js
|
|
190
|
+
// Inside route handlers
|
|
191
|
+
|
|
192
|
+
export default async function (req, res) {
|
|
193
|
+
|
|
194
|
+
await db.feedback.insert(...)
|
|
195
|
+
}
|
|
196
|
+
```
|
|
197
|
+
|
|
206
198
|
|
|
207
|
-
|
|
199
|
+
<br>
|
|
208
200
|
|
|
209
201
|
## 🧰 Error Handling
|
|
210
202
|
|
|
@@ -216,9 +208,13 @@ if (!user) throw createError(404, "User not found");
|
|
|
216
208
|
|
|
217
209
|
Rule of thumb: make the message something you would want to see at 03:00 in logs.
|
|
218
210
|
|
|
219
|
-
|
|
211
|
+
<br>
|
|
220
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.
|
|
221
216
|
|
|
217
|
+
<br>
|
|
222
218
|
|
|
223
219
|
## 🛠️ Tips & Tools
|
|
224
220
|
|
|
@@ -235,5 +231,6 @@ In `.vscode/tasks.json`, enable the task with:
|
|
|
235
231
|
"runOptions": { "runOn": "folderOpen" }
|
|
236
232
|
```
|
|
237
233
|
|
|
238
|
-
### 🤖 AI Assistants
|
|
239
|
-
Copy **`RULES.md`** into your AI tool as system context,
|
|
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(`🚀
|
|
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(
|
|
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
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,29 +12,60 @@ import {
|
|
|
12
12
|
getHttpsOptions,
|
|
13
13
|
} from "./setup.js";
|
|
14
14
|
|
|
15
|
-
import {
|
|
15
|
+
import { getAddress } from "./getAddress.js";
|
|
16
16
|
|
|
17
17
|
|
|
18
|
+
export default async function maxserver(config = {}) {
|
|
18
19
|
|
|
19
|
-
|
|
20
|
+
const {
|
|
20
21
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
22
|
+
// maxserver options
|
|
23
|
+
port = Number(process.env.PORT || 3000),
|
|
24
|
+
secret = process.env.SECRET,
|
|
25
|
+
mongodb = process.env.MONGODB,
|
|
26
|
+
docs = process.env.DOCS !== "false",
|
|
27
|
+
cors = process.env.CORS || "*",
|
|
28
|
+
openapiInfo,
|
|
29
|
+
static: isStatic = process.env.STATIC,
|
|
30
|
+
public: isPublic = process.env.PUBLIC === "true",
|
|
25
31
|
|
|
26
|
-
//
|
|
27
|
-
|
|
28
|
-
...options,
|
|
29
|
-
});
|
|
32
|
+
// everything else goes straight to Fastify
|
|
33
|
+
...fastifyOpts
|
|
30
34
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
+
} = config;
|
|
36
|
+
|
|
37
|
+
const maxserverConfig = {
|
|
38
|
+
port, secret, mongodb, docs, cors,
|
|
39
|
+
static: isStatic,
|
|
40
|
+
public: isPublic
|
|
35
41
|
};
|
|
36
42
|
|
|
37
|
-
|
|
43
|
+
|
|
44
|
+
let app;
|
|
45
|
+
try {
|
|
46
|
+
app = Fastify({
|
|
47
|
+
https: getHttpsOptions() || undefined,
|
|
48
|
+
trustProxy: true,
|
|
49
|
+
|
|
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
|
+
}
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
app.decorate("maxserver", maxserverConfig);
|
|
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
|
+
});
|
|
68
|
+
|
|
38
69
|
await setupCookie(app);
|
|
39
70
|
await setupHelmet(app);
|
|
40
71
|
await setupCors(app);
|
|
@@ -44,6 +75,11 @@ export default async function maxserver(options = {}) {
|
|
|
44
75
|
await setupDocs(app);
|
|
45
76
|
await setupRoutes(app);
|
|
46
77
|
|
|
78
|
+
global.createError = function (code, message) {
|
|
79
|
+
const err = new Error(message);
|
|
80
|
+
err.statusCode = code;
|
|
81
|
+
return err;
|
|
82
|
+
};
|
|
47
83
|
|
|
48
84
|
return app;
|
|
49
85
|
}
|
package/src/setup.js
CHANGED
|
@@ -12,20 +12,11 @@ import apiReference from "@scalar/fastify-api-reference";
|
|
|
12
12
|
|
|
13
13
|
import { loadRoutes } from "./routeLoader.js";
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
// Use COOKIE_SECRET or fall back to JWT_SECRET so you don't need a new env var immediately
|
|
18
|
-
const secret = process.env.COOKIE_SECRET || process.env.JWT_SECRET || "change-me-in-prod";
|
|
19
|
-
|
|
20
|
-
await app.register(cookie, {
|
|
21
|
-
secret,
|
|
22
|
-
hook: "onRequest", // Crucial: Ensures cookies are parsed before your route handlers run
|
|
23
|
-
parseOptions: {}
|
|
24
|
-
});
|
|
15
|
+
export async function setupRoutes(app) {
|
|
16
|
+
await loadRoutes(app);
|
|
25
17
|
}
|
|
26
18
|
|
|
27
19
|
|
|
28
|
-
|
|
29
20
|
export async function setupHelmet(app) {
|
|
30
21
|
await app.register(helmet, {
|
|
31
22
|
contentSecurityPolicy: false,
|
|
@@ -35,21 +26,22 @@ export async function setupHelmet(app) {
|
|
|
35
26
|
});
|
|
36
27
|
}
|
|
37
28
|
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
|
|
38
32
|
export async function setupCors(app) {
|
|
39
33
|
const isProd = process.env.NODE_ENV === "production";
|
|
40
|
-
const origin =
|
|
34
|
+
const origin = app.maxserver.cors ?? "*";
|
|
41
35
|
|
|
42
|
-
if (isProd &&
|
|
36
|
+
if (isProd && origin === "*") {
|
|
37
|
+
app.log.warn("CORS: allowing all origins (*) in production");
|
|
38
|
+
}
|
|
43
39
|
|
|
44
40
|
await app.register(cors, { origin });
|
|
45
41
|
}
|
|
46
42
|
|
|
47
43
|
|
|
48
44
|
|
|
49
|
-
export async function setupRoutes(app) {
|
|
50
|
-
await loadRoutes(app);
|
|
51
|
-
}
|
|
52
|
-
|
|
53
45
|
export function getHttpsOptions() {
|
|
54
46
|
const { TLS_KEY, TLS_CERT } = process.env;
|
|
55
47
|
if (!TLS_KEY || !TLS_CERT) return null;
|
|
@@ -67,67 +59,63 @@ export function getHttpsOptions() {
|
|
|
67
59
|
|
|
68
60
|
|
|
69
61
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
if (url.startsWith("/static/")) return true;
|
|
76
|
-
return false;
|
|
62
|
+
export async function setupCookie(app) {
|
|
63
|
+
await app.register(cookie, {
|
|
64
|
+
secret: app.maxserver.secret || "supersecret",
|
|
65
|
+
hook: "onRequest"
|
|
66
|
+
});
|
|
77
67
|
}
|
|
78
68
|
|
|
69
|
+
|
|
79
70
|
export async function setupJwt(app) {
|
|
80
|
-
const secret = process.env.JWT_SECRET;
|
|
81
|
-
if (!secret) return;
|
|
82
71
|
|
|
83
|
-
|
|
84
|
-
|
|
72
|
+
await app.register(jwt, {
|
|
73
|
+
secret: app.maxserver.secret || "supersecret",
|
|
74
|
+
cookie: { cookieName: "token" }
|
|
75
|
+
});
|
|
85
76
|
|
|
86
|
-
|
|
77
|
+
app.addHook("preHandler", async function (req) {
|
|
87
78
|
|
|
88
|
-
|
|
79
|
+
// Let preflight requests pass
|
|
89
80
|
if (req.method === "OPTIONS") return;
|
|
90
81
|
|
|
91
|
-
const
|
|
92
|
-
|
|
93
|
-
|
|
82
|
+
const auth =
|
|
83
|
+
req.routeOptions?.config?.auth ??
|
|
84
|
+
req.routeOptions?.schema?.auth;
|
|
94
85
|
|
|
95
|
-
|
|
86
|
+
if (!auth) return;
|
|
96
87
|
|
|
88
|
+
await req.jwtVerify();
|
|
97
89
|
const u = req.user;
|
|
98
90
|
req.userId = u?.sub || u?.userId || u?.userid || u?.id || null;
|
|
99
91
|
});
|
|
92
|
+
|
|
100
93
|
}
|
|
101
94
|
|
|
102
|
-
export async function setupMongo(app) {
|
|
103
95
|
|
|
104
|
-
|
|
96
|
+
export async function setupMongo(app) {
|
|
97
|
+
const url = app.maxserver.mongodb;
|
|
105
98
|
if (!url) return;
|
|
106
|
-
|
|
107
99
|
await app.register(mongodb, { url });
|
|
108
100
|
|
|
109
|
-
// ObjectId is available on app.mongo after registration
|
|
110
101
|
const { ObjectId, db } = app.mongo;
|
|
111
|
-
|
|
112
102
|
global.oid = id => new ObjectId(id ? String(id) : undefined);
|
|
113
103
|
global.db = db;
|
|
114
104
|
}
|
|
115
105
|
|
|
116
|
-
export async function setupStatic(app) {
|
|
117
|
-
const dir = process.env.STATIC_DIR;
|
|
118
|
-
if (!dir) return;
|
|
119
|
-
|
|
120
|
-
await app.register(fastifyStatic, {
|
|
121
|
-
root: path.resolve(dir),
|
|
122
|
-
prefix: "/static/",
|
|
123
|
-
});
|
|
124
|
-
}
|
|
125
106
|
|
|
126
107
|
export async function setupDocs(app) {
|
|
127
|
-
|
|
108
|
+
|
|
109
|
+
const info = app.maxserver.openapiInfo || {
|
|
110
|
+
title: "API",
|
|
111
|
+
version: "1.0.0",
|
|
112
|
+
};
|
|
128
113
|
|
|
129
114
|
await app.register(swagger, {
|
|
130
115
|
openapi: {
|
|
116
|
+
info,
|
|
117
|
+
// OpenAPI 3.x: securitySchemes must be defined globally here not per route
|
|
118
|
+
// Routes only add `security: [...]` that references these scheme names
|
|
131
119
|
components: {
|
|
132
120
|
securitySchemes: {
|
|
133
121
|
bearerAuth: { type: "http", scheme: "bearer" },
|
|
@@ -137,13 +125,29 @@ export async function setupDocs(app) {
|
|
|
137
125
|
},
|
|
138
126
|
});
|
|
139
127
|
|
|
140
|
-
app.get("/openapi.json", { config: { public: true } }, () => app.swagger());
|
|
141
128
|
|
|
142
|
-
|
|
129
|
+
app.get("/openapi.json", {}, () => app.swagger());
|
|
130
|
+
|
|
131
|
+
if (app.maxserver.docs != false)
|
|
132
|
+
await app.register(apiReference, { routePrefix: "/docs", openapi: true });
|
|
143
133
|
|
|
144
134
|
app.addHook("onRoute", (route) => {
|
|
145
|
-
|
|
135
|
+
const auth = route.config?.auth ?? route.schema?.auth;
|
|
136
|
+
if (!auth) return;
|
|
146
137
|
route.schema ||= {};
|
|
147
|
-
route.schema.security
|
|
138
|
+
route.schema.security ||= [{ bearerAuth: [] }, { cookieAuth: [] }];
|
|
148
139
|
});
|
|
149
|
-
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
export async function setupStatic(app) {
|
|
145
|
+
const dir = app.maxserver.static;
|
|
146
|
+
if (!dir) return;
|
|
147
|
+
|
|
148
|
+
await app.register(fastifyStatic, {
|
|
149
|
+
root: path.resolve(dir),
|
|
150
|
+
prefix: "/static/",
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
|
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
|
-
|
|
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
|
-
#
|
|
23
|
-
#
|
|
24
|
-
#
|
|
4
|
+
# PORT
|
|
5
|
+
# CORS
|
|
6
|
+
# SECRET
|
|
7
|
+
# MONGODB
|
|
8
|
+
# STATIC
|
package/templates/server.js
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import maxserver from "maxserver";
|
|
2
2
|
|
|
3
|
-
const server = await maxserver(
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
port: Number(process.env.PORT || 3000),
|
|
3
|
+
const server = await maxserver({
|
|
4
|
+
port: 3000,
|
|
5
|
+
secret: "your_secret"
|
|
7
6
|
});
|
|
8
7
|
|
|
9
|
-
|
|
8
|
+
await server.start();
|
|
10
9
|
export default server;
|