@mkgabri18/mini-serve 0.1.0

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,164 @@
1
+ # mini-serve
2
+
3
+ `mini-serve` è un micro-framework HTTP ultra-leggero e **zero-dependency** per Node.js. È nato come alternativa minimale e nativa a Express per creare API REST, CRUD e server web veloci, mantenendo il pieno controllo sulle performance e sulla configurazione dei middleware.
4
+
5
+ ---
6
+
7
+ ## Indice
8
+ 1. [Caratteristiche principali](#caratteristiche-principali)
9
+ 2. [Guida: Come usarlo in un nuovo progetto da zero](#guida-come-usarlo-in-un-nuovo-progetto-da-zero)
10
+ 3. [Guida all'API](#guida-allapi)
11
+ 4. [Gestione dei Middleware](#gestione-dei-middleware)
12
+ 5. [Licenza](#licenza)
13
+
14
+ ---
15
+
16
+ ## Caratteristiche principali
17
+
18
+ * 🚀 **Zero dipendenze esterne**: Utilizza esclusivamente i moduli nativi di Node.js (es. `node:http`, `node:fs`).
19
+ * 🛣️ **Router Express-like**: Supporto nativo per rotte HTTP (`GET`, `POST`, `PUT`, `DELETE`, `PATCH`) e parametri dinamici nell'URL (es. `/api/users/:id`).
20
+ * ⚙️ **Modularità Opt-In**: I middleware built-in (body parser, request/response enhancers, logging) sono disattivati o attivabili a piacimento.
21
+ * 🛡️ **Leggero e Sicuro**: Include un body parser con limite integrato di 1MB sui payload per prevenire attacchi di tipo DOS.
22
+
23
+ ---
24
+
25
+ ## Guida: Come usarlo in un nuovo progetto da zero
26
+
27
+ Ecco i passaggi da seguire per creare un nuovo progetto ed utilizzare `mini-serve`.
28
+
29
+ ### Step 1: Inizializza il progetto Node.js
30
+ Crea una nuova cartella per il tuo progetto e inizializzala tramite il terminale:
31
+ ```bash
32
+ mkdir mio-nuovo-progetto
33
+ cd mio-nuovo-progetto
34
+ npm init -y
35
+ ```
36
+
37
+ ### Step 2: Abilita i moduli ES (ESM)
38
+ Apri il file `package.json` appena generato e aggiungi la riga `"type": "module"`. Questo passaggio è fondamentale in quanto `mini-serve` utilizza la sintassi moderna degli import di ES6:
39
+ ```json
40
+ {
41
+ "name": "mio-nuovo-progetto",
42
+ "version": "1.0.0",
43
+ "type": "module",
44
+ "scripts": {
45
+ "start": "node index.js"
46
+ }
47
+ }
48
+ ```
49
+
50
+ ### Step 3: Installa `mini-serve`
51
+ Installa il pacchetto tramite npm (puoi installarlo localmente o puntare al percorso se lo stai testando localmente):
52
+ ```bash
53
+ npm install mini-serve
54
+ ```
55
+
56
+ ### Step 4: Crea il file del Server (`index.js`)
57
+ Crea un file chiamato `index.js` nella root del tuo progetto e scrivi il seguente codice di esempio:
58
+
59
+ ```javascript
60
+ import http from 'node:http';
61
+ import { createServer } from 'mini-serve';
62
+ import { notFoundHandler, globalErrorHandler } from 'mini-serve/middlewares';
63
+
64
+ // 1. Inizializza il server con le opzioni desiderate
65
+ const app = createServer({
66
+ useEnhancers: true, // Abilita res.json(), res.status() e req.query
67
+ useBodyParser: true, // Parsa automaticamente il JSON per POST/PUT/PATCH
68
+ useLogger: true // Mostra i log delle richieste in console
69
+ });
70
+
71
+ // 2. Definisci le tue rotte
72
+ app.get('/', (req, res) => {
73
+ res.status(200).json({ message: 'Benvenuto nel mio nuovo server agnostico!' });
74
+ });
75
+
76
+ // Rotta con parametri dinamici
77
+ app.get('/items/:id', (req, res) => {
78
+ const { id } = req.params;
79
+ res.status(200).json({ itemId: id, queryString: req.query });
80
+ });
81
+
82
+ // Rotta POST con gestione del body parificato
83
+ app.post('/items', (req, res) => {
84
+ const payload = req.body;
85
+ res.status(201).json({ created: payload });
86
+ });
87
+
88
+ // 3. Registra i middleware di fallback per errori
89
+ app.use(notFoundHandler);
90
+ app.use(globalErrorHandler);
91
+
92
+ // 4. Avvia il server tramite il modulo nativo http di Node.js
93
+ const server = http.createServer(app.handler);
94
+
95
+ server.listen(3000, () => {
96
+ console.log('Server avviato con successo su http://localhost:3000');
97
+ });
98
+ ```
99
+
100
+ ### Step 5: Avvia il server
101
+ Esegui il server tramite il terminale:
102
+ ```bash
103
+ npm start
104
+ ```
105
+
106
+ ---
107
+
108
+ ## Guida all'API
109
+
110
+ ### `createServer(options)`
111
+ Crea un'istanza dell'applicazione. Riceve un oggetto di configurazione per i middleware built-in:
112
+
113
+ | Opzione | Tipo | Default | Descrizione |
114
+ |---|---|---|---|
115
+ | `useEnhancers` | `boolean` | `true` | Aggiunge `req.query`, `req.path`, `res.status(code)` e `res.json(data)`. |
116
+ | `useBodyParser` | `boolean` | `true` | Parsa automaticamente i payload JSON in `req.body` (limite max: 1MB). Ritorna `413 Payload Too Large` se superato o `400 Bad Request` in caso di JSON malformato. |
117
+ | `useLogger` | `boolean` | `false` | Stampa a console le richieste ricevute, lo status code e il tempo di elaborazione in ms (es: `[GET] /api/users - 200 (12ms)`). |
118
+
119
+ L'oggetto `app` restituito espone i seguenti metodi:
120
+ - `app.use(middleware)`: Registra un middleware globale o un gestore errori.
121
+ - `app.get(path, ...handlers)`: Registra una rotta GET.
122
+ - `app.post(path, ...handlers)`: Registra una rotta POST.
123
+ - `app.put(path, ...handlers)`: Registra una rotta PUT.
124
+ - `app.delete(path, ...handlers)`: Registra una rotta DELETE.
125
+ - `app.patch(path, ...handlers)`: Registra una rotta PATCH.
126
+ - `app.handler`: Il delegato nativo `(req, res)` da passare a `http.createServer()`.
127
+
128
+ ---
129
+
130
+ ## Gestione dei Middleware
131
+
132
+ I middleware seguono il classico pattern `(req, res, next)`:
133
+
134
+ ```javascript
135
+ app.use((req, res, next) => {
136
+ console.log('Richiesta in transito...');
137
+ next(); // Passa al middleware successivo
138
+ });
139
+ ```
140
+
141
+ ### Gestione degli Errori globali
142
+ Se passi un errore a `next(err)`, lo stack salterà tutti i middleware standard fino a raggiungere un middleware di errore, identificato dall'avere 4 parametri `(err, req, res, next)`:
143
+
144
+ ```javascript
145
+ app.use((err, req, res, next) => {
146
+ console.error(err.stack);
147
+ res.writeHead(500, { 'Content-Type': 'application/json' });
148
+ res.end(JSON.stringify({ error: 'Errore generico del server' }));
149
+ });
150
+ ```
151
+
152
+ Puoi anche usare gli handler predefiniti importandoli da sottomodulo:
153
+ ```javascript
154
+ import { notFoundHandler, globalErrorHandler } from 'mini-serve/middlewares';
155
+
156
+ app.use(notFoundHandler); // Gestisce i 404 per rotte non registrate
157
+ app.use(globalErrorHandler); // Cattura ed elabora in sicurezza gli errori generici
158
+ ```
159
+
160
+ ---
161
+
162
+ ## Licenza
163
+
164
+ Questo progetto è rilasciato sotto licenza [MIT](LICENSE).
package/lib/index.js ADDED
@@ -0,0 +1,3 @@
1
+ export { createServer } from './server.js';
2
+ export { route } from './router.js';
3
+ export * from './middlewares/index.js';
@@ -0,0 +1,41 @@
1
+ export function runMiddlewares(middlewares, req, res) {
2
+ function dispatch(i, err) {
3
+ if (i >= middlewares.length) return;
4
+
5
+ const middleware = middlewares[i];
6
+ let called = false;
7
+
8
+ function next(nextErr) {
9
+ if (called) {
10
+ console.error("next() called multiple times");
11
+ return;
12
+ }
13
+ called = true;
14
+ dispatch(i + 1, nextErr);
15
+ }
16
+
17
+ try {
18
+ if (err) {
19
+ // Stiamo gestendo un errore: invochiamo solo gli errori middleware (4 parametri)
20
+ if (middleware.length === 4) {
21
+ Promise.resolve(middleware(err, req, res, next)).catch(next);
22
+ } else {
23
+ // Salta il middleware normale e vai avanti trascinando l'errore
24
+ dispatch(i + 1, err);
25
+ }
26
+ } else {
27
+ // Funzionamento normale: invochiamo solo i middleware regolari (< 4 parametri)
28
+ if (middleware.length < 4) {
29
+ Promise.resolve(middleware(req, res, next)).catch(next);
30
+ } else {
31
+ // Salta l'error middleware
32
+ dispatch(i + 1, err);
33
+ }
34
+ }
35
+ } catch (caughtErr) {
36
+ next(caughtErr);
37
+ }
38
+ }
39
+
40
+ dispatch(0);
41
+ }
@@ -0,0 +1,52 @@
1
+ export function jsonBodyParser(req, res, next) {
2
+ // Parsiamo il body solo per le richieste che solitamente lo prevedono
3
+ if (["POST", "PUT", "PATCH"].includes(req.method)) {
4
+ let body = "";
5
+ const MAX_SIZE = 1 * 1024 * 1024; // 1 Megabyte
6
+ let limitExceeded = false;
7
+
8
+ req.on("data", chunk => {
9
+ if (limitExceeded) return;
10
+
11
+ body += chunk;
12
+ if (body.length > MAX_SIZE) {
13
+ limitExceeded = true;
14
+ req.pause();
15
+
16
+ // Send 413 Payload Too Large response gracefully
17
+ res.status(413).json({ error: "Payload Too Large" });
18
+
19
+ // Destroy the socket on the next tick so Node has time to flush the response
20
+ setImmediate(() => {
21
+ req.destroy();
22
+ });
23
+ }
24
+ });
25
+
26
+ req.on("end", () => {
27
+ if (limitExceeded || req.destroyed) return;
28
+
29
+ if (!body) {
30
+ req.body = {};
31
+ return next();
32
+ }
33
+
34
+ try {
35
+ req.body = JSON.parse(body);
36
+ next();
37
+ } catch (err) {
38
+ const error = new Error("Invalid JSON Payload");
39
+ error.status = 400; // Bad Request
40
+ next(error);
41
+ }
42
+ });
43
+
44
+ req.on("error", (err) => {
45
+ if (limitExceeded) return;
46
+ next(err);
47
+ });
48
+ } else {
49
+ // Per le richieste come GET o DELETE, proseguiamo oltre ignorando il body
50
+ next();
51
+ }
52
+ }
@@ -0,0 +1,28 @@
1
+ export function requestResponseEnhancer(req, res, next) {
2
+ // --- ENHANCE REQUEST ---
3
+ // Analizza l'URL includendo anche le query string (es. ?sort=asc)
4
+ const parsedUrl = new URL(req.url, `http://${req.headers.host || 'localhost'}`);
5
+
6
+ // Salva il path "pulito" da eventuali query string (es. /notes invece di /notes?sort=asc)
7
+ req.path = parsedUrl.pathname;
8
+
9
+ // Salva i parametri estratti (es. { sort: 'asc' })
10
+ req.query = Object.fromEntries(parsedUrl.searchParams);
11
+
12
+ // --- ENHANCE RESPONSE ---
13
+ // Aggiunge la scorciatoia per impostare lo status code a catena
14
+ res.status = function(code) {
15
+ res.statusCode = code;
16
+ return res;
17
+ };
18
+
19
+ // Aggiunge la scorciatoia per inviare JSON con i corretti Headers
20
+ res.json = function(data) {
21
+ if (!res.hasHeader("Content-Type")) {
22
+ res.setHeader("Content-Type", "application/json");
23
+ }
24
+ res.end(JSON.stringify(data));
25
+ };
26
+
27
+ next();
28
+ }
@@ -0,0 +1,16 @@
1
+ export function notFoundHandler(req, res, next) {
2
+ res.writeHead(404, { "Content-Type": "application/json" });
3
+ res.end(JSON.stringify({ error: "Not Found" }));
4
+ }
5
+
6
+ export function globalErrorHandler(err, req, res, next) {
7
+ console.error("Global Error Caught:", err.message || err);
8
+
9
+ if (res.headersSent) {
10
+ return next(err); // Lascia che sia Node a gestire la chiusura forzata
11
+ }
12
+
13
+ const statusCode = err.status || 500;
14
+ res.writeHead(statusCode, { "Content-Type": "application/json" });
15
+ res.end(JSON.stringify({ error: err.message || "Internal Server Error" }));
16
+ }
@@ -0,0 +1,4 @@
1
+ export { requestResponseEnhancer } from './enhancers.js';
2
+ export { jsonBodyParser } from './bodyParser.js';
3
+ export { logger } from './logger.js';
4
+ export { notFoundHandler, globalErrorHandler } from './errorHandlers.js';
@@ -0,0 +1,8 @@
1
+ export function logger(req, res, next) {
2
+ const start = Date.now();
3
+ res.on("finish", () => {
4
+ const duration = Date.now() - start;
5
+ console.log(`[${req.method}] ${req.url} - ${res.statusCode} (${duration}ms)`);
6
+ });
7
+ next();
8
+ }
package/lib/router.js ADDED
@@ -0,0 +1,56 @@
1
+ export function route(method, pathDefinition, ...handlers) {
2
+ // Split the path definition into dynamic parameters and static parts,
3
+ // escaping regex special characters in static parts while extracting parameter names.
4
+ const paramNames = [];
5
+ const parts = pathDefinition.split(/(:[a-zA-Z0-9_]+)/);
6
+ const regexParts = parts.map(part => {
7
+ if (part.startsWith(":")) {
8
+ const paramName = part.slice(1);
9
+ paramNames.push(paramName);
10
+ return "([^/]+)";
11
+ }
12
+ // Escape regex characters in static path parts to avoid weak matches (like '.' matching any character)
13
+ return part.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
14
+ });
15
+ const pathRegex = new RegExp(`^${regexParts.join("")}/?$`);
16
+
17
+ return (req, res, next) => {
18
+ if (req.method === method) {
19
+ // Usiamo req.path (pulito dalle query) se disponibile, altrimenti req.url depurato
20
+ const urlToMatch = req.path || req.url.split('?')[0];
21
+ const match = urlToMatch.match(pathRegex);
22
+
23
+ if (match) {
24
+ // Estraiamo i valori dinamici (es. /notes/15 -> req.params.id = "15")
25
+ req.params = {};
26
+ paramNames.forEach((name, index) => {
27
+ req.params[name] = match[index + 1];
28
+ });
29
+
30
+ // Run route-specific handlers in sequence
31
+ let i = 0;
32
+ const runNext = (err) => {
33
+ if (err) {
34
+ return next(err); // Forward errors directly to global error handler
35
+ }
36
+ if (i < handlers.length) {
37
+ const handler = handlers[i++];
38
+ try {
39
+ Promise.resolve(handler(req, res, runNext)).catch(runNext);
40
+ } catch (caughtErr) {
41
+ runNext(caughtErr);
42
+ }
43
+ } else {
44
+ next(); // Handlers completed without sending a response, fallback to next outer middleware
45
+ }
46
+ };
47
+
48
+ runNext();
49
+ return; // Interrompiamo qui, la route è stata gestita
50
+ }
51
+ }
52
+
53
+ // Se non c'è match, passiamo al prossimo middleware della catena
54
+ next();
55
+ };
56
+ }
package/lib/server.js ADDED
@@ -0,0 +1,88 @@
1
+ import { route } from "./router.js";
2
+ import { runMiddlewares } from "./middlewareRunner.js";
3
+ import { requestResponseEnhancer } from "./middlewares/enhancers.js";
4
+ import { jsonBodyParser } from "./middlewares/bodyParser.js";
5
+ import { logger } from "./middlewares/logger.js";
6
+
7
+ /**
8
+ * Factory che crea l'istanza del server mini-serve.
9
+ *
10
+ * @param {Object} [options] - Opzioni di configurazione dei middleware built-in.
11
+ * @param {boolean} [options.useEnhancers=true] - Se registrare il middleware che arricchisce req e res.
12
+ * @param {boolean} [options.useBodyParser=true] - Se registrare il parser JSON del body per POST/PUT/PATCH.
13
+ * @param {boolean} [options.useLogger=false] - Se abilitare il logger delle richieste in console.
14
+ * @returns {Object} Istanza dell'applicazione con i metodi per definire le rotte e registrare middleware.
15
+ */
16
+ export function createServer(options = {}) {
17
+ const {
18
+ useEnhancers = true,
19
+ useBodyParser = true,
20
+ useLogger = false,
21
+ } = options;
22
+
23
+ const middlewares = [];
24
+
25
+ // Registrazione dei middleware built-in in base alle opzioni
26
+ if (useEnhancers) {
27
+ middlewares.push(requestResponseEnhancer);
28
+ }
29
+ if (useBodyParser) {
30
+ middlewares.push(jsonBodyParser);
31
+ }
32
+ if (useLogger) {
33
+ middlewares.push(logger);
34
+ }
35
+
36
+ const app = {
37
+ /**
38
+ * Registra un middleware generico nello stack.
39
+ */
40
+ use(fn) {
41
+ middlewares.push(fn);
42
+ },
43
+
44
+ /**
45
+ * Registra una rotta GET.
46
+ */
47
+ get(path, ...handler) {
48
+ middlewares.push(route("GET", path, ...handler));
49
+ },
50
+
51
+ /**
52
+ * Registra una rotta POST.
53
+ */
54
+ post(path, ...handler) {
55
+ middlewares.push(route("POST", path, ...handler));
56
+ },
57
+
58
+ /**
59
+ * Registra una rotta PUT.
60
+ */
61
+ put(path, ...handler) {
62
+ middlewares.push(route("PUT", path, ...handler));
63
+ },
64
+
65
+ /**
66
+ * Registra una rotta DELETE.
67
+ */
68
+ delete(path, ...handler) {
69
+ middlewares.push(route("DELETE", path, ...handler));
70
+ },
71
+
72
+ /**
73
+ * Registra una rotta PATCH.
74
+ */
75
+ patch(path, ...handler) {
76
+ middlewares.push(route("PATCH", path, ...handler));
77
+ },
78
+
79
+ /**
80
+ * Il delegato (req, res) da passare a http.createServer().
81
+ */
82
+ handler(req, res) {
83
+ runMiddlewares(middlewares, req, res);
84
+ },
85
+ };
86
+
87
+ return app;
88
+ }
package/package.json ADDED
@@ -0,0 +1,39 @@
1
+ {
2
+ "name": "@mkgabri18/mini-serve",
3
+ "version": "0.1.0",
4
+ "description": "A tiny, zero-dependency HTTP framework for Node.js",
5
+ "type": "module",
6
+ "main": "./lib/index.js",
7
+ "exports": {
8
+ ".": "./lib/index.js",
9
+ "./middlewares": "./lib/middlewares/index.js"
10
+ },
11
+ "repository": {
12
+ "type": "git",
13
+ "url": "https://github.com/Mkgabri18/mini-serve.git"
14
+ },
15
+ "bugs": {
16
+ "url": "https://github.com/Mkgabri18/mini-serve/issues"
17
+ },
18
+ "homepage": "https://github.com/Mkgabri18/mini-serve#readme",
19
+ "files": [
20
+ "lib"
21
+ ],
22
+ "engines": {
23
+ "node": ">=18.0.0"
24
+ },
25
+ "scripts": {
26
+ "dev": "node src/server.js",
27
+ "test": "node --test notes/notes.test.js"
28
+ },
29
+ "keywords": [
30
+ "http",
31
+ "framework",
32
+ "server",
33
+ "middleware",
34
+ "router",
35
+ "zero-dependency"
36
+ ],
37
+ "author": "",
38
+ "license": "MIT"
39
+ }