skyguard-js 1.1.8 → 1.2.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.
Files changed (48) hide show
  1. package/README.md +395 -42
  2. package/dist/app.d.ts +21 -2
  3. package/dist/app.js +33 -4
  4. package/dist/crypto/hasher.js +2 -1
  5. package/dist/crypto/jwt.js +2 -1
  6. package/dist/helpers/http.d.ts +37 -0
  7. package/dist/helpers/http.js +37 -0
  8. package/dist/http/index.d.ts +1 -0
  9. package/dist/http/logger.d.ts +10 -1
  10. package/dist/http/logger.js +44 -8
  11. package/dist/http/nodeNativeHttp.d.ts +3 -1
  12. package/dist/http/nodeNativeHttp.js +4 -3
  13. package/dist/http/request.d.ts +10 -11
  14. package/dist/http/request.js +8 -11
  15. package/dist/http/response.d.ts +47 -3
  16. package/dist/http/response.js +77 -0
  17. package/dist/index.d.ts +4 -4
  18. package/dist/index.js +5 -2
  19. package/dist/middlewares/cors.js +2 -2
  20. package/dist/middlewares/csrf.d.ts +69 -0
  21. package/dist/middlewares/csrf.js +315 -0
  22. package/dist/middlewares/index.d.ts +2 -0
  23. package/dist/middlewares/index.js +5 -1
  24. package/dist/middlewares/rateLimiter.d.ts +82 -0
  25. package/dist/middlewares/rateLimiter.js +159 -0
  26. package/dist/routing/routerGroup.d.ts +2 -2
  27. package/dist/routing/routerGroup.js +2 -2
  28. package/dist/sessions/databaseSessionStorage.d.ts +52 -0
  29. package/dist/sessions/databaseSessionStorage.js +166 -0
  30. package/dist/sessions/fileSessionStorage.js +1 -1
  31. package/dist/sessions/index.d.ts +1 -0
  32. package/dist/sessions/index.js +3 -1
  33. package/dist/sessions/memorySessionStorage.js +1 -1
  34. package/dist/storage/uploader.d.ts +1 -1
  35. package/dist/storage/uploader.js +7 -6
  36. package/dist/types/index.d.ts +1 -10
  37. package/dist/validators/rules/bigIntRule.d.ts +0 -6
  38. package/dist/validators/rules/bigIntRule.js +0 -24
  39. package/dist/validators/rules/convertPrimitiveRule.d.ts +12 -0
  40. package/dist/validators/rules/convertPrimitiveRule.js +24 -0
  41. package/dist/validators/rules/dateRule.js +3 -13
  42. package/dist/validators/types.d.ts +47 -1
  43. package/dist/validators/validationRule.d.ts +2 -0
  44. package/dist/validators/validationRule.js +6 -1
  45. package/dist/validators/validationSchema.d.ts +83 -9
  46. package/dist/validators/validationSchema.js +151 -12
  47. package/dist/validators/validator.js +20 -4
  48. package/package.json +1 -1
package/README.md CHANGED
@@ -6,6 +6,7 @@
6
6
 
