maxserver 0.0.13 → 0.0.15

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
@@ -20,7 +20,6 @@ Ready node server setup based on **Fastify** to speedup api development.
20
20
  - **Dev server**
21
21
  <br><br>
22
22
 
23
-
24
23
  - Dependencies: original fastify packages + scalar/fastify-api-reference (doc generator)
25
24
  - The source is simple and short. Everyone shall be able to read, understand and modify if needed.
26
25
 
@@ -36,23 +35,19 @@ npm install maxserver
36
35
  ## Setup
37
36
  ```js
38
37
  import maxserver from "maxserver";
38
+
39
39
  const server = await maxserver();
40
- const address = await server.listen({
41
- port: Number(process.env.PORT || 3000),
42
- });
40
+ await server.listen();
43
41
 
44
- console.log("Server running at", address);
42
+ console.log("Server running at ", server.getAddress());
45
43
  export default server;
46
- ```
47
-
48
- **maxserver(options)** forwards options to fastify(options).
49
- It returns the fully configured Fastify server instance.
50
-
51
44
 
45
+ ```
52
46
  ---
53
47
 
54
- ## ⚙️ Configuration
55
- Configure the server by setting variabels in your .env file.
48
+ ## ⚙️ 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.
56
51
 
57
52
  | Variable | Default | Description |
58
53
  | :--- | :--- | :--- |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "maxserver",
3
- "version": "0.0.13",
3
+ "version": "0.0.15",
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,37 @@
1
- import os from "os";
2
-
1
+ import os from "node:os";
3
2
 
4
3
  export function setupGetAddress(app) {
5
- app.decorate("getAddress", function () {
6
- const addr = this.server.address();
4
+ app.decorate("getAddress", () => {
5
+ const addr = app.server?.address();
6
+ const protocol = app.initialConfig?.https ? "https" : "http";
7
7
 
8
8
  if (!addr) return null;
9
9
  if (typeof addr === "string") return addr;
10
10
 
11
- const protocol = this.initialConfig.https ? "https" : "http";
12
- const host = getExternalIp() || "localhost";
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;
13
22
 
14
23
  return `${protocol}://${host}:${addr.port}`;
15
24
  });
16
25
  }
17
26
 
