nicola-framework 1.0.1 → 1.0.2
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 +139 -494
- package/core/Core.js +85 -80
- package/core/Remote.js +7 -2
- package/database/Connection.js +2 -7
- package/database/Model.js +1 -1
- package/database/dialects/Postgres.js +8 -3
- package/dev-tools/LiveCurrent.js +2 -2
- package/middlewares/EasyCors.js +33 -14
- package/middlewares/Shadowgraph.js +2 -3
- package/package.json +6 -6
- package/security/Coherer.js +63 -38
- package/test/Coherer.test.js +73 -0
- package/test/Core.test.js +96 -0
- package/test/Router.test.js +76 -0
- package/utils/console.js +31 -0
- package/utils/expTime.js +30 -0
- package/utils/isPromise.js +7 -0
- package/database/dialects/Mysql.js +0 -0
package/README.md
CHANGED
|
@@ -3,23 +3,25 @@
|
|
|
3
3
|
[](https://www.npmjs.com/package/nicola-framework)
|
|
4
4
|
[](https://opensource.org/licenses/MIT)
|
|
5
5
|
[](https://nodejs.org)
|
|
6
|
-
[](https://www.npmjs.com/package/nicola-framework)
|
|
7
6
|
|
|
8
|
-
>
|
|
7
|
+
> Framework HTTP minimalista para Node.js (ESM): servidor, router, middlewares, JWT y una capa ORM sencilla.
|
|
9
8
|
|
|
10
|
-
Nicola
|
|
9
|
+
Nicola expone un **servidor HTTP nativo** con un **router tipo Express** y utilidades integradas. El proyecto está escrito como **ES Modules** (`"type": "module"`), por lo que los ejemplos usan `import`.
|
|
11
10
|
|
|
12
11
|
---
|
|
13
12
|
|
|
14
|
-
##
|
|
13
|
+
## ✅ Estado actual del proyecto (lo que realmente hay)
|
|
15
14
|
|
|
16
|
-
-
|
|
17
|
-
-
|
|
18
|
-
-
|
|
19
|
-
-
|
|
20
|
-
-
|
|
21
|
-
-
|
|
22
|
-
-
|
|
15
|
+
- **Core/Router**: `Nicola` (default) extiende `Remote`.
|
|
16
|
+
- **Body parsing**: JSON si `Content-Type` incluye `application/json` (límite ~2MB). Si no, `req.body = {}`.
|
|
17
|
+
- **Helpers de response**: `res.json(data)` y `res.send(text)`. (No existe `res.status()`.)
|
|
18
|
+
- **CORS**: `EasyCors` permite `*` y responde `OPTIONS` con `204`.
|
|
19
|
+
- **Security headers**: `Teleforce` aplica headers básicos (no-sniff, frame deny, etc.).
|
|
20
|
+
- **Logger**: `Shadowgraph` loggea al terminar la respuesta.
|
|
21
|
+
- **Errores**: si un handler lanza error o llama `next(err)`, se responde HTML via `BlackBox`.
|
|
22
|
+
- **JWT**: `Coherer` (HS256) funciona vía métodos **estáticos** y requiere `NICOLA_SECRET` en env.
|
|
23
|
+
- **ORM**: `Dynamo` soporta **Postgres** hoy (driver `postgres`). La lib `pg` es **dependencia opcional** (se instala aparte).
|
|
24
|
+
- **Hot reload**: `LiveCurrent` reinicia el proceso Node al detectar cambios en el directorio.
|
|
23
25
|
|
|
24
26
|
---
|
|
25
27
|
|
|
@@ -29,601 +31,244 @@ Nicola es un framework web moderno para Node.js con arquitectura **Zero-Dependen
|
|
|
29
31
|
npm install nicola-framework
|
|
30
32
|
```
|
|
31
33
|
|
|
34
|
+
### (Opcional) Postgres
|
|
35
|
+
|
|
36
|
+
El dialecto Postgres usa `pg` por import dinámico.
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
npm install pg
|
|
40
|
+
```
|
|
41
|
+
|
|
32
42
|
---
|
|
33
43
|
|
|
34
44
|
## ⚡ Quickstart
|
|
35
45
|
|
|
36
|
-
### Servidor HTTP
|
|
46
|
+
### Servidor HTTP básico
|
|
37
47
|
|
|
38
|
-
```
|
|
48
|
+
```js
|
|
39
49
|
import Nicola from 'nicola-framework';
|
|
40
50
|
|
|
41
51
|
const app = new Nicola();
|
|
42
52
|
|
|
43
53
|
app.get('/', (req, res) => {
|
|
44
|
-
res.json({ message: 'Hello from Nicola!' });
|
|
54
|
+
res.json({ ok: true, message: 'Hello from Nicola!' });
|
|
45
55
|
});
|
|
46
56
|
|
|
47
57
|
app.listen(3000, () => {
|
|
48
|
-
console.log('
|
|
58
|
+
console.log('Server running on http://localhost:3000');
|
|
49
59
|
});
|
|
50
60
|
```
|
|
51
61
|
|
|
52
|
-
###
|
|
62
|
+
### Router anidado y params
|
|
53
63
|
|
|
54
|
-
```
|
|
55
|
-
import { Nicola, Remote
|
|
64
|
+
```js
|
|
65
|
+
import { Nicola, Remote } from 'nicola-framework';
|
|
56
66
|
|
|
57
67
|
const app = new Nicola();
|
|
58
|
-
const
|
|
59
|
-
|
|
60
|
-
// Middlewares globales
|
|
61
|
-
app.use(Shadowgraph); // Logger HTTP
|
|
62
|
-
app.use(Teleforce); // Security headers
|
|
68
|
+
const api = new Remote();
|
|
63
69
|
|
|
64
|
-
|
|
65
|
-
router.get('/users', (req, res) => {
|
|
70
|
+
api.get('/users', (req, res) => {
|
|
66
71
|
res.json({ users: ['Alice', 'Bob'] });
|
|
67
72
|
});
|
|
68
73
|
|
|
69
|
-
|
|
74
|
+
api.get('/users/:id', (req, res) => {
|
|
70
75
|
res.json({ userId: req.params.id });
|
|
71
76
|
});
|
|
72
77
|
|
|
73
|
-
app.use('/api',
|
|
74
|
-
|
|
75
|
-
app.listen(3000);
|
|
76
|
-
```
|
|
77
|
-
|
|
78
|
-
### Con ORM (Dynamo)
|
|
79
|
-
|
|
80
|
-
```javascript
|
|
81
|
-
import { Nicola, Dynamo } from 'nicola-framework';
|
|
82
|
-
import User from './models/User.js';
|
|
83
|
-
|
|
84
|
-
// Configurar base de datos
|
|
85
|
-
await Dynamo.connect({
|
|
86
|
-
dialect: 'mysql',
|
|
87
|
-
host: 'localhost',
|
|
88
|
-
user: 'root',
|
|
89
|
-
password: '',
|
|
90
|
-
database: 'mydb'
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
const app = new Nicola();
|
|
94
|
-
|
|
95
|
-
// Crear usuario
|
|
96
|
-
app.post('/users', async (req, res) => {
|
|
97
|
-
const user = await User.create(req.body);
|
|
98
|
-
res.json(user);
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
// Buscar usuarios
|
|
102
|
-
app.get('/users', async (req, res) => {
|
|
103
|
-
const users = await User.where('active', true).get();
|
|
104
|
-
res.json(users);
|
|
105
|
-
});
|
|
106
|
-
|
|
78
|
+
app.use('/api', api);
|
|
107
79
|
app.listen(3000);
|
|
108
80
|
```
|
|
109
81
|
|
|
110
82
|
---
|
|
111
83
|
|
|
112
|
-
##
|
|
113
|
-
|
|
114
|
-
Nicola está compuesto por módulos independientes que trabajan en armonía:
|
|
84
|
+
## 🧠 API (resumen fiel al código)
|
|
115
85
|
|
|
116
|
-
|
|
117
|
-
|--------|---------|-------------|
|
|
118
|
-
| **Core** | El Motor | Servidor HTTP nativo con body parsing y middleware system |
|
|
119
|
-
| **Remote** | El Cerebro | Router avanzado con soporte MVC, regex y rutas dinámicas |
|
|
120
|
-
| **Dynamo** | La Base | ORM completo con QueryBuilder y Active Record |
|
|
121
|
-
| **Coherer** | El Notario | Sistema JWT nativo (HMAC SHA256) |
|
|
122
|
-
| **Shadowgraph** | El Observador | Logger HTTP de alto rendimiento |
|
|
123
|
-
| **Teleforce** | El Escudo | Suite de seguridad (XSS, NoSniff, CORS) |
|
|
124
|
-
| **BlackBox** | El Forense | Error handler visual estilo terminal |
|
|
125
|
-
| **Insulator** | El Portero | Validador de schemas |
|
|
126
|
-
| **Regulator** | El Gestor | Parser de variables .env |
|
|
127
|
-
| **LiveCurrent** | El Fénix | Hot reload automático |
|
|
86
|
+
### `Nicola` (Core)
|
|
128
87
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
### 1. Core (Servidor HTTP)
|
|
88
|
+
- `new Nicola()`
|
|
89
|
+
- `app.get/post/put/patch/delete(path, ...handlers)`
|
|
90
|
+
- `app.use([path], ...handlers | router)`
|
|
91
|
+
- `app.listen(port, [callback])`
|
|
134
92
|
|
|
135
|
-
|
|
93
|
+
Notas:
|
|
94
|
+
- `Nicola.listen()` ejecuta internamente `Shadowgraph`, `EasyCors` y `Teleforce` en cada request.
|
|
95
|
+
- `req.query` se construye desde querystring.
|
|
136
96
|
|
|
137
|
-
|
|
138
|
-
import Nicola from 'nicola-framework';
|
|
97
|
+
### `Remote` (Router)
|
|
139
98
|
|
|
140
|
-
|
|
99
|
+
`Remote` es el router base. Soporta middlewares y rutas con params (`/users/:id`).
|
|
141
100
|
|
|
142
|
-
|
|
143
|
-
app.get('/users', handler);
|
|
144
|
-
app.post('/users', handler);
|
|
145
|
-
app.put('/users/:id', handler);
|
|
146
|
-
app.delete('/users/:id', handler);
|
|
147
|
-
app.patch('/users/:id', handler);
|
|
101
|
+
Ejemplo de middleware simple:
|
|
148
102
|
|
|
149
|
-
|
|
103
|
+
```js
|
|
150
104
|
app.use((req, res, next) => {
|
|
151
|
-
|
|
105
|
+
// No existe res.status(); usa statusCode
|
|
106
|
+
if (req.url === '/blocked') {
|
|
107
|
+
res.statusCode = 403;
|
|
108
|
+
res.end('Forbidden');
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
152
111
|
next();
|
|
153
112
|
});
|
|
154
|
-
|
|
155
|
-
// Prefijo de ruta
|
|
156
|
-
app.use('/api', router);
|
|
157
|
-
|
|
158
|
-
app.listen(3000);
|
|
159
113
|
```
|
|
160
114
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
Sistema de routing recursivo con soporte de rutas dinámicas.
|
|
164
|
-
|
|
165
|
-
```javascript
|
|
166
|
-
import { Remote } from 'nicola-framework';
|
|
167
|
-
|
|
168
|
-
const router = new Remote();
|
|
169
|
-
|
|
170
|
-
// Rutas básicas
|
|
171
|
-
router.get('/users', getUsers);
|
|
172
|
-
router.post('/users', createUser);
|
|
173
|
-
|
|
174
|
-
// Parámetros dinámicos
|
|
175
|
-
router.get('/users/:id', getUser); // req.params.id
|
|
176
|
-
router.get('/posts/:slug/comments/:id', getComment);
|
|
177
|
-
|
|
178
|
-
// Routers anidados
|
|
179
|
-
const adminRouter = new Remote();
|
|
180
|
-
adminRouter.get('/dashboard', adminDashboard);
|
|
181
|
-
router.use('/admin', adminRouter); // /admin/dashboard
|
|
182
|
-
```
|
|
183
|
-
|
|
184
|
-
### 3. Dynamo ORM
|
|
185
|
-
|
|
186
|
-
ORM completo con Active Record pattern.
|
|
187
|
-
|
|
188
|
-
#### Configuración
|
|
189
|
-
|
|
190
|
-
```javascript
|
|
191
|
-
import { Dynamo } from 'nicola-framework';
|
|
115
|
+
Errores:
|
|
192
116
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
port: 3306,
|
|
197
|
-
user: 'root',
|
|
198
|
-
password: 'secret',
|
|
199
|
-
database: 'mydb'
|
|
117
|
+
```js
|
|
118
|
+
app.get('/boom', (req, res) => {
|
|
119
|
+
throw new Error('Boom');
|
|
200
120
|
});
|
|
201
121
|
```
|
|
202
122
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
```javascript
|
|
206
|
-
import { Dynamo } from 'nicola-framework';
|
|
207
|
-
|
|
208
|
-
class User extends Dynamo.Model {
|
|
209
|
-
static table = 'users';
|
|
210
|
-
|
|
211
|
-
static schema = {
|
|
212
|
-
name: { type: 'string', required: true },
|
|
213
|
-
email: { type: 'string', required: true },
|
|
214
|
-
age: { type: 'number' }
|
|
215
|
-
};
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
export default User;
|
|
219
|
-
```
|
|
220
|
-
|
|
221
|
-
#### Query Builder
|
|
222
|
-
|
|
223
|
-
```javascript
|
|
224
|
-
// Seleccionar todos
|
|
225
|
-
const users = await User.all();
|
|
226
|
-
|
|
227
|
-
// Buscar por ID
|
|
228
|
-
const user = await User.find(1);
|
|
229
|
-
|
|
230
|
-
// Condiciones
|
|
231
|
-
const activeUsers = await User.where('active', true).get();
|
|
232
|
-
const adults = await User.where('age', '>', 18).get();
|
|
233
|
-
|
|
234
|
-
// Múltiples condiciones
|
|
235
|
-
const results = await User
|
|
236
|
-
.where('country', 'US')
|
|
237
|
-
.where('age', '>=', 21)
|
|
238
|
-
.get();
|
|
123
|
+
---
|
|
239
124
|
|
|
240
|
-
|
|
241
|
-
const newUser = await User.create({
|
|
242
|
-
name: 'Alice',
|
|
243
|
-
email: 'alice@example.com'
|
|
244
|
-
});
|
|
125
|
+
## 🔐 Seguridad
|
|
245
126
|
|
|
246
|
-
|
|
247
|
-
await User.where('id', 5).update({ name: 'Bob' });
|
|
127
|
+
### `Regulator` (.env)
|
|
248
128
|
|
|
249
|
-
|
|
250
|
-
await User.where('id', 5).delete();
|
|
129
|
+
Lee `.env` desde el directorio actual (`process.cwd()`) y lo copia a `process.env`.
|
|
251
130
|
|
|
252
|
-
|
|
253
|
-
|
|
131
|
+
```js
|
|
132
|
+
import { Regulator } from 'nicola-framework';
|
|
254
133
|
|
|
255
|
-
|
|
256
|
-
const first10 = await User.limit(10).get();
|
|
134
|
+
Regulator.load();
|
|
257
135
|
```
|
|
258
136
|
|
|
259
|
-
###
|
|
260
|
-
|
|
261
|
-
Sistema de autenticación JWT sin dependencias externas.
|
|
137
|
+
### `Coherer` (JWT HS256)
|
|
262
138
|
|
|
263
|
-
|
|
264
|
-
import { Coherer } from 'nicola-framework';
|
|
139
|
+
`Coherer` es una clase con métodos **estáticos**.
|
|
265
140
|
|
|
266
|
-
|
|
141
|
+
```js
|
|
142
|
+
import { Regulator, Coherer } from 'nicola-framework';
|
|
267
143
|
|
|
268
|
-
|
|
269
|
-
const token = jwt.sign({
|
|
270
|
-
userId: 123,
|
|
271
|
-
role: 'admin'
|
|
272
|
-
}, '24h'); // Expira en 24 horas
|
|
273
|
-
|
|
274
|
-
// Verificar token
|
|
275
|
-
const payload = jwt.verify(token);
|
|
276
|
-
if (payload) {
|
|
277
|
-
console.log('Token válido:', payload);
|
|
278
|
-
} else {
|
|
279
|
-
console.log('Token inválido o expirado');
|
|
280
|
-
}
|
|
144
|
+
Regulator.load();
|
|
281
145
|
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
const payload = jwt.verify(token);
|
|
287
|
-
if (!payload) {
|
|
288
|
-
return res.status(401).json({ error: 'No autorizado' });
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
req.user = payload;
|
|
292
|
-
next();
|
|
293
|
-
}
|
|
146
|
+
const token = Coherer.sign(
|
|
147
|
+
{ userId: 123, role: 'admin' },
|
|
148
|
+
{ expiresIn: '24h' }
|
|
149
|
+
);
|
|
294
150
|
|
|
295
|
-
|
|
151
|
+
const payload = Coherer.verify(token);
|
|
152
|
+
console.log(payload.userId);
|
|
296
153
|
```
|
|
297
154
|
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
Parser seguro de archivos .env
|
|
301
|
-
|
|
302
|
-
```javascript
|
|
303
|
-
import { Regulator } from 'nicola-framework';
|
|
304
|
-
|
|
305
|
-
// Cargar .env
|
|
306
|
-
Regulator.load();
|
|
155
|
+
`.env` mínimo:
|
|
307
156
|
|
|
308
|
-
// Acceder a variables
|
|
309
|
-
const port = process.env.PORT || 3000;
|
|
310
|
-
const dbUrl = process.env.DATABASE_URL;
|
|
311
|
-
const secret = process.env.JWT_SECRET;
|
|
312
|
-
```
|
|
313
|
-
|
|
314
|
-
**.env example:**
|
|
315
157
|
```env
|
|
316
|
-
|
|
317
|
-
DATABASE_URL=mysql://root:password@localhost:3306/mydb
|
|
318
|
-
JWT_SECRET=mi-super-secreto-jwt
|
|
319
|
-
NODE_ENV=development
|
|
158
|
+
NICOLA_SECRET=mi-secreto-super-seguro
|
|
320
159
|
```
|
|
321
160
|
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
#### Shadowgraph (Logger HTTP)
|
|
161
|
+
---
|
|
325
162
|
|
|
326
|
-
|
|
327
|
-
import { Shadowgraph } from 'nicola-framework';
|
|
163
|
+
## 🗃️ Dynamo (ORM)
|
|
328
164
|
|
|
329
|
-
|
|
330
|
-
// Output: GET /users → 200 (15ms)
|
|
331
|
-
```
|
|
165
|
+
### Configuración
|
|
332
166
|
|
|
333
|
-
|
|
167
|
+
`Dynamo.connect()` **no recibe config**: toma la configuración desde variables de entorno.
|
|
334
168
|
|
|
335
|
-
```
|
|
336
|
-
import {
|
|
169
|
+
```js
|
|
170
|
+
import { Regulator, Dynamo } from 'nicola-framework';
|
|
337
171
|
|
|
338
|
-
|
|
339
|
-
|
|
172
|
+
Regulator.load();
|
|
173
|
+
await Dynamo.connect();
|
|
340
174
|
```
|
|
341
175
|
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
```javascript
|
|
345
|
-
import { EasyCors } from 'nicola-framework';
|
|
176
|
+
`.env` para Postgres:
|
|
346
177
|
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
178
|
+
```env
|
|
179
|
+
DB_DRIVER=postgres
|
|
180
|
+
DB_HOST=localhost
|
|
181
|
+
DB_PORT=5432
|
|
182
|
+
DB_USER=postgres
|
|
183
|
+
DB_PASS=postgres
|
|
184
|
+
DB_NAME=mydb
|
|
352
185
|
```
|
|
353
186
|
|
|
354
|
-
|
|
187
|
+
### Modelos
|
|
355
188
|
|
|
356
|
-
```
|
|
357
|
-
import {
|
|
358
|
-
|
|
359
|
-
app.use(BlackBox); // Debe ser el último middleware
|
|
360
|
-
// Captura errores y muestra interfaz visual
|
|
361
|
-
```
|
|
189
|
+
```js
|
|
190
|
+
import { Dynamo } from 'nicola-framework';
|
|
362
191
|
|
|
363
|
-
|
|
192
|
+
export default class User extends Dynamo.Model {
|
|
193
|
+
static tableName = 'users';
|
|
364
194
|
|
|
365
|
-
|
|
366
|
-
|
|
195
|
+
static schema = {
|
|
196
|
+
name: { type: 'string', required: true },
|
|
197
|
+
email: { type: 'string', required: true },
|
|
198
|
+
age: { type: 'number', required: false }
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
```
|
|
367
202
|
|
|
368
|
-
|
|
369
|
-
name: { type: 'string', required: true, min: 3 },
|
|
370
|
-
email: { type: 'string', required: true, pattern: /^[^\s@]+@[^\s@]+\.[^\s@]+$/ },
|
|
371
|
-
age: { type: 'number', min: 18 }
|
|
372
|
-
};
|
|
203
|
+
### Queries
|
|
373
204
|
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
});
|
|
378
|
-
```
|
|
205
|
+
```js
|
|
206
|
+
// All
|
|
207
|
+
const users = await User.all();
|
|
379
208
|
|
|
380
|
-
|
|
209
|
+
// Where
|
|
210
|
+
const active = await User.where('active', true).get();
|
|
381
211
|
|
|
382
|
-
|
|
212
|
+
// Insert (valida contra schema)
|
|
213
|
+
const created = await User.create({ name: 'Alice', email: 'a@a.com', age: 20 });
|
|
383
214
|
|
|
384
|
-
|
|
385
|
-
|
|
215
|
+
// Update / Delete
|
|
216
|
+
await User.where('id', 1).update({ name: 'Alice 2' });
|
|
217
|
+
await User.where('id', 1).delete();
|
|
386
218
|
|
|
387
|
-
//
|
|
388
|
-
|
|
389
|
-
watch: ['src/', 'config/'],
|
|
390
|
-
ignore: ['node_modules/', 'logs/']
|
|
391
|
-
});
|
|
392
|
-
```
|
|
219
|
+
// Select específico (usa string con coma)
|
|
220
|
+
const names = await User.select('name,email').get();
|
|
393
221
|
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
# El servidor se recarga automáticamente al editar archivos
|
|
222
|
+
// Order/limit/offset vía QueryBuilder
|
|
223
|
+
const latest = await User.query().orderBy('id', 'DESC').limit(10).offset(0).get();
|
|
397
224
|
```
|
|
398
225
|
|
|
399
226
|
---
|
|
400
227
|
|
|
401
|
-
##
|
|
228
|
+
## 🧩 Middlewares
|
|
402
229
|
|
|
403
|
-
###
|
|
230
|
+
### `Insulator(schema)`
|
|
404
231
|
|
|
405
|
-
|
|
406
|
-
import { Nicola, Remote, Coherer, Regulator } from 'nicola-framework';
|
|
407
|
-
import User from './models/User.js';
|
|
232
|
+
Valida `req.body` con un esquema **simple** de tipos (`typeof`).
|
|
408
233
|
|
|
409
|
-
|
|
234
|
+
```js
|
|
235
|
+
import { Insulator } from 'nicola-framework';
|
|
410
236
|
|
|
411
|
-
const
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
// Middleware de autenticación
|
|
416
|
-
const authenticate = (req, res, next) => {
|
|
417
|
-
const token = req.headers.authorization?.split(' ')[1];
|
|
418
|
-
const payload = jwt.verify(token);
|
|
419
|
-
|
|
420
|
-
if (!payload) {
|
|
421
|
-
return res.status(401).json({ error: 'No autorizado' });
|
|
422
|
-
}
|
|
423
|
-
|
|
424
|
-
req.user = payload;
|
|
425
|
-
next();
|
|
237
|
+
const schema = {
|
|
238
|
+
name: 'string',
|
|
239
|
+
age: 'number'
|
|
426
240
|
};
|
|
427
241
|
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
const user = await User.create(req.body);
|
|
431
|
-
const token = jwt.sign({ userId: user.id }, '7d');
|
|
432
|
-
|
|
433
|
-
res.json({ user, token });
|
|
434
|
-
});
|
|
435
|
-
|
|
436
|
-
router.post('/login', async (req, res) => {
|
|
437
|
-
const user = await User.where('email', req.body.email).first();
|
|
438
|
-
|
|
439
|
-
if (!user || user.password !== req.body.password) {
|
|
440
|
-
return res.status(401).json({ error: 'Credenciales inválidas' });
|
|
441
|
-
}
|
|
442
|
-
|
|
443
|
-
const token = jwt.sign({ userId: user.id }, '7d');
|
|
444
|
-
res.json({ user, token });
|
|
445
|
-
});
|
|
446
|
-
|
|
447
|
-
// Rutas protegidas
|
|
448
|
-
router.get('/profile', authenticate, async (req, res) => {
|
|
449
|
-
const user = await User.find(req.user.userId);
|
|
450
|
-
res.json(user);
|
|
451
|
-
});
|
|
452
|
-
|
|
453
|
-
router.put('/profile', authenticate, async (req, res) => {
|
|
454
|
-
await User.where('id', req.user.userId).update(req.body);
|
|
455
|
-
res.json({ message: 'Perfil actualizado' });
|
|
242
|
+
app.post('/users', Insulator(schema), (req, res) => {
|
|
243
|
+
res.json({ ok: true });
|
|
456
244
|
});
|
|
457
|
-
|
|
458
|
-
app.use('/api', router);
|
|
459
|
-
app.listen(3000);
|
|
460
245
|
```
|
|
461
246
|
|
|
462
|
-
###
|
|
463
|
-
|
|
464
|
-
```javascript
|
|
465
|
-
import { Nicola, Remote, Dynamo, Insulator, Shadowgraph, BlackBox } from 'nicola-framework';
|
|
466
|
-
import Post from './models/Post.js';
|
|
467
|
-
|
|
468
|
-
await Dynamo.connect({
|
|
469
|
-
dialect: 'mysql',
|
|
470
|
-
host: 'localhost',
|
|
471
|
-
database: 'blog'
|
|
472
|
-
});
|
|
473
|
-
|
|
474
|
-
const app = new Nicola();
|
|
475
|
-
const router = new Remote();
|
|
476
|
-
|
|
477
|
-
app.use(Shadowgraph); // Logger
|
|
478
|
-
|
|
479
|
-
const postSchema = {
|
|
480
|
-
title: { type: 'string', required: true, min: 5 },
|
|
481
|
-
content: { type: 'string', required: true },
|
|
482
|
-
published: { type: 'boolean' }
|
|
483
|
-
};
|
|
484
|
-
|
|
485
|
-
// List posts
|
|
486
|
-
router.get('/', async (req, res) => {
|
|
487
|
-
const posts = await Post.where('published', true).get();
|
|
488
|
-
res.json(posts);
|
|
489
|
-
});
|
|
490
|
-
|
|
491
|
-
// Get single post
|
|
492
|
-
router.get('/:id', async (req, res) => {
|
|
493
|
-
const post = await Post.find(req.params.id);
|
|
494
|
-
|
|
495
|
-
if (!post) {
|
|
496
|
-
return res.status(404).json({ error: 'Post no encontrado' });
|
|
497
|
-
}
|
|
498
|
-
|
|
499
|
-
res.json(post);
|
|
500
|
-
});
|
|
501
|
-
|
|
502
|
-
// Create post
|
|
503
|
-
router.post('/', Insulator(postSchema), async (req, res) => {
|
|
504
|
-
const post = await Post.create(req.body);
|
|
505
|
-
res.status(201).json(post);
|
|
506
|
-
});
|
|
507
|
-
|
|
508
|
-
// Update post
|
|
509
|
-
router.put('/:id', Insulator(postSchema), async (req, res) => {
|
|
510
|
-
await Post.where('id', req.params.id).update(req.body);
|
|
511
|
-
res.json({ message: 'Post actualizado' });
|
|
512
|
-
});
|
|
513
|
-
|
|
514
|
-
// Delete post
|
|
515
|
-
router.delete('/:id', async (req, res) => {
|
|
516
|
-
await Post.where('id', req.params.id).delete();
|
|
517
|
-
res.status(204).send();
|
|
518
|
-
});
|
|
247
|
+
### `Shadowgraph`, `Teleforce`, `EasyCors`
|
|
519
248
|
|
|
520
|
-
|
|
521
|
-
app.use(BlackBox); // Error handler (último)
|
|
522
|
-
|
|
523
|
-
app.listen(3000);
|
|
524
|
-
```
|
|
249
|
+
Se ejecutan automáticamente dentro de `Nicola.listen()`. También puedes llamarlos manualmente si estás usando `Remote` por separado.
|
|
525
250
|
|
|
526
251
|
---
|
|
527
252
|
|
|
528
|
-
##
|
|
253
|
+
## 🔥 LiveCurrent (hot reload)
|
|
529
254
|
|
|
530
|
-
```
|
|
531
|
-
|
|
532
|
-
├── /src
|
|
533
|
-
│ ├── /controllers # Lógica de negocio
|
|
534
|
-
│ ├── /models # Modelos Dynamo
|
|
535
|
-
│ ├── /routes # Definición de rutas
|
|
536
|
-
│ ├── /middlewares # Middlewares custom
|
|
537
|
-
│ └── /config # Configuraciones
|
|
538
|
-
├── app.js # Entry point
|
|
539
|
-
├── dev.js # Script desarrollo
|
|
540
|
-
├── .env # Variables de entorno
|
|
541
|
-
├── .env.example # Template de .env
|
|
542
|
-
└── package.json
|
|
543
|
-
```
|
|
255
|
+
```js
|
|
256
|
+
import { LiveCurrent } from 'nicola-framework';
|
|
544
257
|
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
## 🚀 Scripts NPM Recomendados
|
|
548
|
-
|
|
549
|
-
```json
|
|
550
|
-
{
|
|
551
|
-
"scripts": {
|
|
552
|
-
"start": "node app.js",
|
|
553
|
-
"dev": "node dev.js",
|
|
554
|
-
"test": "node --test"
|
|
555
|
-
}
|
|
556
|
-
}
|
|
258
|
+
const dev = new LiveCurrent('app.js');
|
|
259
|
+
dev.boot();
|
|
557
260
|
```
|
|
558
261
|
|
|
559
262
|
---
|
|
560
263
|
|
|
561
|
-
## 🌟 Características Destacadas
|
|
562
|
-
|
|
563
|
-
### Zero Dependencies
|
|
564
|
-
A diferencia de Express o Fastify, Nicola no tiene dependencias externas (excepto chalk para logs en desarrollo). Todo está construido con módulos nativos de Node.js.
|
|
565
|
-
|
|
566
|
-
### JWT Nativo
|
|
567
|
-
Implementación propia de JWT usando el módulo `crypto` nativo, sin necesidad de jsonwebtoken.
|
|
568
|
-
|
|
569
|
-
### ORM Incluido
|
|
570
|
-
No necesitas instalar Sequelize, TypeORM o Prisma. Dynamo viene integrado con soporte para MySQL y PostgreSQL.
|
|
571
|
-
|
|
572
|
-
### Hot Reload Nativo
|
|
573
|
-
LiveCurrent detecta cambios en archivos y reinicia el servidor sin necesidad de nodemon o pm2.
|
|
574
|
-
|
|
575
|
-
### Error Handler Visual
|
|
576
|
-
BlackBox captura errores y los muestra en una interfaz web elegante con stack trace completo.
|
|
577
|
-
|
|
578
|
-
---
|
|
579
|
-
|
|
580
|
-
## 📊 Comparación con Otros Frameworks
|
|
581
|
-
|
|
582
|
-
| Feature | Nicola | Express | Fastify |
|
|
583
|
-
|---------|--------|---------|---------|
|
|
584
|
-
| Dependencies | 0 | ~50 | ~20 |
|
|
585
|
-
| Router | ✅ Incluido | ✅ | ✅ |
|
|
586
|
-
| ORM | ✅ Incluido | ❌ | ❌ |
|
|
587
|
-
| JWT | ✅ Nativo | ❌ | ❌ |
|
|
588
|
-
| Hot Reload | ✅ Nativo | ❌ | ❌ |
|
|
589
|
-
| Error UI | ✅ Visual | ❌ | ❌ |
|
|
590
|
-
| Validation | ✅ Incluido | ❌ | ✅ |
|
|
591
|
-
|
|
592
|
-
---
|
|
593
|
-
|
|
594
264
|
## 🤝 Contribuir
|
|
595
265
|
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
2. Crea una rama (`git checkout -b feature/amazing-feature`)
|
|
600
|
-
3. Commit tus cambios (`git commit -m 'Add amazing feature'`)
|
|
601
|
-
4. Push a la rama (`git push origin feature/amazing-feature`)
|
|
602
|
-
5. Abre un Pull Request
|
|
266
|
+
1. Fork
|
|
267
|
+
2. Rama feature
|
|
268
|
+
3. PR
|
|
603
269
|
|
|
604
270
|
---
|
|
605
271
|
|
|
606
272
|
## 📝 Licencia
|
|
607
273
|
|
|
608
|
-
MIT ©
|
|
609
|
-
|
|
610
|
-
---
|
|
611
|
-
|
|
612
|
-
## 🙏 Agradecimientos
|
|
613
|
-
|
|
614
|
-
Inspirado en la filosofía de Nikola Nicola: *"Si quieres encontrar los secretos del universo, piensa en términos de energía, frecuencia y vibración."*
|
|
615
|
-
|
|
616
|
-
---
|
|
617
|
-
|
|
618
|
-
## 📬 Contacto
|
|
619
|
-
|
|
620
|
-
- **NPM**: [nicola-framework](https://www.npmjs.com/package/nicola-framework)
|
|
621
|
-
- **Issues**: [GitHub Issues](https://github.com/yourusername/nicola-framework/issues)
|
|
622
|
-
|
|
623
|
-
---
|
|
624
|
-
|
|
625
|
-
<div align="center">
|
|
626
|
-
|
|
627
|
-
⚡ **Built with electricity and zero dependencies** ⚡
|
|
628
|
-
|
|
629
|
-
</div>
|
|
274
|
+
MIT © Erick Mauricio Tiznado Rodriguez
|