7
7
  [![NPM Version](https://img.shields.io/npm/v/skyguard-js)](https://www.npmjs.com/package/skyguard-js)
8
8
  [![Deployment Pipeline](https://github.com/Pipe930/Skyguard-js/actions/workflows/pipeline.yml/badge.svg)](https://github.com/Pipe930/Skyguard-js/actions/workflows/pipeline.yml)
9
+ [![Socket Badge](https://badge.socket.dev/npm/package/skyguard-js/1.1.8)](https://badge.socket.dev/npm/package/skyguard-js/1.1.8)
9
10
 
10
11
  **Skyguard.js** is a **lightweight, dependency-free web framework** built entirely with **TypeScript**.
11
12
 
@@ -32,10 +33,11 @@ Skyguard.js currently delivers a solid core that includes **routing**, **type-sa
32
33
  - Global, group, and route-level middlewares
33
34
  - Request / Response abstractions
34
35
  - Declarative data validation
35
- - Simple template engine with layouts and helpers
36
+ - Support for template motors (handlebars, pugs, ejs, etc.)
36
37
  - Built-in HTTP exceptions
37
38
  - Password hashing and JWT token generation
38
39
  - CORS middleware
40
+ - CSRF middleware protection
39
41
  - File uploads (via middleware)
40
42
  - Static file serving
41
43
  - Session handling (via middleware)
@@ -44,6 +46,24 @@ Skyguard.js currently delivers a solid core that includes **routing**, **type-sa
44
46
 
45
47
  ## 📦 Installation
46
48
 
49
+ You need to have [NodeJS](https://nodejs.org/) version 22 or later installed.
50
+
51
+ Create the `package.json` file to start a new [NodeJS](https://nodejs.org/) project using the `npm init` command.
52
+
53
+ After configuring the package.json, install [Typescript](https://www.typescriptlang.org/).
54
+
55
+ ```bash
56
+ npm install typescript -D
57
+ ```
58
+
59
+ After installing [Typescript](https://www.typescriptlang.org/) in your project, you need to create the [Typescript](https://www.typescriptlang.org/) configuration file `tsconfig.json`.
60
+
61
+ ```bash
62
+ npx tsc --init
63
+ ```
64
+
65
+ Now that [Typescript](https://www.typescriptlang.org/) is configured, we can install the library. Since it's a module that's in the [NPM Registry](https://www.npmjs.com/), we use the npm package manager.
66
+
47
67
  ```bash
48
68
  npm install skyguard-js
49
69
  ```
@@ -68,9 +88,6 @@ app.run(PORT, () => {
68
88
  });
69
89
  ```
70
90
 
71
- > [!NOTE]
72
- > It is recommended to develop with `TypeScript` for a more secure and efficient development process; the framework already has native support for `TypeScript` and includes the necessary types.
73
-
74
91
  ---
75
92
 
76
93
  ## 🛣️ Routing
@@ -97,8 +114,8 @@ Route groups allow you to organize endpoints under a shared prefix.
97
114
 
98
115
  ```ts
99
116
  app.group("/api", api => {
100
- api.get("/users", () => Response.json({ message: "Users" }));
101
- api.get("/products", () => Response.json({ message: "Products" }));
117
+ api.get("/users", () => res.json({ message: "Users" }));
118
+ api.get("/products", () => res.json({ message: "Products" }));
102
119
  });
103
120
  ```
104
121
 
@@ -109,30 +126,30 @@ app.group("/api", api => {
109
126
  Middlewares can be registered **globally**, **per group**, or **per route**.
110
127
 
111
128
  ```ts
112
- import { Request, Response, RouteHandler } from "skyguard-js";
129
+ import { Request, Response, json, RouteHandler } from "skyguard-js";
113
130
 
114
131
  const authMiddleware = async (
115
132
  request: Request,
116
133
  next: RouteHandler,
117
134
  ): Promise<Response> => {
118
135
  if (request.headers["authorization"] !== "secret") {
119
- return Response.json({ message: "Unauthorized" }).setStatus(401);
136
+ return json({ message: "Unauthorized" }).setStatus(401);
120
137
  }
121
138
 
122
139
  return next(request);
123
140
  };
124
141
 
125
142
  // Global middleware
126
- app.middlewares([authMiddleware]);
143
+ app.middlewares(authMiddleware);
127
144
 
128
145
  // Group middleware
129
146
  app.group("/admin", admin => {
130
- admin.middlewares([authMiddleware]);
131
- admin.get("/dashboard", () => Response.json({ ok: true }));
147
+ admin.middlewares(authMiddleware);
148
+ admin.get("/dashboard", () => json({ ok: true }));
132
149
  });
133
150
 
134
151
  // Route-level middleware
135
- app.get("/secure", () => Response.json({ secure: true }), [authMiddleware]);
152
+ app.get("/secure", () => json({ secure: true }), [authMiddleware]);
136
153
  ```
137
154
 
138
155
  ---
@@ -144,14 +161,135 @@ To enable CORS, use the built-in `cors` middleware.
144
161
  ```ts
145
162
  import { cors, HttpMethods } from "skyguard-js";
146
163
 
147
- app.middlewares([
164
+ app.middlewares(
148
165
  cors({
149
166
  origin: ["http://localhost:3000", "https://myapp.com"],
150
167
  methods: [HttpMethods.get, HttpMethods.post],
151
168
  allowedHeaders: ["Content-Type", "Authorization"],
152
169
  credentials: true,
153
170
  }),
154
- ]);
171
+ );
172
+ ```
173
+
174
+ ---
175
+
176
+ ## 🛡️ CSRF Middleware
177
+
178
+ Use the built-in `csrf` middleware to protect endpoints against CSRF attacks.
179
+
180
+ ```ts
181
+ import { csrf, json } from "skyguard-js";
182
+
183
+ app.middlewares(
184
+ csrf({
185
+ cookieName: "XSRF-TOKEN",
186
+ headerNames: ["x-csrf-token"],
187
+ }),
188
+ );
189
+
190
+ app.post("/transfer", () => {
191
+ return json({ ok: true });
192
+ });
193
+ ```
194
+
195
+ The middleware follows a hardened **double-submit cookie** strategy:
196
+
197
+ - It issues a CSRF cookie when missing (including first GET/HEAD/OPTIONS and failed protected requests).
198
+ - For state-changing requests (POST/PUT/PATCH/DELETE), it validates the token from header/body against the cookie value.
199
+ - It validates `Origin`/`Referer` for protected requests (and requires `Referer` on HTTPS when `Origin` is missing).
200
+ - It rejects duplicated CSRF header values to avoid ambiguous token parsing.
201
+
202
+ ### Example: CSRF token in HTML templates (Express Handlebars)
203
+
204
+ When you render server-side HTML, you can pass the CSRF token to your template and include it as a hidden field in forms.
205
+
206
+ ```ts
207
+ import { createApp, csrf, render, json } from "skyguard-js";
208
+ import { engine } from "express-handlebars";
209
+ import { join } from "node:path";
210
+
211
+ const app = createApp();
212
+
213
+ app.views(__dirname, "views");
214
+ app.engineTemplates(
215
+ "hbs",
216
+ engine({
217
+ extname: "hbs",
218
+ layoutsDir: join(__dirname, "views"),
219
+ defaultLayout: "main",
220
+ }),
221
+ );
222
+
223
+ app.middlewares(
224
+ csrf({
225
+ cookieName: "XSRF-TOKEN",
226
+ headerNames: ["x-csrf-token"],
227
+ }),
228
+ );
229
+
230
+ app.get("/transfer", request => {
231
+ return render("transfer", {
232
+ csrfToken: request.cookies["XSRF-TOKEN"],
233
+ });
234
+ });
235
+
236
+ app.post("/transfer", request => {
237
+ // If middleware passes, token is valid
238
+ return json({ ok: true, amount: request.body.amount });
239
+ });
240
+ ```
241
+
242
+ `views/transfer.hbs`:
243
+
244
+ ```hbs
245
+ <form action="/transfer" method="POST">
246
+ <input type="hidden" name="csrf" value="{{csrfToken}}" />
247
+ <input type="number" name="amount" />
248
+ <button type="submit">Send</button>
249
+ </form>
250
+ ```
251
+
252
+ For `fetch`/AJAX requests, send the same token in headers:
253
+
254
+ ```html
255
+ <script>
256
+ const csrfToken = "{{csrfToken}}";
257
+
258
+ async function sendTransfer() {
259
+ await fetch("/transfer", {
260
+ method: "POST",
261
+ headers: {
262
+ "Content-Type": "application/json",
263
+ "x-csrf-token": csrfToken,
264
+ },
265
+ body: JSON.stringify({ amount: 150 }),
266
+ });
267
+ }
268
+ </script>
269
+ ```
270
+
271
+ ---
272
+
273
+ ## 🚦 Rate Limit Middleware
274
+
275
+ You can limit requests with the built-in `rateLimit` middleware.
276
+
277
+ ```ts
278
+ import { rateLimit, Response } from "skyguard-js";
279
+
280
+ const apiRateLimit = rateLimit({
281
+ windowMs: 60_000, // 1 minute
282
+ max: 100,
283
+ message: "Too many requests from this IP",
284
+ });
285
+
286
+ app.get(
287
+ "/api/users",
288
+ () => {
289
+ return Response.json([{ id: 1 }]);
290
+ },
291
+ [apiRateLimit],
292
+ );
155
293
  ```
156
294
 
157
295
  ---
@@ -175,33 +313,26 @@ app.staticFiles(join(__dirname, "..", "static"));
175
313
  To validate the data in the body of client requests, the framework provides the creation of validation schemes and a middleware function to validate the body of HTTP requests, used as follows:
176
314
 
177
315
  ```ts
178
- import { v, schema, validateData } from "skyguard-js";
316
+ import { v, schema, validateRequest, json } from "skyguard-js";
179
317
 
180
318
  // Created Schema
181
319
  const userSchema = schema({
182
- name: v.string({ maxLength: 60 }),
183
- email: v.email(),
184
- age: v.number({ min: 18 }),
185
- active: v.boolean().default(false),
186
- birthdate: v.date({ max: new Date() }),
320
+ body: {
321
+ name: v.string({ maxLength: 60 }),
322
+ email: v.email(),
323
+ age: v.number({ min: 18 }),
324
+ active: v.boolean().default(false),
325
+ birthdate: v.date({ max: new Date() }),
326
+ },
187
327
  });
188
328
 
189
- // Typing Interface
190
- interface User {
191
- name: string;
192
- email: string;
193
- age: number;
194
- active: boolean;
195
- birthdate: Date;
196
- }
197
-
198
329
  app.post(
199
330
  "/test",
200
331
  (request: Request) => {
201
- const data = request.getData<User>(); // The .getData() method returns the typed data with the interface you created
332
+ const data = request.body;
202
333
  return json(data).setStatusCode(201);
203
334
  },
204
- [validateData(userSchema)],
335
+ [validateRequest(userSchema)],
205
336
  );
206
337
  ```
207
338
 
@@ -221,7 +352,7 @@ Validation is:
221
352
  The framework provides a set of built-in HTTP exceptions that can be thrown from route handlers or middleware. When an exception is thrown, the framework detects it and sends an appropriate HTTP response with the status code and message you specified in the class.
222
353
 
223
354
  ```ts
224
- import { NotFoundError, InternalServerError } from "skyguard-js";
355
+ import { NotFoundError, InternalServerError, json } from "skyguard-js";
225
356
 
226
357
  const listResources = ["1", "2", "3"];
227
358
 
@@ -232,7 +363,7 @@ app.get("/resource/{id}", (request: Request) => {
232
363
  throw new NotFoundError("Resource not found");
233
364
  }
234
365
 
235
- return Response.json(resource);
366
+ return json(resource);
236
367
  });
237
368
 
238
369
  app.get("/divide", (request: Request) => {
@@ -240,7 +371,7 @@ app.get("/divide", (request: Request) => {
240
371
  const { a, b } = request.query;
241
372
  const result = Number(a) / Number(b);
242
373
 
243
- return Response.json({ result });
374
+ return json({ result });
244
375
  } catch (error) {
245
376
  throw new InternalServerError(
246
377
  "An error occurred while processing your request",
@@ -256,9 +387,9 @@ app.get("/divide", (request: Request) => {
256
387
  To handle sessions, you must use the framework’s built-in middleware. Depending on where you want to store them (in memory, in files, or in a database), you need to use the corresponding storage class.
257
388
 
258
389
  ```ts
259
- import { sessions, FileSessionStorage } from "skyguard-js";
390
+ import { sessions, FileSessionStorage, json } from "skyguard-js";
260
391
 
261
- app.middlewares([
392
+ app.middlewares(
262
393
  sessions(FileSessionStorage, {
263
394
  name: "connect.sid",
264
395
  rolling: true,
@@ -271,7 +402,7 @@ app.middlewares([
271
402
  path: "/",
272
403
  },
273
404
  }),
274
- ]);
405
+ );
275
406
 
276
407
  app.post("/login", (request: Request) => {
277
408
  const { username, password } = request.data;
@@ -297,6 +428,227 @@ app.get("/me", (request: Request) => {
297
428
  });
298
429
  ```
299
430
 
431
+ For **database-backed sessions**, configure `DatabaseSessionStorage` once with an adapter that maps to your DB client/ORM. This keeps the framework **DB-engine agnostic** (MySQL, MariaDB, SQLite, PostgreSQL, SQL Server, Oracle, etc.).
432
+
433
+ ```ts
434
+ import {
435
+ sessions,
436
+ DatabaseSessionStorage,
437
+ type SessionDatabaseAdapter,
438
+ } from "skyguard-js";
439
+
440
+ const sessionAdapter: SessionDatabaseAdapter = {
441
+ async findById(id) {
442
+ // query row by id and return: { data: parsedJson, expiresAt: unixMs }
443
+ return null;
444
+ },
445
+ async upsert(id, payload) {
446
+ // insert/update row depending on your DB driver
447
+ },
448
+ async deleteById(id) {
449
+ // delete row by id
450
+ },
451
+ async deleteExpired(now) {
452
+ // delete rows where expiresAt <= now
453
+ },
454
+ };
455
+
456
+ DatabaseSessionStorage.configure(sessionAdapter);
457
+
458
+ app.middlewares(sessions(DatabaseSessionStorage));
459
+ ```
460
+
461
+ ### Concrete DB adapter examples
462
+
463
+ > Suggested table shape (portable across engines):
464
+ >
465
+ > - `id` (string/varchar, primary key)
466
+ > - `data` (JSON/TEXT containing serialized object)
467
+ > - `expires_at` (bigint/timestamp in unix milliseconds)
468
+
469
+ To keep the code cleaner, you should create a separate file where you can configure the database sessions, such as `src/sessions/config.ts`
470
+
471
+ #### Prisma (MySQL / PostgreSQL / SQLite / SQL Server / CockroachDB)
472
+
473
+ ```ts
474
+ import { PrismaClient } from "@prisma/client";
475
+ import {
476
+ DatabaseSessionStorage,
477
+ type SessionDatabaseAdapter,
478
+ } from "skyguard-js";
479
+
480
+ const prisma = new PrismaClient();
481
+
482
+ // model Session {
483
+ // id String @id
484
+ // data String
485
+ // expiresAt BigInt @map("expires_at")
486
+ // @@map("sessions")
487
+ // }
488
+
489
+ const adapter: SessionDatabaseAdapter = {
490
+ async findById(id) {
491
+ const row = await prisma.session.findUnique({ where: { id } });
492
+ if (!row) return null;
493
+ return { data: JSON.parse(row.data), expiresAt: Number(row.expiresAt) };
494
+ },
495
+ async upsert(id, payload) {
496
+ await prisma.session.upsert({
497
+ where: { id },
498
+ update: {
499
+ data: JSON.stringify(payload.data),
500
+ expiresAt: BigInt(payload.expiresAt),
501
+ },
502
+ create: {
503
+ id,
504
+ data: JSON.stringify(payload.data),
505
+ expiresAt: BigInt(payload.expiresAt),
506
+ },
507
+ });
508
+ },
509
+ async deleteById(id) {
510
+ await prisma.session.deleteMany({ where: { id } });
511
+ },
512
+ async deleteExpired(now) {
513
+ await prisma.session.deleteMany({
514
+ where: { expiresAt: { lte: BigInt(now) } },
515
+ });
516
+ },
517
+ };
518
+
519
+ DatabaseSessionStorage.configure(adapter);
520
+ ```
521
+
522
+ #### TypeORM (MySQL / MariaDB / PostgreSQL / SQLite / MSSQL / Oracle)
523
+
524
+ ```ts
525
+ import {
526
+ DataSource,
527
+ Entity,
528
+ Column,
529
+ PrimaryColumn,
530
+ LessThanOrEqual,
531
+ } from "typeorm";
532
+ import {
533
+ DatabaseSessionStorage,
534
+ type SessionDatabaseAdapter,
535
+ } from "skyguard-js";
536
+
537
+ @Entity({ name: "sessions" })
538
+ class SessionEntity {
539
+ @PrimaryColumn({ type: "varchar", length: 64 })
540
+ id!: string;
541
+
542
+ @Column({ type: "text" })
543
+ data!: string;
544
+
545
+ @Column({ name: "expires_at", type: "bigint" })
546
+ expiresAt!: string;
547
+ }
548
+
549
+ const ds = new DataSource({ /* your db config */ entities: [SessionEntity] });
550
+ await ds.initialize();
551
+ const repo = ds.getRepository(SessionEntity);
552
+
553
+ const adapter: SessionDatabaseAdapter = {
554
+ async findById(id) {
555
+ const row = await repo.findOneBy({ id });
556
+ if (!row) return null;
557
+ return { data: JSON.parse(row.data), expiresAt: Number(row.expiresAt) };
558
+ },
559
+ async upsert(id, payload) {
560
+ await repo.save({
561
+ id,
562
+ data: JSON.stringify(payload.data),
563
+ expiresAt: String(payload.expiresAt),
564
+ });
565
+ },
566
+ async deleteById(id) {
567
+ await repo.delete({ id });
568
+ },
569
+ async deleteExpired(now) {
570
+ await repo.delete({ expiresAt: LessThanOrEqual(String(now)) });
571
+ },
572
+ };
573
+
574
+ DatabaseSessionStorage.configure(adapter);
575
+ ```
576
+
577
+ #### mysql2 (MySQL)
578
+
579
+ ```ts
580
+ import mysql from "mysql2/promise";
581
+ import {
582
+ DatabaseSessionStorage,
583
+ type SessionDatabaseAdapter,
584
+ } from "skyguard-js";
585
+
586
+ const pool = mysql.createPool({ uri: process.env.DATABASE_URL });
587
+
588
+ const adapter: SessionDatabaseAdapter = {
589
+ async findById(id) {
590
+ const [rows] = await pool.query<any[]>(
591
+ "SELECT data, expires_at FROM sessions WHERE id = ? LIMIT 1",
592
+ [id],
593
+ );
594
+ const row = rows[0];
595
+ if (!row) return null;
596
+ return { data: JSON.parse(row.data), expiresAt: Number(row.expires_at) };
597
+ },
598
+ async upsert(id, payload) {
599
+ await pool.query(
600
+ `INSERT INTO sessions (id, data, expires_at) VALUES (?, ?, ?)
601
+ ON DUPLICATE KEY UPDATE data = VALUES(data), expires_at = VALUES(expires_at)`,
602
+ [id, JSON.stringify(payload.data), payload.expiresAt],
603
+ );
604
+ },
605
+ async deleteById(id) {
606
+ await pool.query("DELETE FROM sessions WHERE id = ?", [id]);
607
+ },
608
+ async deleteExpired(now) {
609
+ await pool.query("DELETE FROM sessions WHERE expires_at <= ?", [now]);
610
+ },
611
+ };
612
+
613
+ DatabaseSessionStorage.configure(adapter);
614
+ ```
615
+
616
+ #### sqlite3 (SQLite)
617
+
618
+ ```ts
619
+ import sqlite3 from "sqlite3";
620
+ import { open } from "sqlite";
621
+ import { type SessionDatabaseAdapter } from "skyguard-js";
622
+
623
+ const db = await open({ filename: "./sessions.db", driver: sqlite3.Database });
624
+
625
+ const adapter: SessionDatabaseAdapter = {
626
+ async findById(id) {
627
+ const row = await db.get<{ data: string; expires_at: number }>(
628
+ "SELECT data, expires_at FROM sessions WHERE id = ? LIMIT 1",
629
+ [id],
630
+ );
631
+ if (!row) return null;
632
+ return { data: JSON.parse(row.data), expiresAt: Number(row.expires_at) };
633
+ },
634
+ async upsert(id, payload) {
635
+ await db.run(
636
+ `INSERT INTO sessions (id, data, expires_at) VALUES (?, ?, ?)
637
+ ON CONFLICT(id) DO UPDATE SET data = excluded.data, expires_at = excluded.expires_at`,
638
+ [id, JSON.stringify(payload.data), payload.expiresAt],
639
+ );
640
+ },
641
+ async deleteById(id) {
642
+ await db.run("DELETE FROM sessions WHERE id = ?", [id]);
643
+ },
644
+ async deleteExpired(now) {
645
+ await db.run("DELETE FROM sessions WHERE expires_at <= ?", [now]);
646
+ },
647
+ };
648
+
649
+ DatabaseSessionStorage.configure(adapter);
650
+ ```
651
+
300
652
  ---
301
653
 
302
654
  ## 🛡️ Security
@@ -304,7 +656,7 @@ app.get("/me", (request: Request) => {
304
656
  The framework includes some password hashing and JWT token generation functions, and also includes JWT authentication middleware.
305
657
 
306
658
  ```ts
307
- import { Hasher, JWT } from "skyguard-js";
659
+ import { Hasher, JWT, json } from "skyguard-js";
308
660
 
309
661
  app.post("/register", async (request: Request) => {
310
662
  const { username, password } = request.data;
@@ -313,7 +665,7 @@ app.post("/register", async (request: Request) => {
313
665
  // Save username and hashedPassword to database
314
666
  // ...
315
667
 
316
- return Response.json({ message: "User registered" });
668
+ return json({ message: "User registered" });
317
669
  });
318
670
 
319
671
  app.post("/login", async (request: Request) => {
@@ -333,7 +685,7 @@ app.post("/login", async (request: Request) => {
333
685
  expiresIn: "1h",
334
686
  });
335
687
 
336
- return Response.json({ token });
688
+ return json({ token });
337
689
  });
338
690
  ```
339
691
 
@@ -344,7 +696,7 @@ app.post("/login", async (request: Request) => {
344
696
  To handle file uploads, use the built-in `createUploader` function to create an uploader middleware with the desired storage configuration.
345
697
 
346
698
  ```ts
347
- import { createUploader, StorageType } from "skyguard-js";
699
+ import { createUploader, StorageType, json } from "skyguard-js";
348
700
 
349
701
  const uploader = createUploader({
350
702
  storageType: StorageType.DISK,
@@ -358,7 +710,7 @@ const uploader = createUploader({
358
710
  app.post(
359
711
  "/upload",
360
712
  (request: Request) => {
361
- return Response.json({
713
+ return json({
362
714
  message: "File uploaded successfully",
363
715
  file: request.file,
364
716
  });
@@ -379,6 +731,7 @@ To render views, you must first set up the template engine using the `engineTemp
379
731
  import { engine } from "express-handlebars";
380
732
  import ejs from "ejs";
381
733
  import { join } from "node:path";
734
+ import { render } from "skyguard-js";
382
735
 
383
736
  app.views(__dirname, "views");
384
737
 
package/dist/app.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { RouterGroup } from "./routing";
2
+ import { type LogFormat } from "./http";
2
3
  import type { Middleware, RouteHandler } from "./types";
3
4
  import { type TemplateEngineFunction } from "./views/engineTemplate";
4
5
  /**
@@ -19,13 +20,15 @@ import { type TemplateEngineFunction } from "./views/engineTemplate";
19
20
  * from the runtime platform (Node, Bun, Deno, etc.)
20
21
  * through {@link HttpAdapter} and {@link Server}.
21
22
  */
22
- export declare class App {
23
+ declare class App {
23
24
  /** Main routing system */
24
25
  private router;
25
26
  /** Static file handler (optional) */
26
27
  private staticFileHandler;
27
28
  /** View engine for rendering templates (optional) */
28
29
  private viewEngine;
30
+ /** Logger configuration */
31
+ private loggerOptions;
29
32
  /**
30
33
  * Bootstraps and configures the application.
31
34
  *
@@ -107,6 +110,21 @@ export declare class App {
107
110
  * @param port - TCP port to listen on
108
111
  */
109
112
  run(port: number, callback: VoidFunction, hostname?: string): void;
113
+ /**
114
+ * Configures HTTP request logger output format and optional file output.
115
+ *
116
+ * Supported formats are inspired by morgan:
117
+ * - "combined"
118
+ * - "common"
119
+ * - "dev"
120
+ * - "short"
121
+ * - "tiny"
122
+ *
123
+ * @example
124
+ * app.logger("common");
125
+ * app.logger("combined", "./logs/http.log");
126
+ */
127
+ logger(format?: LogFormat, filePath?: string): void;
110
128
  /**
111
129
  * Sets a global prefix for all routes.
112
130
  *
@@ -140,7 +158,7 @@ export declare class App {
140
158
  *
141
159
  * app.middlewares(auth);
142
160
  */
143
- middlewares(middlewares: Middleware[]): void;
161
+ middlewares(...middlewares: Middleware[]): void;
144
162
  /**
145
163
  * Creates a route group with a shared prefix.
146
164
  *
@@ -159,3 +177,4 @@ export declare class App {
159
177
  private handleError;
160
178
  }
161
179
  export declare const createApp: () => App;
180
+ export {};