18
-
19
- function getExternalIp() {
27
+ function getLanIp() {
20
28
  const nets = os.networkInterfaces();
21
29
 
22
30
  for (const name of Object.keys(nets)) {
23
- for (const net of nets[name]) {
24
- if (net.family === "IPv4" && !net.internal) return net.address;
31
+ for (const net of (nets[name] || [])) {
32
+ if (net?.family === "IPv4" && !net.internal) return net.address;
25
33
  }
26
34
  }
27
35
 
28
36
  return null;
29
- }
37
+ }
package/src/index.js CHANGED
@@ -15,23 +15,50 @@ import {
15
15
  import { setupGetAddress } from "./getAddress.js";
16
16
 
17
17
 
18
+ export default async function maxserver(config = {}) {
19
+
20
+ const {
21
+
22
+ // maxserver options
23
+ secret = process.env.SECRET,
24
+ cookieSecret = process.env.COOKIE_SECRET,
25
+ mongodbUri = process.env.MONGODB_URI,
26
+ docs = process.env.DOCS !== "false",
27
+ staticDir = process.env.STATIC_DIR,
28
+ corsOrigin = process.env.CORS_ORIGIN || "*",
29
+
30
+ // everything else goes straight to Fastify
31
+ ...fastifyOpts
32
+
33
+ } = config;
34
+
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
42
+ };
43
+
44
+ process.env.NODE_ENV;
45
+
46
+
18
47
 
19
- export default async function maxserver(options = {}) {
20
48
 
21
49
  const app = Fastify({
22
- trustProxy: true,
50
+
23
51
  https: getHttpsOptions() || undefined,
52
+ trustProxy: true,
24
53
 
25
54
  // To allow writing example value fields to schemas for doucumentation
26
55
  ajv: { customOptions: { strictSchema: false } },
27
- ...options,
56
+ ...fastifyOpts,
28
57
  });
29
58
 
30
- global.createError = function (code, message) {
31
- const err = new Error(message);
32
- err.statusCode = code;
33
- return err;
34
- };
59
+ app.decorate("maxserver", maxserverConfig);
60
+
61
+
35
62
 
36
63
  setupGetAddress(app);
37
64
  await setupCookie(app);
@@ -44,5 +71,12 @@ export default async function maxserver(options = {}) {
44
71
  await setupRoutes(app);
45
72
 
46
73
 
74
+ global.createError = function (code, message) {
75
+ const err = new Error(message);
76
+ err.statusCode = code;
77
+ return err;
78
+ };
79
+
80
+
47
81
  return app;
48
82
  }
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
- export async function setupCookie(app) {
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,19 @@ export async function setupHelmet(app) {
35
26
  });
36
27
  }
37
28
 
29
+
38
30
  export async function setupCors(app) {
39
31
  const isProd = process.env.NODE_ENV === "production";
40
- const origin = process.env.CORS_ORIGIN || true;
32
+ const origin = app.maxserver.corsOrigin ?? true;
41
33
 
42
- if (isProd && !process.env.CORS_ORIGIN) app.log.warn("CORS_ORIGIN not set, allowing all origins");
34
+ if (isProd && origin === true) {
35
+ app.log.warn("CORS origin not set, allowing all origins");
36
+ }
43
37
 
44
38
  await app.register(cors, { origin });
45
39
  }
46
40
 
47
41
 
48
-
49
- export async function setupRoutes(app) {
50
- await loadRoutes(app);
51
- }
52
-
53
42
  export function getHttpsOptions() {
54
43
  const { TLS_KEY, TLS_CERT } = process.env;
55
44
  if (!TLS_KEY || !TLS_CERT) return null;
@@ -67,67 +56,63 @@ export function getHttpsOptions() {
67
56
 
68
57
 
69
58
 
70
-
71
- function isAuthSkippableUrl(url) {
72
- if (!url) return false;
73
- if (url.startsWith("/openapi.json")) return true;
74
- if (url.startsWith("/docs")) return true;
75
- if (url.startsWith("/static/")) return true;
76
- return false;
59
+ export async function setupCookie(app) {
60
+ await app.register(cookie, {
61
+ secret: app.maxserver.secret || "supersecret",
62
+ hook: "onRequest"
63
+ });
77
64
  }
78
65
 
66
+
79
67
  export async function setupJwt(app) {
80
- const secret = process.env.JWT_SECRET;
81
- if (!secret) return;
82
68
 
83
- // because we added own cookie setup function above
84
- //await app.register(cookie);
69
+ await app.register(jwt, {
70
+ secret: app.maxserver.secret || "supersecret",
71
+ cookie: { cookieName: "token" }
72
+ });
85
73
 
86
- await app.register(jwt, { secret, cookie: { cookieName: "token" } });
74
+ app.addHook("preHandler", async function (req) {
87
75
 
88
- app.addHook("onRequest", async function (req) {
76
+ // Let preflight requests pass
89
77
  if (req.method === "OPTIONS") return;
90
78
 
91
- const url = req.raw?.url || req.url;
92
- if (isAuthSkippableUrl(url)) return;
93
- if (req.routeOptions?.config?.public) return;
79
+ const auth =
80
+ req.routeOptions?.config?.auth ??
81
+ req.routeOptions?.schema?.auth;
94
82
 
95
- await req.jwtVerify();
83
+ if (!auth) return;
96
84
 
85
+ await req.jwtVerify();
97
86
  const u = req.user;
98
87
  req.userId = u?.sub || u?.userId || u?.userid || u?.id || null;
99
88
  });
89
+
100
90
  }
101
91
 
102
- export async function setupMongo(app) {
103
92
 
104
- const url = process.env.MONGODB_URI;
93
+ export async function setupMongo(app) {
94
+ const url = app.maxserver.mongodbUri;
105
95
  if (!url) return;
106
-
107
96
  await app.register(mongodb, { url });
108
97
 
109
- // ObjectId is available on app.mongo after registration
110
98
  const { ObjectId, db } = app.mongo;
111
-
112
99
  global.oid = id => new ObjectId(id ? String(id) : undefined);
113
100
  global.db = db;
114
101
  }
115
102
 
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
103
 
126
104
  export async function setupDocs(app) {
127
- const defaultSecurity = [{ bearerAuth: [] }, { cookieAuth: [] }];
105
+
106
+ const info = app.maxserver.openapiInfo || {
107
+ title: "API",
108
+ version: "1.0.0",
109
+ };
128
110
 
129
111
  await app.register(swagger, {
130
112
  openapi: {
113
+ info,
114
+ // OpenAPI 3.x: securitySchemes must be defined globally here not per route
115
+ // Routes only add `security: [...]` that references these scheme names
131
116
  components: {
132
117
  securitySchemes: {
133
118
  bearerAuth: { type: "http", scheme: "bearer" },
@@ -137,13 +122,29 @@ export async function setupDocs(app) {
137
122
  },
138
123
  });
139
124
 
140
- app.get("/openapi.json", { config: { public: true } }, () => app.swagger());
141
125
 
142
- await app.register(apiReference, { routePrefix: "/docs", openapi: true });
126
+ app.get("/openapi.json", {}, () => app.swagger());
127
+
128
+ if (app.maxserver.docs != false)
129
+ await app.register(apiReference, { routePrefix: "/docs", openapi: true });
143
130
 
144
131
  app.addHook("onRoute", (route) => {
145
- if (route.config?.public) return;
132
+ const auth = route.config?.auth ?? route.schema?.auth;
133
+ if (!auth) return;
146
134
  route.schema ||= {};
147
- route.schema.security = defaultSecurity;
135
+ route.schema.security ||= [{ bearerAuth: [] }, { cookieAuth: [] }];
148
136
  });
149
- }
137
+ }
138
+
139
+
140
+
141
+ export async function setupStatic(app) {
142
+ const dir = app.maxserver.staticDir;
143
+ if (!dir) return;
144
+
145
+ await app.register(fastifyStatic, {
146
+ root: path.resolve(dir),
147
+ prefix: "/static/",
148
+ });
149
+ }
150
+