maxserver 0.0.17 โ 0.1.1
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 +21 -19
- package/package.json +1 -1
- package/src/getAddress.js +8 -0
- package/src/index.js +7 -5
- package/src/setup.js +33 -49
- package/src/{routeLoader.js โ setupRoutes.js} +3 -3
package/README.md
CHANGED
|
@@ -64,7 +64,10 @@ Any fastify options can be passed to maxserver() too.
|
|
|
64
64
|
| `mongodb` | *-* | MongoDB URI, if set auto-connects db |
|
|
65
65
|
| `public` | `false` | Set `true` to expose the server publicly (binds to `0.0.0.0`) |
|
|
66
66
|
| `static` | *-* | If set, serves this directory statically |
|
|
67
|
-
|
|
67
|
+
| `routesDir` | *src* | Directory to auto collect routes |
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
<br>
|
|
68
71
|
|
|
69
72
|
## ๐๏ธ Project Structure
|
|
70
73
|
Our golden rule: **1 route = 1 handler file + 1 schema file**
|
|
@@ -82,21 +85,16 @@ src/
|
|
|
82
85
|
```
|
|
83
86
|
<br>
|
|
84
87
|
|
|
85
|
-
##
|
|
86
|
-
|
|
87
|
-
#### 1) Define method + path
|
|
88
|
-
Start each route file with a comment to define the path.
|
|
89
|
-
That comment is what the route loader uses to auto-register the route.
|
|
90
|
-
|
|
91
|
-
```js
|
|
92
|
-
// GET /teams/:id
|
|
93
|
-
```
|
|
94
|
-
|
|
95
|
-
#### 2) Export default handler
|
|
88
|
+
## ๐ค Auto Routing
|
|
96
89
|
|
|
90
|
+
To auto-register routes, simple add a comment of the form:
|
|
91
|
+
**// METHOD /path**
|
|
92
|
+
**// GET /user**
|
|
93
|
+
**// POST /feedback/something**
|
|
94
|
+
...
|
|
97
95
|
|
|
98
96
|
```js
|
|
99
|
-
// GET /
|
|
97
|
+
// GET /example/:id
|
|
100
98
|
|
|
101
99
|
export default async function (req, res) {
|
|
102
100
|
|
|
@@ -105,8 +103,11 @@ export default async function (req, res) {
|
|
|
105
103
|
return team;
|
|
106
104
|
}
|
|
107
105
|
```
|
|
108
|
-
|
|
106
|
+
<br>
|
|
107
|
+
|
|
108
|
+
And remember to use default export for your handler.
|
|
109
109
|
If you don't want to autoregister some routes, then simply don't add that magic comment ๐
|
|
110
|
+
That's it.
|
|
110
111
|
|
|
111
112
|
|
|
112
113
|
<br>
|
|
@@ -114,11 +115,11 @@ If you don't want to autoregister some routes, then simply don't add that magic
|
|
|
114
115
|
|
|
115
116
|
|
|
116
117
|
## ๐งพ Schemas
|
|
117
|
-
Create a sibling file ending with **`.schema.js`**, so it will be auto registered.
|
|
118
|
-
|
|
118
|
+
Create a sibling file ending with **`.schema.js`**, so it will be auto registered. For example: **hello.js** and **hello.schema.js**
|
|
119
|
+
|
|
120
|
+
Besides the basic validation fields we can set fields like summary and description, which will appear in the docs. Mostly you don't need to write schemas yourself, chat gpt and gemini do it excelently.
|
|
121
|
+
|
|
119
122
|
|
|
120
|
-
Besides the basic validation fields we can set fields like summary and description,
|
|
121
|
-
which will appear in the docs. Mostly you don't need to write schemas yourself, chat gpt and gemini do it excelently.
|
|
122
123
|
|
|
123
124
|
|
|
124
125
|
```js
|
|
@@ -142,7 +143,8 @@ export default {
|
|
|
142
143
|
};
|
|
143
144
|
```
|
|
144
145
|
|
|
145
|
-
|
|
146
|
+
**โผ๏ธ Important use export default
|
|
147
|
+
**
|
|
146
148
|
|
|
147
149
|
<br>
|
|
148
150
|
|
package/package.json
CHANGED
package/src/getAddress.js
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
import os from "node:os";
|
|
2
2
|
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* When listening on all interfaces (0.0.0.0), Node returns "0.0.0.0".
|
|
6
|
+
* This is a setting, not a valid URL for other devices.
|
|
7
|
+
* So we try hunt down the actual LAN IP to print the real address on which server can be accessed
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
|
|
3
11
|
function getLanIp() {
|
|
4
12
|
const nets = os.networkInterfaces();
|
|
5
13
|
|
package/src/index.js
CHANGED
|
@@ -6,13 +6,13 @@ import {
|
|
|
6
6
|
setupJwt,
|
|
7
7
|
setupMongo,
|
|
8
8
|
setupStatic,
|
|
9
|
-
setupRoutes,
|
|
10
9
|
setupDocs,
|
|
11
10
|
setupCookie,
|
|
12
|
-
getHttpsOptions,
|
|
13
11
|
} from "./setup.js";
|
|
14
12
|
|
|
13
|
+
|
|
15
14
|
import { getAddress } from "./getAddress.js";
|
|
15
|
+
import { setupRoutes } from "./setupRoutes.js";
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
export default async function maxserver(config = {}) {
|
|
@@ -26,6 +26,7 @@ export default async function maxserver(config = {}) {
|
|
|
26
26
|
docs = process.env.DOCS !== "false",
|
|
27
27
|
cors = process.env.CORS || "*",
|
|
28
28
|
env = process.env.NODE_ENV || "development",
|
|
29
|
+
routesDir = process.env.ROUTESDIR || "src",
|
|
29
30
|
openapiInfo,
|
|
30
31
|
static: isStatic = process.env.STATIC,
|
|
31
32
|
public: isPublic = process.env.PUBLIC === "true",
|
|
@@ -36,18 +37,19 @@ export default async function maxserver(config = {}) {
|
|
|
36
37
|
} = config;
|
|
37
38
|
|
|
38
39
|
const maxserverConfig = {
|
|
39
|
-
port, secret, mongodb, docs, cors,
|
|
40
|
+
port, secret, mongodb, docs, cors, env, openapiInfo, src,
|
|
40
41
|
static: isStatic,
|
|
41
42
|
public: isPublic
|
|
42
43
|
};
|
|
43
44
|
|
|
45
|
+
if (!secret)
|
|
46
|
+
throw new Error("secret is must have");
|
|
47
|
+
|
|
44
48
|
|
|
45
49
|
let app;
|
|
46
50
|
try {
|
|
47
51
|
app = Fastify({
|
|
48
|
-
https: getHttpsOptions() || undefined,
|
|
49
52
|
trustProxy: true,
|
|
50
|
-
|
|
51
53
|
// Required to allow adding doc fields on schema
|
|
52
54
|
ajv: { customOptions: { strictSchema: false } },
|
|
53
55
|
...fastifyOpts
|
package/src/setup.js
CHANGED
|
@@ -10,12 +10,6 @@ import helmet from "@fastify/helmet";
|
|
|
10
10
|
import swagger from "@fastify/swagger";
|
|
11
11
|
import apiReference from "@scalar/fastify-api-reference";
|
|
12
12
|
|
|
13
|
-
import { loadRoutes } from "./routeLoader.js";
|
|
14
|
-
|
|
15
|
-
export async function setupRoutes(app) {
|
|
16
|
-
await loadRoutes(app);
|
|
17
|
-
}
|
|
18
|
-
|
|
19
13
|
|
|
20
14
|
export async function setupHelmet(app) {
|
|
21
15
|
await app.register(helmet, {
|
|
@@ -27,50 +21,41 @@ export async function setupHelmet(app) {
|
|
|
27
21
|
}
|
|
28
22
|
|
|
29
23
|
|
|
30
|
-
|
|
31
|
-
|
|
32
24
|
export async function setupCors(app) {
|
|
33
|
-
const isProd =
|
|
25
|
+
const isProd = app.maxserver.env === "production";
|
|
34
26
|
const origin = app.maxserver.cors ?? "*";
|
|
35
|
-
|
|
36
27
|
if (isProd && origin === "*") {
|
|
37
28
|
app.log.warn("CORS: allowing all origins (*) in production");
|
|
38
29
|
}
|
|
39
|
-
|
|
40
30
|
await app.register(cors, { origin });
|
|
41
31
|
}
|
|
42
32
|
|
|
43
33
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
try {
|
|
50
|
-
return {
|
|
51
|
-
key: fs.readFileSync(TLS_KEY),
|
|
52
|
-
cert: fs.readFileSync(TLS_CERT),
|
|
53
|
-
};
|
|
54
|
-
} catch (err) {
|
|
55
|
-
throw new Error(`TLS read failed: ${err.message || err}`);
|
|
56
|
-
}
|
|
34
|
+
export async function setupCookie(app) {
|
|
35
|
+
await app.register(cookie, {
|
|
36
|
+
secret: app.maxserver.secret,
|
|
37
|
+
hook: "onRequest"
|
|
38
|
+
});
|
|
57
39
|
}
|
|
58
40
|
|
|
59
41
|
|
|
60
42
|
|
|
61
43
|
|
|
62
|
-
export async function
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
44
|
+
export async function setupMongo(app) {
|
|
45
|
+
const url = app.maxserver.mongodb;
|
|
46
|
+
if (!url) return;
|
|
47
|
+
await app.register(mongodb, { url });
|
|
48
|
+
|
|
49
|
+
const { ObjectId, db } = app.mongo;
|
|
50
|
+
global.oid = id => new ObjectId(id);
|
|
51
|
+
global.db = db;
|
|
67
52
|
}
|
|
68
53
|
|
|
69
54
|
|
|
70
|
-
export async function setupJwt(app) {
|
|
71
55
|
|
|
56
|
+
export async function setupJwt(app) {
|
|
72
57
|
await app.register(jwt, {
|
|
73
|
-
secret: app.maxserver.secret
|
|
58
|
+
secret: app.maxserver.secret,
|
|
74
59
|
cookie: { cookieName: "token" }
|
|
75
60
|
});
|
|
76
61
|
|
|
@@ -79,9 +64,7 @@ export async function setupJwt(app) {
|
|
|
79
64
|
// Let preflight requests pass
|
|
80
65
|
if (req.method === "OPTIONS") return;
|
|
81
66
|
|
|
82
|
-
const auth =
|
|
83
|
-
req.routeOptions?.config?.auth ??
|
|
84
|
-
req.routeOptions?.schema?.auth;
|
|
67
|
+
const auth = req.routeOptions?.config?.auth;
|
|
85
68
|
|
|
86
69
|
if (!auth) return;
|
|
87
70
|
|
|
@@ -93,15 +76,6 @@ export async function setupJwt(app) {
|
|
|
93
76
|
}
|
|
94
77
|
|
|
95
78
|
|
|
96
|
-
export async function setupMongo(app) {
|
|
97
|
-
const url = app.maxserver.mongodb;
|
|
98
|
-
if (!url) return;
|
|
99
|
-
await app.register(mongodb, { url });
|
|
100
|
-
|
|
101
|
-
const { ObjectId, db } = app.mongo;
|
|
102
|
-
global.oid = id => new ObjectId(id ? String(id) : undefined);
|
|
103
|
-
global.db = db;
|
|
104
|
-
}
|
|
105
79
|
|
|
106
80
|
|
|
107
81
|
export async function setupDocs(app) {
|
|
@@ -128,11 +102,11 @@ export async function setupDocs(app) {
|
|
|
128
102
|
|
|
129
103
|
app.get("/openapi.json", {}, () => app.swagger());
|
|
130
104
|
|
|
131
|
-
if (app.maxserver.docs
|
|
105
|
+
if (app.maxserver.docs !== false)
|
|
132
106
|
await app.register(apiReference, { routePrefix: "/docs", openapi: true });
|
|
133
107
|
|
|
134
108
|
app.addHook("onRoute", (route) => {
|
|
135
|
-
const auth = route.config?.auth
|
|
109
|
+
const auth = route.config?.auth;
|
|
136
110
|
if (!auth) return;
|
|
137
111
|
route.schema ||= {};
|
|
138
112
|
route.schema.security ||= [{ bearerAuth: [] }, { cookieAuth: [] }];
|
|
@@ -145,9 +119,19 @@ export async function setupStatic(app) {
|
|
|
145
119
|
const dir = app.maxserver.static;
|
|
146
120
|
if (!dir) return;
|
|
147
121
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
}
|
|
122
|
+
if (typeof dir !== "string") {
|
|
123
|
+
console.error("โ maxserver.static must be a string path");
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const abs = path.resolve(dir);
|
|
128
|
+
if (!fs.existsSync(abs)) {
|
|
129
|
+
console.error(`โ maxserver.static not found: ${abs}`);
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
await app.register(fastifyStatic, { root: abs });
|
|
152
134
|
}
|
|
153
135
|
|
|
136
|
+
|
|
137
|
+
|
|
@@ -74,9 +74,9 @@ async function importDefault(file) {
|
|
|
74
74
|
}
|
|
75
75
|
|
|
76
76
|
|
|
77
|
-
export async function
|
|
77
|
+
export async function setupRoutes(app) {
|
|
78
78
|
const seen = new Map();
|
|
79
|
-
const root = path.
|
|
79
|
+
const root = path.resolve(app.maxserver.routesDir || "src");
|
|
80
80
|
|
|
81
81
|
for (const file of walk(root)) {
|
|
82
82
|
// Skip schema files; they are loaded alongside their route file
|
|
@@ -125,6 +125,6 @@ export async function loadRoutes(fastify) {
|
|
|
125
125
|
};
|
|
126
126
|
}
|
|
127
127
|
|
|
128
|
-
|
|
128
|
+
app[info.method](info.url, { ...routeOptions, schema }, handler);
|
|
129
129
|
}
|
|
130
130
|
}
|