Entrevista nodejs Flashcards

(139 cards)

1
Q

¿Cómo trabaja Node.js?

A

Node.js trabaja con un solo hilo y utiliza un modelo de E/S no bloqueante basado en eventos. Esto significa que solo ejecuta un proceso a la vez, pero puede manejar múltiples operaciones simultáneamente mediante el uso de un bucle de eventos (Event Loop), delegando tareas de E/S o computación intensiva a subprocesos o APIs de sistema.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
2
Q

¿Qué es el modelo Blocking y Non-Blocking en Node.js?

A

Blocking: La ejecución del programa se detiene hasta que una operación completa su tarea. Por ejemplo, usar métodos síncronos como fs.readFileSync.
Non-Blocking: Permite que el programa continúe ejecutándose sin esperar a que una operación termine, usando métodos asíncronos como fs.readFile.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
3
Q

¿Cuál es la diferencia entre operaciones síncronas y asíncronas?

A

Síncronas: Bloquean el flujo del programa hasta que se completa la operación.
Asíncronas: Permiten que otras tareas se ejecuten mientras una operación está en curso, delegando su resolución a un callback, promesa o async/await.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
4
Q

¿Qué son los callbacks y cómo se usan en Node.js?

A

En Node.js, un “callback” es una función que se pasa como argumento a otra función y se ejecuta cuando se completa una operación asincrónica, como leer un archivo o realizar una consulta a la base de datos. Node.js usa callbacks para garantizar que el código no se bloquee durante la espera de estas operaciones.

Ejemplo de lectura de archivo con callback:

```javascript
const fs = require(‘fs’);

fs.readFile(‘archivo.txt’, ‘utf8’, (err, data) => {
if (err) {
console.log(‘Error:’, err);
} else {
console.log(‘Contenido del archivo:’, data);
}
});
~~~

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
5
Q

¿Qué es el Event Loop en Node.js?

A

El Event Loop es el mecanismo que permite a Node.js manejar operaciones asíncronas. Este verifica continuamente si hay tareas en la Callback Queue que deben ser ejecutadas en el Call Stack. Si el Call Stack está vacío, transfiere las tareas de la Callback Queue para su ejecución.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
6
Q

¿Qué es el Call Stack y cómo interactúa con el Event Loop?

A

El Call Stack es una estructura de datos en la que Node.js mantiene un seguimiento de las funciones que están siendo ejecutadas. Las operaciones se apilan (push) cuando son llamadas y se desapilan (pop) cuando terminan.
El Event Loop solo mueve tareas de la Callback Queue al Call Stack si este último está vacío.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
7
Q

¿Qué es la Callback Queue?

A

Es una cola donde se almacenan los callbacks listos para ser ejecutados una vez que las operaciones asíncronas correspondientes han terminado. Utiliza un modelo FIFO (First In, First Out).

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
8
Q

¿Qué papel juega Libuv en Node.js?

A

Libuv es una biblioteca escrita en C que abstrae las operaciones de E/S no bloqueantes y provee el Event Loop. Maneja subprocesos, operaciones de archivos, sockets y temporizadores.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
9
Q

¿Cómo manejarías un código asíncrono que se vuelve difícil de leer debido al anidamiento de callbacks?

A

Este problema se conoce como Callback Hell. Para solucionarlo, podemos:
1. Usar Promesas:

javascript
   fs.promises.readFile('archivo.txt', 'utf8')
     .then(data => console.log(data))
     .catch(err => console.error(err));
  

2. Usar async/await para un flujo más limpio:
javascript
   async function leerArchivo() {
     try {
       const data = await fs.promises.readFile('archivo.txt', 'utf8');
       console.log(data);
     } catch (err) {
       console.error(err);
     }
   }
   leerArchivo();
  
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
10
Q

¿Qué son las Promesas en Node.js y cómo funcionan?

A

Las “promesas” son un mecanismo para manejar operaciones asincrónicas en JavaScript. Una promesa representa un valor que puede no estar disponible en el momento de su creación, pero lo estará en el futuro. Tiene tres estados:
- Pending: La promesa está en progreso.
- Resolved: La promesa se resolvió correctamente.
- Rejected: La promesa fue rechazada con un error.

Se puede crear una promesa con el constructor Promise, y se usa .then() para manejar el resultado, o .catch() para manejar errores.

Ejemplo:

```javascript
const miPromesa = new Promise((resolve, reject) => {
const exito = true;
if (exito) {
resolve(‘Operación exitosa’);
} else {
reject(‘Hubo un error’);
}
});

miPromesa
.then((resultado) => console.log(resultado)) // “Operación exitosa”
.catch((error) => console.log(error)); // “Hubo un error”
~~~

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
11
Q

¿Qué es async/await y cómo mejora el manejo de código asíncrono?

A

async/await es una sintaxis moderna que permite trabajar con Promesas de manera más sencilla y similar a un flujo síncrono.
Ejemplo:

```javascript
async function ejemplo() {
try {
const resultado = await fs.promises.readFile(‘archivo.txt’, ‘utf8’);
console.log(resultado);
} catch (error) {
console.error(error);
}
}
~~~

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
12
Q

¿Qué diferencia hay entre setImmediate y process.nextTick?

A
  • setImmediate: Agenda la ejecución de una función en la siguiente iteración del Event Loop.
  • process.nextTick: Ejecuta la función inmediatamente después de que se complete la ejecución actual, antes de que el Event Loop procese otras tareas.

Ejemplo:

```javascript
console.log(‘Inicio’);
setImmediate(() => console.log(‘setImmediate’));
process.nextTick(() => console.log(‘nextTick’));
console.log(‘Fin’);
// Output: Inicio, Fin, nextTick, setImmediate
~~~

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
13
Q

¿Qué pasa si bloqueas el Event Loop?

A

Bloquear el Event Loop, por ejemplo, con cálculos intensivos o bucles infinitos, impide que Node.js maneje nuevas solicitudes o complete tareas asíncronas, causando una mala experiencia del usuario.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
14
Q

¿Cómo se comunican los subprocesos en Node.js?

A

Node.js utiliza el módulo worker_threads o child_process para crear subprocesos o procesos secundarios. Estos pueden comunicarse mediante eventos y mensajes, gracias a la serialización de datos con postMessage y onmessage.

Ejemplo usando worker_threads:

```javascript
const { Worker } = require(‘worker_threads’);

const worker = new Worker(‘./worker.js’);
worker.postMessage(‘Inicia tarea’);
worker.on(‘message’, (msg) => console.log(msg));
~~~

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
15
Q

¿Qué es Express.js y cuáles son sus principales características?

A

Express.js es un marco de aplicación web minimalista para Node.js que facilita la creación de APIs y servidores web.
Principales características:
1. Rutas (Routing): Manejo sencillo de rutas para diferentes métodos HTTP (GET, POST, etc.).
2. Middleware: Permite manejar solicitudes, respuestas y lógica intermedia.
3. Plantillas: Soporte para motores de plantillas como EJS, Pug o Handlebars.
4. Compatibilidad: Flexible y compatible con múltiples bibliotecas y herramientas.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
16
Q

¿Qué son los middleware en Express.js y cómo funcionan?

A

Un middleware en Express.js es una función que tiene acceso al objeto de solicitud (req), al objeto de respuesta (res) y a la siguiente función middleware en el ciclo de vida de la solicitud. Los middlewares se utilizan para realizar tareas como autenticación, validación de datos, logging, etc.

Ejemplo:

```javascript
const express = require(‘express’);
const app = express();

// Middleware de logging
app.use((req, res, next) => {
console.log(${req.method} ${req.url});
next(); // Pasa al siguiente middleware
});

app.get(‘/’, (req, res) => {
res.send(‘Hola, Mundo!’);
});

app.listen(3000, () => console.log(‘Servidor en puerto 3000’));
~~~

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
17
Q

¿Cómo se manejan los errores en Express.js?

A

Express.js maneja errores utilizando middleware de manejo de errores. Un middleware de error tiene cuatro parámetros: err, req, res, y next.

Ejemplo:

```javascript
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(500).send(‘Ocurrió un error!’);
});
~~~

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
18
Q

¿Cómo definirías y usarías rutas en Express.js?

A

Las rutas se definen usando los métodos HTTP (como app.get, app.post). También se pueden agrupar con express.Router.

Ejemplo:

```javascript
app.get(‘/users’, (req, res) => {
res.send(‘Lista de usuarios’);
});

const router = express.Router();
router.post(‘/login’, (req, res) => {
res.send(‘Usuario autenticado’);
});
app.use(‘/auth’, router);
~~~

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
19
Q

¿Qué es CORS y cómo lo manejas en Express.js?

A

CORS (Cross-Origin Resource Sharing) permite que un servidor acepte solicitudes desde dominios diferentes al suyo. Se configura en Express.js usando el middleware cors.

Ejemplo:

```javascript
const cors = require(‘cors’);
app.use(cors());
~~~

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
20
Q

¿Cómo proteges una API en Express.js?

A

Helmet
(Helmet es una colección de middleware que ayuda a proteger tu aplicación configurando varios encabezados HTTP de seguridad)

```javascript:app.js
const helmet = require(‘helmet’);
app.use(helmet());

// Si solo quieres deshabilitar X-Powered-By
app.disable(‘x-powered-by’);
~~~

TLS/HTTPS
(Transport Layer Security cifra los datos entre el cliente y servidor, protegiendo contra ataques man-in-the-middle)

```javascript:app.js
const https = require(‘https’);
const fs = require(‘fs’);

const options = {
key: fs.readFileSync(‘path/to/key.pem’),
cert: fs.readFileSync(‘path/to/cert.pem’)
};

https.createServer(options, app).listen(443);
~~~

  1. Gestión de Cookies Seguras
    (Las cookies deben configurarse con opciones de seguridad apropiadas para prevenir ataques XSS y CSRF)

```javascript:app.js
const session = require(‘express-session’);

app.use(session({
name: ‘sessionId’, // Evitar el nombre por defecto ‘connect.sid’
secret: ‘tu_secreto_muy_seguro’,
cookie: {
secure: true, // Solo HTTPS
httpOnly: true, // No accesible via JavaScript
domain: ‘tudominio.com’,
path: ‘/’,
expires: new Date(Date.now() + 60 * 60 * 1000) // 1 hora
}
}));
~~~

  1. Rate Limiting
    (Previene ataques de fuerza bruta limitando el número de solicitudes)

```javascript:app.js
const rateLimit = require(‘express-rate-limit’);

const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutos
max: 100, // límite de 100 peticiones por ventana
message: ‘Demasiadas peticiones desde esta IP, intente nuevamente más tarde’
});

app.use(‘/api/’, limiter);
~~~

  1. Validación de Entrada
    (Previene inyecciones y ataques XSS validando la entrada del usuario)

```javascript:routes/api.js
const { body, validationResult } = require(‘express-validator’);

app.post(‘/api/user’, [
body(‘email’).isEmail(),
body(‘password’).isLength({ min: 8 }),
], (req, res) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() });
}
// Procesar la petición
});
~~~

  1. CORS Configuración
    (Cross-Origin Resource Sharing controla qué dominios pueden acceder a tu API)

```javascript:app.js
const cors = require(‘cors’);

// Configuración básica
app.use(cors());

// Configuración específica
const corsOptions = {
origin: ‘https://tudominio.com’,
methods: [‘GET’, ‘POST’],
allowedHeaders: [‘Content-Type’, ‘Authorization’],
credentials: true
};

app.use(cors(corsOptions));
~~~

  1. Prevención de Ataques SQL
    (Usar consultas parametrizadas para prevenir inyección SQL)

```javascript:db/queries.js
const { Pool } = require(‘pg’);
const pool = new Pool();

// ❌ MAL (vulnerable a inyección SQL)
const badQuery = SELECT * FROM users WHERE email = '${userInput}';

// ✅ BIEN (consulta parametrizada)
const goodQuery = {
text: ‘SELECT * FROM users WHERE email = $1’,
values: [userInput]
};

await pool.query(goodQuery);
~~~

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
21
Q

¿Cómo manejarías el almacenamiento de variables de entorno en Node.js?

A

Usando la biblioteca dotenv para cargar variables de un archivo .env al objeto process.env.

Ejemplo:

```javascript
require(‘dotenv’).config();
console.log(process.env.DB_HOST);
~~~

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
22
Q

¿Cómo implementas autenticación con JWT en Express.js?

A
  1. Generar un token al iniciar sesión.
  2. Verificar el token en rutas protegidas.

Ejemplo:

```javascript
const jwt = require(‘jsonwebtoken’);

// Generar un token
app.post(‘/login’, (req, res) => {
const user = { id: 1, username: ‘usuario’ };
const token = jwt.sign(user, ‘clave_secreta’);
res.json({ token });
});

// Middleware para verificar el token
function verificarToken(req, res, next) {
const token = req.headers[‘authorization’];
if (!token) return res.status(403).send(‘Token requerido’);

jwt.verify(token, ‘clave_secreta’, (err, user) => {
if (err) return res.status(403).send(‘Token inválido’);
req.user = user;
next();
});
}

app.get(‘/protegido’, verificarToken, (req, res) => {
res.send(‘Ruta protegida’);
});
~~~

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
23
Q

¿Qué son los Streams en Node.js y cómo se relacionan con Express.js?

A

Los streams en Node.js son flujos de datos que pueden ser leídos o escritos de manera secuencial. En Express.js, se usan para manejar respuestas eficientes, como enviar archivos grandes al cliente.

Ejemplo:

javascript
Copy code
app.get(‘/archivo’, (req, res) => {
const stream = fs.createReadStream(‘archivo_grande.txt’);
stream.pipe(res);
});

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
24
Q

¿Cómo manejarías grandes volúmenes de datos en una API construida con Express.js?

A
  1. Paginación: Dividir los resultados en porciones manejables.
  2. Streams: Para procesar datos en partes.
  3. Cacheo: Usar Redis o memoria para almacenar resultados frecuentes.
  4. Compresión: Usar compression para reducir el tamaño de las respuestas.

Ejemplo de compresión:

```javascript
const compression = require(‘compression’);
app.use(compression());
~~~

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
25
¿Qué es la clusterización en Node.js y cómo puede mejorar el rendimiento de Express.js?
La clusterización permite que Node.js utilice múltiples núcleos del procesador creando múltiples instancias del Event Loop, balanceando la carga entre procesos. Se implementa con el módulo `cluster`. **Ejemplo:** ```javascript const cluster = require('cluster'); const os = require('os'); if (cluster.isMaster) { const numCPUs = os.cpus().length; for (let i = 0; i < numCPUs; i++) { cluster.fork(); } } else { app.listen(3000, () => console.log('Servidor corriendo en el puerto 3000')); } ```
26
¿Cómo funciona el proceso de event loop cuando hay un código que usa Promises y `async/await`?
El `event loop` gestiona las promesas en la cola de microtareas. 1. Las promesas resueltas se mueven a la cola de microtareas (Microtasks Queue). 2. Antes de ejecutar tareas en la cola de tareas (Macrotasks Queue, como `setTimeout`), el `event loop` procesa primero todas las microtareas pendientes. 3. El uso de `async/await` encapsula la lógica asíncrona en promesas, pero no bloquea el hilo principal. **Ejemplo:** ```javascript console.log('Inicio'); setTimeout(() => console.log('Timeout'), 0); Promise.resolve().then(() => console.log('Promesa resuelta')); console.log('Fin'); ``` **Orden de ejecución:** `Inicio`, `Fin`, `Promesa resuelta`, `Timeout`.
27
¿Cómo manejarías una aplicación con alto tráfico usando Express.js?
1. **Clusterización:** Usar el módulo `cluster` para aprovechar múltiples núcleos. 2. **Cacheo:** Implementar estrategias de cacheo con Redis o Memcached. 3. **Middleware optimizados:** Minimizar el uso de middleware no esencial. 4. **Compresión:** Usar `compression` para reducir el tamaño de las respuestas. 5. **Rate Limiting:** Implementar límites de solicitudes por cliente. 6. **Carga estática desde CDN:** Servir archivos estáticos desde una red de distribución de contenido.
28
¿Qué son las `closures` en JavaScript y cómo las usarías?
Una closure es una función que recuerda el entorno en el que fue creada, incluso después de que dicho entorno haya finalizado. **Ejemplo:** ```javascript function crearContador() { let contador = 0; return () => { contador++; return contador; }; } const contador = crearContador(); console.log(contador()); // 1 console.log(contador()); // 2 ```
29
¿Cómo manejarías excepciones no capturadas en una aplicación de Node.js?
Usando `process.on('uncaughtException')` o `process.on('unhandledRejection')` para manejar errores globales. **Ejemplo:** ```javascript process.on('uncaughtException', (err) => { console.error('Excepción no capturada:', err); }); throw new Error('Error crítico'); ```
30
¿Qué diferencia hay entre `call`, `apply` y `bind` en JavaScript?
1. **`call`:** Llama a una función con un contexto explícito y argumentos individuales. 2. **`apply`:** Similar a `call`, pero los argumentos se pasan como un array. 3. **`bind`:** Devuelve una nueva función con un contexto explícito y argumentos predefinidos. **Ejemplo:** ```javascript function saludar(saludo, nombre) { console.log(`${saludo}, ${nombre}`); } saludar.call(null, 'Hola', 'Juan'); // Hola, Juan saludar.apply(null, ['Hola', 'Juan']); // Hola, Juan const saludarJuan = saludar.bind(null, 'Hola', 'Juan'); saludarJuan(); // Hola, Juan ```
31
¿Qué es el módulo fs en Node.js?
fs es un módulo nativo de Node.js que permite interactuar con el sistema de archivos, proporcionando métodos para leer, escribir, borrar y modificar archivos y directorios.
32
¿Qué es MongoDB?
MongoDB es una base de datos NoSQL orientada a documentos que almacena datos en formato BSON (JSON binario), ideal para datos no estructurados o semi-estructurados.
33
¿Qué es un documento en MongoDB?
Un documento es una unidad de almacenamiento en MongoDB, similar a una fila en una base de datos relacional, pero en formato JSON.
34
¿Qué es una colección en MongoDB?
Una colección es un grupo de documentos relacionados, similar a una tabla en bases de datos relacionales.
35
Explica cómo funciona el *hoisting* en JavaScript y qué diferencias existen entre las declaraciones con `var`, `let` y `const`
El *hoisting* es un comportamiento de JavaScript donde las declaraciones de variables y funciones se "mueven" al principio de su ámbito (global o de función) durante la fase de compilación, antes de la ejecución del código. - **`var`**: Se *hoistea* al principio de su ámbito y se inicializa con `undefined`. Esto puede llevar a problemas si se accede a la variable antes de su declaración. - **`let` y `const`**: También se *hoistean*, pero quedan en un estado llamado *Temporal Dead Zone (TDZ)* desde el inicio del bloque hasta que se ejecuta su declaración. Esto significa que intentar acceder a estas variables antes de su declaración generará un error de referencia (`ReferenceError`). Además, `const` requiere que la variable se inicialice al momento de declararse y no permite reasignaciones, mientras que `let` sí lo permite.
36
Describe las diferencias entre *deep copy* y *shallow copy* en JavaScript. Proporciona ejemplos de cada caso.
- Una *shallow copy* copia las referencias de los objetos internos en lugar de duplicar los datos subyacentes. Esto significa que cambios en los objetos anidados se reflejan en ambas copias. Por ejemplo: ```javascript const original = { a: 1, b: { c: 2 } }; const shallowCopy = { ...original }; shallowCopy.b.c = 42; // Cambia también original.b.c ``` - Una *deep copy* crea una copia completamente independiente, incluyendo los objetos anidados. Esto puede hacerse con técnicas como `JSON.parse(JSON.stringify(obj))` (aunque tiene limitaciones) o librerías como `Lodash`. Ejemplo: ```javascript const original = { a: 1, b: { c: 2 } }; const deepCopy = JSON.parse(JSON.stringify(original)); deepCopy.b.c = 42; // No afecta a original.b.c ```
37
Explica la diferencia entre el modelo monohilo de Node.js y los modelos de concurrencia tradicionales como los basados en hilos múltiples.
Node.js utiliza un modelo monohilo basado en eventos, lo que significa que todas las operaciones se ejecutan en un único hilo de JavaScript. Sin embargo, para manejar tareas costosas como operaciones de I/O, utiliza un *thread pool* en segundo plano (manejado por la librería `libuv`). En contraste, en modelos basados en hilos múltiples, como en Java o C++, múltiples hilos pueden ejecutarse simultáneamente, cada uno con su propia pila de ejecución. Ventajas de Node.js: - Bajo consumo de memoria debido al uso de un solo hilo para la lógica principal. - Ideal para aplicaciones con operaciones intensivas en I/O. Desventajas: - No es adecuado para tareas CPU-intensivas, ya que pueden bloquear el event loop.
38
¿Cómo funcionan los módulos en Node.js y qué diferencia hay entre `require` y `import`?
Node.js soporta dos sistemas de módulos: - **CommonJS (`require`)**: Es el sistema de módulos predeterminado en Node.js. Los módulos se cargan de forma síncrona, y las dependencias se resuelven al momento de ejecutarse. Ejemplo: ```javascript const fs = require('fs'); ``` - **ESM (`import/export`)**: Es el estándar de JavaScript moderno. Se soporta en Node.js desde la versión 12 y permite carga de módulos asíncrona. Requiere que el archivo tenga la extensión `.mjs` o que el archivo `package.json` tenga `"type": "module"`. Ejemplo: ```javascript import fs from 'fs'; ``` Diferencias clave: - `require` es síncrono, mientras que `import` permite manejo asíncrono. - `import` tiene soporte nativo para `tree-shaking` (eliminación de código no utilizado).
39
¿Qué es el patrón MVC y cómo se aplica en Express.js?
El patrón MVC (Modelo-Vista-Controlador) es una arquitectura de software que separa una aplicación en tres componentes principales: - **Modelo**: Maneja la lógica de datos y comunicación con la base de datos. - **Vista**: Representa la interfaz de usuario. - **Controlador**: Actúa como intermediario entre el modelo y la vista, manejando las solicitudes del usuario y actualizando el modelo o la vista según sea necesario. En Express.js, este patrón puede implementarse organizando el código en controladores que gestionen las rutas, modelos para interactuar con la base de datos, y vistas (si se utiliza un motor como EJS o Pug). Ejemplo: ```javascript const express = require('express'); const app = express(); const UserController = require('./controllers/UserController'); app.get('/users', UserController.listUsers); ```
40
Explica cómo funcionan las operaciones de escritura con transacciones en MongoDB y cuándo utilizarlas.
Las transacciones en MongoDB permiten realizar múltiples operaciones de lectura y escritura en documentos dentro de varias colecciones como una única operación atómica. Esto es útil para garantizar consistencia en operaciones críticas como transferencias de dinero. Para usar transacciones, debes tener una base de datos en clúster con réplica configurada. Ejemplo: ```javascript const session = client.startSession(); try { session.startTransaction(); await collection1.insertOne({ key: 'value' }, { session }); await collection2.updateOne({ key: 'value' }, { $set: { key: 'newValue' } }, { session }); await session.commitTransaction(); } catch (err) { await session.abortTransaction(); } finally { session.endSession(); } ```
41
¿Qué son los *aggregation pipelines* en MongoDB y cómo funcionan?
Los *aggregation pipelines* son una herramienta poderosa en MongoDB para procesar y transformar documentos en una colección. Funcionan aplicando una serie de etapas, donde el resultado de cada etapa se convierte en la entrada de la siguiente. Ejemplo de un pipeline básico: ```javascript db.orders.aggregate([ { $match: { status: "shipped" } }, // Filtra documentos { $group: { _id: "$customerId", total: { $sum: "$amount" } } }, // Agrupa por cliente y suma { $sort: { total: -1 } } // Ordena por total ]); ```
42
Explica las propiedades ACID y por qué son importantes en los sistemas de bases de datos.
Las propiedades ACID garantizan que las transacciones en bases de datos sean confiables y consistentes: - **Atomicidad (A):** Todas las operaciones en una transacción se completan o ninguna se ejecuta. - **Consistencia (C):** Después de una transacción, la base de datos debe estar en un estado válido. - **Aislamiento (I):** Las transacciones concurrentes no interfieren entre sí. - **Durabilidad (D):** Los cambios confirmados son permanentes incluso en caso de fallos. Estas propiedades son esenciales para sistemas críticos como bancos, donde la pérdida o corrupción de datos podría tener graves consecuencias.
43
Explica el concepto de sharding en bases de datos y cuándo usarlo.
*Sharding* divide una base de datos grande en fragmentos más pequeños y manejables distribuidos entre diferentes servidores. Ventajas: - Escalabilidad horizontal. - Mejor rendimiento en grandes volúmenes de datos. Usar *sharding* es ideal cuando la base de datos experimenta una alta carga de escritura o consulta que no puede manejarse eficientemente con un solo nodo.
44
¿Qué problemas pueden surgir al usar funciones asíncronas dentro de bucles `for` y cómo solucionarlos?
En un bucle `for`, las funciones asíncronas no esperan automáticamente su resolución, lo que puede llevar a resultados inesperados debido a la naturaleza concurrente de las promesas. Ejemplo de problema: ```javascript for (let i = 0; i < 5; i++) { setTimeout(() => console.log(i), 1000); } ``` Esto imprimirá `5` cinco veces debido al alcance del cierre en los bucles `var`. **Solución:** Utilizar `async/await` con `for...of` o crear una función que encapsule cada iteración: ```javascript for (const item of items) { await processItem(item); } ```
45
Explica qué es el *currying* en JavaScript y proporciona un caso de uso.
El *currying* es una técnica de programación funcional donde una función de múltiples argumentos se descompone en una secuencia de funciones que toman un solo argumento. Ejemplo: ```javascript const multiply = a => b => c => a * b * c; const multiplyBy2 = multiply(2); multiplyBy2(3)(4); // 24 ``` Caso de uso: Permite crear funciones reutilizables con parámetros parcialmente aplicados, mejorando la composición de funciones y el código modular.
46
¿Cómo maneja Node.js la concurrencia si usa un solo hilo para ejecutar JavaScript?
Node.js utiliza un modelo de concurrencia basado en eventos, manejado por el *event loop*. Cuando se realizan operaciones de I/O, estas se delegan al *thread pool* de `libuv` o al sistema operativo. El hilo principal no se bloquea, lo que permite manejar múltiples solicitudes simultáneamente. - **Event loop:** Coordina la ejecución de callbacks y promesas. - **Thread pool:** Utilizado para tareas intensivas de I/O, como operaciones de archivos o consultas a bases de datos. Este modelo permite una alta escalabilidad, pero puede ser ineficiente para tareas intensivas en CPU.
47
Explica cómo funcionan los *streams* de lectura y escritura en Node.js y su ventaja frente a otros métodos de procesamiento de datos.
Los *streams* son abstracciones para trabajar con flujos de datos, permitiendo leer o escribir datos de forma fragmentada en lugar de cargar todo en memoria. - **Tipos de streams:** - *Readable*: Para leer datos (ej., `fs.createReadStream`). - *Writable*: Para escribir datos (ej., `fs.createWriteStream`). - *Duplex*: Combina ambos, lectura y escritura (ej., sockets). - *Transform*: Permite modificar datos durante el flujo (ej., compresión). Ventajas: - Procesamiento eficiente de grandes volúmenes de datos. - Reduce el uso de memoria. Ejemplo: ```javascript const fs = require('fs'); const readStream = fs.createReadStream('input.txt'); const writeStream = fs.createWriteStream('output.txt'); readStream.pipe(writeStream); ```
48
¿Cómo se implementa la autenticación basada en tokens (JWT) en una aplicación Express?
La autenticación basada en tokens JWT consiste en generar un token único que se envía al cliente tras un inicio de sesión exitoso. El cliente incluye este token en el encabezado de futuras solicitudes. Ejemplo básico: - Generar el token: ```javascript const jwt = require('jsonwebtoken'); const token = jwt.sign({ userId: user.id }, 'secret', { expiresIn: '1h' }); ``` - Verificar el token en middleware: ```javascript app.use((req, res, next) => { const token = req.headers['authorization']; if (!token) return res.status(403).send('Token required'); try { const decoded = jwt.verify(token, 'secret'); req.user = decoded; next(); } catch (err) { res.status(401).send('Invalid token'); } }); ```
49
¿Qué es la Primera Forma Normal (1NF) y cómo se aplica?
1NF exige que: - Los datos estén organizados en tablas con filas y columnas. - Cada columna contenga valores atómicos (sin listas ni conjuntos). - Cada fila sea única (clave primaria). **Ejemplo:** | ID | Nombre | Teléfonos | |------|----------|-------------------| | 1 | Ana | 555-123, 555-456 | | 2 | Pedro | 555-789 | No cumple con 1NF porque `Teléfonos` contiene múltiples valores. **Solución:** | ID | Nombre | Teléfono | |------|----------|-------------| | 1 | Ana | 555-123 | | 1 | Ana | 555-456 | | 2 | Pedro | 555-789 |
50
¿Qué es la Segunda Forma Normal (2NF) y cómo se aplica?
2NF exige que: - Cumpla con 1NF. - Todas las columnas no clave dependan completamente de la clave primaria (sin dependencias parciales). **Ejemplo:** | ID Pedido | ID Producto | Nombre Producto | Cantidad | Cliente | Dirección | |-----------|-------------|-----------------|----------|---------|-----------| | 1 | 101 | Lápiz | 10 | Ana | Calle 123 | | 1 | 102 | Cuaderno | 5 | Ana | Calle 123 | `Cliente` y `Dirección` dependen parcialmente de `ID Pedido`, no de la combinación `ID Pedido, ID Producto`. **Solución:** Dividir en dos tablas: 1. Pedidos: | ID Pedido | ID Producto | Cantidad | |-----------|-------------|----------| | 1 | 101 | 10 | | 1 | 102 | 5 | 2. Clientes: | ID Pedido | Cliente | Dirección | |-----------|---------|-----------| | 1 | Ana | Calle 123 |
51
¿Qué es la Tercera Forma Normal (3NF) y cómo se aplica?
3NF exige que: - Cumpla con 2NF. - Las columnas no clave dependan únicamente de la clave primaria (sin dependencias transitivas). **Ejemplo:** | ID Pedido | Cliente | ID Ciudad | Ciudad | Código Postal | |-----------|-----------|-----------|--------------|---------------| | 1 | Ana | 11 | Madrid | 28001 | | 2 | Pedro | 12 | Barcelona | 08001 | `Ciudad` y `Código Postal` dependen de `ID Ciudad`, no directamente de `ID Pedido`. **Solución:** Dividir en dos tablas: 1. Pedidos: | ID Pedido | Cliente | ID Ciudad | |-----------|---------|-----------| | 1 | Ana | 11 | | 2 | Pedro | 12 | 2. Ciudades: | ID Ciudad | Ciudad | Código Postal | |-----------|------------|---------------| | 11 | Madrid | 28001 | | 12 | Barcelona | 08001 |
52
¿Qué es la Forma Normal de Boyce-Codd (BCNF) y cómo se aplica?
BCNF exige que: - Cumpla con 3NF. - Cada determinante (atributo que determina otro) sea una clave candidata. **Ejemplo:** | Profesor | Materia | Aula | |-----------|-------------|---------| | Juan | Matemáticas | 101 | | Pedro | Física | 102 | | Juan | Física | 101 | `Profesor` determina `Aula`, pero no es clave candidata. **Solución:** Dividir en dos tablas: 1. Profesor-Aula: | Profesor | Aula | |-----------|-------| | Juan | 101 | | Pedro | 102 | 2. Profesor-Materia: | Profesor | Materia | |-----------|-------------| | Juan | Matemáticas | | Juan | Física | | Pedro | Física |
53
¿Qué es el Garbage Collector en JavaScript y cómo funciona?
El **garbage collector** en JavaScript es responsable de gestionar la memoria automáticamente, liberando objetos que ya no son accesibles. El proceso principal utilizado por el GC en motores como V8 de Chrome se basa en dos estrategias: 1. **Mark-and-sweep**: El GC marca los objetos que están en uso (referenciados) y luego elimina aquellos que no tienen ninguna referencia, liberando memoria. 2. **Generational garbage collection**: Los objetos se organizan en generaciones (jóvenes y viejos). Los objetos jóvenes se recogen con mayor frecuencia, mientras que los objetos viejos, que han sobrevivido a varias recolecciones, se recolectan menos a menudo. El proceso de GC ocurre en segundo plano y no se puede controlar directamente, pero es importante escribir código que minimice las referencias perdidas o cíclicas para evitar problemas de rendimiento.
54
¿Qué es el "Heap" en el contexto del Garbage Collector en JavaScript?
El **Heap** es la zona de memoria utilizada para almacenar objetos, funciones y variables dinámicas en JavaScript. El Garbage Collector opera principalmente en el Heap, ya que es donde los objetos se asignan dinámicamente durante la ejecución del programa. Los valores primitivos (como números y cadenas) suelen almacenarse en la **Stack** (pila), que es una estructura de memoria más sencilla. Cuando un objeto se crea en el Heap, se mantiene allí hasta que ya no haya ninguna referencia a él, momento en el cual el GC lo identifica como "basura" y lo elimina para liberar espacio.
55
¿Qué son las "Fugas de Memoria" y cómo se relacionan con el Garbage Collector en JavaScript?
Una **fuga de memoria** ocurre cuando un programa mantiene referencias a objetos que ya no son necesarios, impidiendo que el Garbage Collector los elimine y liberé la memoria. Esto puede suceder por varias razones: - **Referencias no eliminadas:** Si un objeto ya no es necesario pero aún se mantiene una referencia hacia él (por ejemplo, dentro de una variable global o un objeto), el GC no lo eliminará, lo que genera una fuga de memoria. - **Ciclos de referencia:** Si dos o más objetos se refieren entre sí pero no son accesibles desde el entorno global, el Garbage Collector puede no detectar esta referencia circular y dejar los objetos en memoria. El manejo adecuado de las referencias y la eliminación explícita de las mismas (por ejemplo, con `null` o `delete`) puede ayudar a evitar fugas de memoria.
56
¿Qué es la Programación Orientada a Objetos (OOP) en JavaScript?
La Programación Orientada a Objetos (OOP) es un paradigma de programación basado en objetos. En JavaScript, esto se implementa utilizando clases y objetos. Los principios clave de la OOP son: 1. **Encapsulamiento**: Agrupar datos y funciones que operan sobre esos datos en una sola unidad (objeto). 2. **Herencia**: Permite a una clase heredar características y comportamientos de otra. 3. **Polimorfismo**: Permite que diferentes clases implementen el mismo método de diferentes formas. 4. **Abstracción**: Ocultar la complejidad interna y exponer solo lo necesario. En JavaScript, las clases se definen usando la palabra clave `class`.
57
¿Qué es la herencia en JavaScript y cómo se implementa?
La herencia en JavaScript permite que una clase hija herede propiedades y métodos de una clase padre. Se implementa usando `extends` y `super()` para invocar el constructor de la clase padre. Ejemplo: ```javascript class Animal { constructor(nombre) { this.nombre = nombre; } saludar() { console.log(`Hola, soy un ${this.nombre}`); } } class Perro extends Animal { constructor(nombre, raza) { super(nombre); this.raza = raza; } ladrar() { console.log(`${this.nombre} dice: ¡Guau!`); } } const miPerro = new Perro('Rex', 'Labrador'); miPerro.saludar(); // "Hola, soy un Rex" miPerro.ladrar(); // "Rex dice: ¡Guau!" ```
58
¿Cómo funcionan los **event emitters** en Node.js y cómo se usan para manejar eventos personalizados?
En Node.js, un **event emitter** es un patrón de diseño basado en el manejo de eventos. Utiliza el módulo `events` y permite que los objetos emitan eventos y que otros objetos escuchen esos eventos para realizar acciones cuando se disparan. El objeto `EventEmitter` proporciona métodos como `.on()`, `.emit()` y `.removeListener()`. Ejemplo: ```javascript const EventEmitter = require('events'); class MiEmisor extends EventEmitter {} const emisor = new MiEmisor(); // Listener que escucha el evento 'saludo' emisor.on('saludo', (nombre) => { console.log(`¡Hola, ${nombre}!`); }); // Emisión del evento 'saludo' emisor.emit('saludo', 'Juan'); ```
59
¿Qué son los **Web Workers** y cómo se utilizan en Node.js?
Los **Web Workers** permiten la ejecución de JavaScript en un hilo separado del hilo principal (del "main thread"), lo que permite realizar operaciones intensivas sin bloquear la interfaz de usuario o el flujo principal de la aplicación. En **Node.js**, se pueden usar a través del módulo `worker_threads`, lo que permite crear subprocesos para tareas que requieren computación pesada, como el procesamiento de grandes cantidades de datos o cálculos complejos. Ejemplo: ```javascript const { Worker, isMainThread, parentPort } = require('worker_threads'); if (isMainThread) { const worker = new Worker(__filename); worker.on('message', (result) => console.log(result)); worker.postMessage('start'); } else { parentPort.on('message', (message) => { if (message === 'start') { parentPort.postMessage('Trabajo completado'); } }); } ```
60
¿Cómo manejarías la asincronía en Node.js con promesas?
En Node.js, usaría `promesas` para manejar la asincronía. Una promesa representa la eventual finalización (o fracaso) de una operación asincrónica. Para crear una promesa, usaría el constructor `new Promise()`. Para esperar el resultado, usaría `then()` y `catch()`. Ejemplo: ```javascript function obtenerDatos() { return new Promise((resolve, reject) => { // Simulación de operación asincrónica setTimeout(() => { resolve('Datos obtenidos'); }, 1000); }); } obtenerDatos() .then(respuesta => console.log(respuesta)) .catch(error => console.error(error)); ``` También puedo usar `async/await` para escribir un código más limpio: ```javascript async function obtenerDatos() { const respuesta = await obtenerDatos(); console.log(respuesta); } ```
61
¿Cómo estructurarías una ruta básica en Express.js?
Para estructurar una ruta básica en Express.js, usaría el siguiente enfoque: ```javascript const express = require('express'); const app = express(); // Ruta GET para la raíz app.get('/', (req, res) => { res.send('¡Hola Mundo!'); }); // Iniciar servidor en el puerto 3000 app.listen(3000, () => { console.log('Servidor en http://localhost:3000'); }); ``` Este ejemplo muestra cómo crear un servidor Express y manejar una solicitud GET en la ruta raíz. Utilizo `res.send()` para enviar una respuesta al cliente.
62
¿Cómo manejarías múltiples rutas con Express?
Para manejar múltiples rutas en Express, crearías diferentes manejadores para cada tipo de solicitud (GET, POST, PUT, DELETE, etc.) en las rutas que desees. Ejemplo: ```javascript const express = require('express'); const app = express(); // Ruta GET para obtener todos los usuarios app.get('/usuarios', (req, res) => { res.send('Lista de usuarios'); }); // Ruta POST para crear un nuevo usuario app.post('/usuarios', (req, res) => { res.send('Usuario creado'); }); // Ruta PUT para actualizar un usuario app.put('/usuarios/:id', (req, res) => { res.send(`Usuario con ID ${req.params.id} actualizado`); }); // Ruta DELETE para eliminar un usuario app.delete('/usuarios/:id', (req, res) => { res.send(`Usuario con ID ${req.params.id} eliminado`); }); app.listen(3000, () => { console.log('Servidor funcionando'); }); ``` En este ejemplo, se manejan varias rutas con distintos métodos HTTP.
63
¿Cómo conectarías una aplicación Node.js con MongoDB?
Para conectar Node.js con MongoDB, usaría el paquete `mongoose` para facilitar las interacciones con la base de datos. Primero, instalaría Mongoose: ```bash npm install mongoose ``` Luego, en mi archivo de configuración, establecería la conexión: ```javascript const mongoose = require('mongoose'); mongoose.connect('mongodb://localhost:27017/miDB', { useNewUrlParser: true, useUnifiedTopology: true }) .then(() => console.log('Conexión exitosa a MongoDB')) .catch(error => console.error('Error al conectar a MongoDB:', error)); ```
64
¿Cómo manejarías la validación de datos en MongoDB con Mongoose?
Usaría `mongoose` para definir un esquema con validaciones. Ejemplo: ```javascript const mongoose = require('mongoose'); const usuarioSchema = new mongoose.Schema({ nombre: { type: String, required: true, // Requiere que el nombre esté presente }, edad: { type: Number, min: 18, // Edad mínima de 18 max: 100, // Edad máxima de 100 }, }); const Usuario = mongoose.model('Usuario', usuarioSchema); // Crear un nuevo usuario const nuevoUsuario = new Usuario({ nombre: 'Juan', edad: 25, }); nuevoUsuario.save() .then(() => console.log('Usuario guardado correctamente')) .catch(error => console.error('Error al guardar usuario:', error)); ``` En este caso, se validan el nombre y la edad al crear un nuevo documento.
65
¿Cómo harías un middleware en Express para la autenticación de un token JWT?
Crear un middleware en Express para verificar un token JWT implicaría lo siguiente: ```javascript const jwt = require('jsonwebtoken'); const verificarToken = (req, res, next) => { const token = req.headers['authorization']; if (!token) { return res.status(403).send('Acceso denegado'); } jwt.verify(token, 'miSecreto', (err, decoded) => { if (err) { return res.status(401).send('Token no válido'); } req.user = decoded; next(); }); }; // Usar el middleware en una ruta protegida app.get('/perfil', verificarToken, (req, res) => { res.send(`Bienvenido ${req.user.nombre}`); }); ``` Este middleware verifica la validez del token JWT y lo decodifica antes de permitir el acceso a las rutas protegidas.
66
¿Cómo manejarías errores globalmente en una aplicación Express?
Para manejar errores globalmente, usaría un middleware de manejo de errores en Express: ```javascript app.use((err, req, res, next) => { console.error(err.stack); res.status(500).send('Algo salió mal'); }); ``` Este middleware captura cualquier error que ocurra en las rutas y responde con un mensaje de error general. Puede usarse al final de todas las rutas.
67
¿Cómo harías una consulta a MongoDB usando Mongoose para encontrar todos los documentos de una colección?
Utilizando Mongoose, para encontrar todos los documentos de una colección usaría el método `find()`: ```javascript Usuario.find() .then(usuarios => console.log(usuarios)) .catch(error => console.error('Error al obtener usuarios:', error)); ```
68
¿Cómo manejarías la concurrencia en Node.js cuando necesitas realizar múltiples tareas asincrónicas que dependen de un orden específico, pero algunas pueden fallar?
Para manejar la concurrencia, usaría `Promise.all()` cuando las tareas no dependan entre sí, pero si algunas pueden fallar y otras deben continuar, usaría `Promise.allSettled()` o `Promise.race()`. Sin embargo, si el orden es importante, podría usar `async/await` con un control adecuado de errores. Ejemplo: ```javascript async function procesarTareas() { try { const resultado1 = await tarea1(); const resultado2 = await tarea2(); const resultado3 = await tarea3(); return [resultado1, resultado2, resultado3]; } catch (error) { console.error('Error en una de las tareas:', error); } } procesarTareas(); ``` Si se necesitan manejar múltiples tareas en paralelo, con `Promise.allSettled()` se puede esperar que todas las promesas se resuelvan, pero sin que una fallida interrumpa las demás: ```javascript const resultados = await Promise.allSettled([tarea1(), tarea2(), tarea3()]); resultados.forEach(resultado => { if (resultado.status === 'fulfilled') { console.log('Resultado:', resultado.value); } else { console.error('Error:', resultado.reason); } }); ```
69
¿Cómo garantizarías la idempotencia de una API REST en Express, asegurando que las solicitudes repetidas no causen efectos secundarios?
La idempotencia garantiza que repetir una misma solicitud no cause efectos secundarios, independientemente de cuántas veces se ejecute. En una API REST, aseguraría la idempotencia principalmente en las operaciones PUT y DELETE. Para lograrlo, implementaría un control con algún identificador único (como un ID de transacción) en el encabezado o en el cuerpo de la solicitud. Ejemplo para una operación de creación: ```javascript app.post('/usuarios', async (req, res) => { const { id, nombre } = req.body; // Comprobamos si el usuario ya existe por su ID const usuarioExistente = await Usuario.findOne({ id }); if (usuarioExistente) { return res.status(409).send('El usuario ya existe'); } const nuevoUsuario = new Usuario({ id, nombre }); await nuevoUsuario.save(); res.status(201).send(nuevoUsuario); }); ``` En este caso, si un cliente intenta crear un usuario con el mismo `id` más de una vez, la respuesta será un código 409 (conflicto) y no se duplicará la entrada.
70
¿Cómo manejarías la paginación en una API REST que consulta una gran cantidad de documentos en MongoDB?
Para manejar la paginación en MongoDB, usaría los parámetros `skip` y `limit` en la consulta. Esto permite controlar cuántos documentos se devuelven y desde qué documento se comienza a devolver. Un ejemplo básico sería: ```javascript app.get('/usuarios', async (req, res) => { const page = parseInt(req.query.page) || 1; const pageSize = parseInt(req.query.pageSize) || 10; const usuarios = await Usuario.find() .skip((page - 1) * pageSize) .limit(pageSize); const totalUsuarios = await Usuario.countDocuments(); const totalPages = Math.ceil(totalUsuarios / pageSize); res.json({ usuarios, totalPages, currentPage: page, totalUsuarios }); }); ``` Este ejemplo proporciona una paginación básica, calculando cuántas páginas existen en función del número total de usuarios.
71
¿Cómo manejarías la autenticación y autorización en una aplicación Node.js que usa JWT, pero también quieres revocar tokens de acceso?
Para manejar la revocación de tokens JWT, dado que los tokens no se almacenan en el servidor y no pueden invalidarse directamente, implementaría una lista negra (blacklist) de tokens o un sistema basado en la expiración. Podría almacenar los tokens revocados en una base de datos y verificar su validez en cada solicitud: ```javascript const blacklistedTokens = []; const verificarToken = (req, res, next) => { const token = req.headers['authorization']; if (blacklistedTokens.includes(token)) { return res.status(401).send('Token revocado'); } jwt.verify(token, 'miSecreto', (err, decoded) => { if (err) { return res.status(401).send('Token no válido'); } req.user = decoded; next(); }); }; // Función para revocar el token function revocarToken(token) { blacklistedTokens.push(token); } ``` En este enfoque, cuando un token es revocado, se agrega a la lista negra. Antes de procesar cada solicitud, se verifica que el token no esté en la lista negra.
72
¿Cómo optimizarías una consulta MongoDB que involucra grandes volúmenes de datos con relaciones complejas entre documentos?
Para optimizar una consulta en MongoDB con grandes volúmenes de datos y relaciones complejas, usaría varias estrategias, como: 1. **Indexación**: Asegurarme de que las propiedades más consultadas estén indexadas. MongoDB permite crear índices sobre campos específicos para mejorar la velocidad de las consultas. ```javascript UsuarioSchema.index({ nombre: 1 }); ``` 2. **Población (Populate)**: Si la consulta involucra relaciones entre colecciones, como un `referenciado` de un documento a otro, podría usar `populate()` para realizar una consulta eficiente: ```javascript const usuarios = await Usuario.find() .populate('roles') // Población de los datos relacionados .exec(); ``` 3. **Proyección**: Utilizaría proyección para devolver solo los campos necesarios y reducir el tamaño de la respuesta. ```javascript const usuarios = await Usuario.find({}, 'nombre edad').exec(); ``` 4. **Agregación**: Si las consultas requieren cálculos o transformaciones más complejas, usaría el marco de agregación de MongoDB para mejorar el rendimiento: ```javascript const resultados = await Usuario.aggregate([ { $match: { edad: { $gte: 18 } } }, { $group: { _id: "$edad", total: { $sum: 1 } } } ]); ```
73
¿Cómo optimizarías el rendimiento de una aplicación Express que maneja múltiples rutas y peticiones concurrentes de alta carga?
Para optimizar el rendimiento en una aplicación Express de alta carga, implementaría varias estrategias, como: 1. **Uso de Cluster**: Node.js puede aprovechar múltiples núcleos del sistema operativo mediante el módulo `cluster` para manejar más conexiones concurrentes. ```javascript const cluster = require('cluster'); const os = require('os'); const numCPUs = os.cpus().length; if (cluster.isMaster) { for (let i = 0; i < numCPUs; i++) { cluster.fork(); } } else { app.listen(3000, () => console.log('Servidor en ejecución')); } ``` 2. **Caché**: Implementaría un sistema de caché con Redis o similar para almacenar respuestas de rutas que no cambian frecuentemente y reducir la carga en la base de datos. 3. **Limitación de tasa (Rate Limiting)**: Usaría un middleware para limitar el número de solicitudes a las rutas y prevenir abusos. ```javascript const rateLimit = require('express-rate-limit'); const limiter = rateLimit({ windowMs: 15 * 60 * 1000, // 15 minutos max: 100 // Limita a 100 solicitudes por IP }); app.use(limiter); ``` 4. **Compresión**: Usaría `compression` para comprimir las respuestas y reducir el ancho de banda necesario para servir las respuestas. ```javascript const compression = require('compression'); app.use(compression()); ```
74
¿Cómo implementarías un sistema de caché en una aplicación Express para evitar consultas repetidas a la base de datos?
Para implementar un sistema de caché en Express, usaría una base de datos de caché como `Redis`. De esta forma, se pueden almacenar los resultados de las consultas más comunes y devolverlos rápidamente sin necesidad de consultar la base de datos cada vez. Ejemplo básico con `redis`: ```bash npm install redis ``` Configuración y uso en Express: ```javascript const express = require('express'); const redis = require('redis'); const client = redis.createClient(); const app = express(); // Conexión a Redis client.on('connect', () => { console.log('Conectado a Redis'); }); // Middleware de caché const cache = (req, res, next) => { const { id } = req.params; client.get(id, (err, data) => { if (data != null) { return res.json(JSON.parse(data)); // Si existe en caché, devolverlo } else { next(); // Si no existe en caché, continuar con la solicitud } }); }; // Ruta con caché app.get('/usuario/:id', cache, async (req, res) => { const usuario = await obtenerUsuarioDesdeBD(req.params.id); client.setex(req.params.id, 3600, JSON.stringify(usuario)); // Guardar en caché durante 1 hora res.json(usuario); }); app.listen(3000, () => { console.log('Servidor en http://localhost:3000'); }); ```
75
¿Cómo manejarías la seguridad de una API REST que expone endpoints sensibles, como la actualización de información de usuario o el cambio de contraseñas?
Para garantizar la seguridad en una API REST que expone endpoints sensibles, implementaría varias capas de protección: 1. **Autenticación con JWT**: Aseguraría que solo los usuarios autenticados puedan acceder a los endpoints sensibles usando JWT, y validaría el token en cada solicitud. ```javascript const jwt = require('jsonwebtoken'); const secretKey = 'miSecreto'; const verificarToken = (req, res, next) => { const token = req.headers['authorization']; if (!token) { return res.status(403).send('Acceso denegado'); } jwt.verify(token, secretKey, (err, decoded) => { if (err) { return res.status(401).send('Token no válido'); } req.user = decoded; next(); }); }; ``` 2. **Autorización**: Verificaría que el usuario tiene permisos para realizar la acción solicitada. Por ejemplo, solo permitiría que un usuario actualice su propia información: ```javascript app.put('/usuarios/:id', verificarToken, (req, res) => { if (req.user.id !== req.params.id) { return res.status(403).send('No autorizado'); } // Continuar con la actualización }); ``` 3. **Encriptación de contraseñas**: Utilizaría un algoritmo de hash como `bcrypt` para encriptar las contraseñas y garantizar que nunca se almacenen en texto claro en la base de datos. ```bash npm install bcrypt ``` Ejemplo de encriptación de contraseña: ```javascript const bcrypt = require('bcrypt'); const saltRounds = 10; bcrypt.hash('contraseña123', saltRounds, (err, hash) => { // Guardar el hash en la base de datos }); ``` 4. **Validación de entrada**: Validaría todas las entradas del usuario para prevenir ataques de inyección (como SQL o NoSQL injection) utilizando bibliotecas como `express-validator`.
76
¿Cómo configurarías headers HTTP para manejar CORS en una API Express?
Para habilitar CORS (Cross-Origin Resource Sharing) en una API Express, configuraría los headers adecuados para permitir solicitudes desde orígenes específicos. Usaría el middleware `cors` de Express, que simplifica la configuración de los headers CORS. Ejemplo: ```bash npm install cors ``` Configuración básica en Express: ```javascript const express = require('express'); const cors = require('cors'); const app = express(); // Permitir solicitudes de un origen específico app.use(cors({ origin: 'https://www.mi-aplicacion-frontend.com', methods: ['GET', 'POST'], allowedHeaders: ['Content-Type', 'Authorization'] })); app.get('/datos', (req, res) => { res.json({ mensaje: 'Datos enviados correctamente' }); }); app.listen(3000, () => { console.log('Servidor en http://localhost:3000'); }); ``` Este código configura CORS para permitir solo solicitudes desde un dominio específico y los métodos y headers definidos. Esto mejora la seguridad de la API.
77
¿Cómo utilizarías cookies en Express para mantener el estado de sesión de un usuario?
En Express, se pueden usar cookies para mantener el estado de sesión de un usuario, por ejemplo, para guardar un token de autenticación. Para gestionar las cookies de forma segura, usaría la librería `cookie-parser` y `express-session` para crear sesiones persistentes. Ejemplo: ```bash npm install cookie-parser express-session ``` Configuración básica con `express-session`: ```javascript const express = require('express'); const cookieParser = require('cookie-parser'); const session = require('express-session'); const app = express(); // Middleware para parsear cookies app.use(cookieParser()); // Configuración de sesión app.use(session({ secret: 'mi-secreto', resave: false, saveUninitialized: true, cookie: { secure: true, httpOnly: true, maxAge: 3600000 } // 1 hora })); app.get('/login', (req, res) => { // Guardar datos en la sesión req.session.user = { id: 1, nombre: 'Juan' }; res.send('Usuario autenticado'); }); app.get('/perfil', (req, res) => { if (req.session.user) { res.json(req.session.user); } else { res.status(401).send('No autenticado'); } }); app.listen(3000, () => { console.log('Servidor en http://localhost:3000'); }); ``` Aquí, se usa `express-session` para almacenar los datos del usuario en una cookie de sesión, lo que permite mantener el estado entre peticiones.
78
¿Cómo manejarías la seguridad de las cookies que contienen información sensible en una aplicación Express?
Para mejorar la seguridad de las cookies que contienen información sensible, usaría las siguientes prácticas: 1. **Establecer el atributo `HttpOnly`**: Esto evita que el acceso a la cookie sea posible desde JavaScript en el navegador, protegiéndola de ataques XSS. 2. **Establecer el atributo `Secure`**: Esto asegura que la cookie solo se transmita a través de conexiones HTTPS, evitando la exposición a ataques de intermediarios (MITM). 3. **Configurar un tiempo de expiración adecuado**: Limitar el tiempo de vida de la cookie para reducir la ventana de oportunidad en caso de que la cookie sea robada. 4. **Firmar cookies para prevenir la manipulación**: Usar cookies firmadas para asegurar que los datos no se alteren. Ejemplo de configuración de cookies seguras en Express: ```javascript const express = require('express'); const cookieParser = require('cookie-parser'); const app = express(); app.use(cookieParser()); app.get('/set-cookie', (req, res) => { res.cookie('authToken', 'valorDelToken', { httpOnly: true, // No accesible desde JavaScript secure: true, // Solo a través de HTTPS maxAge: 3600000, // 1 hora de expiración signed: true // Cookie firmada }); res.send('Cookie segura establecida'); }); app.get('/get-cookie', (req, res) => { const cookie = req.signedCookies.authToken; if (cookie) { res.send(`Token de autenticación: ${cookie}`); } else { res.status(400).send('Cookie no encontrada'); } }); app.listen(3000, () => { console.log('Servidor en http://localhost:3000'); }); ``` En este ejemplo, la cookie es accesible solo a través de HTTPS, es `HttpOnly` y está firmada, lo que refuerza la seguridad.
79
¿Cómo gestionarías la expiración de una cookie en Express y qué implicaciones tendría en la experiencia del usuario?
Para gestionar la expiración de una cookie en Express, se puede utilizar el parámetro `maxAge` en la configuración de la cookie. Esto establece el tiempo de vida de la cookie en milisegundos. Después de que la cookie haya expirado, el navegador la elimina automáticamente. Si se maneja correctamente, la expiración de cookies puede mejorar la seguridad, ya que limita el tiempo en que una cookie puede ser usada por un atacante en caso de que se obtenga de manera no autorizada. Sin embargo, es importante considerar la experiencia del usuario: si las cookies expiran muy rápido, los usuarios pueden tener que volver a iniciar sesión frecuentemente. Ejemplo de expiración de cookies en Express: ```javascript app.get('/set-cookie', (req, res) => { res.cookie('sessionID', '123456', { maxAge: 3600000 }); // 1 hora res.send('Cookie establecida'); }); app.get('/get-cookie', (req, res) => { const cookie = req.cookies.sessionID; if (cookie) { res.send(`Session ID: ${cookie}`); } else { res.send('Cookie expirada o no encontrada'); } }); ``` Si la cookie tiene un `maxAge` de 1 hora, el usuario será desconectado automáticamente después de una hora de inactividad.
80
¿Cómo manejarías la validación y decodificación de un JWT almacenado en una cookie en Express?
Para manejar la validación y decodificación de un JWT almacenado en una cookie, seguiría estos pasos: 1. **Leer el JWT desde la cookie**: Usaría el middleware `cookie-parser` para acceder a las cookies de la solicitud. 2. **Validar y decodificar el JWT**: Usaría una librería como `jsonwebtoken` para verificar y decodificar el token. Ejemplo: ```bash npm install cookie-parser jsonwebtoken ``` Código de validación y decodificación de JWT en Express: ```javascript const express = require('express'); const cookieParser = require('cookie-parser'); const jwt = require('jsonwebtoken'); const app = express(); const secretKey = 'mi-secreto'; // Middleware para parsear cookies app.use(cookieParser()); app.get('/verificar-token', (req, res) => { const token = req.cookies.authToken; if (!token) { return res.status(403).send('No token provided'); } jwt.verify(token, secretKey, (err, decoded) => { if (err) { return res.status(401).send('Token no válido'); } res.json({ usuario: decoded }); }); }); app.listen(3000, () => { console.log('Servidor en http://localhost:3000'); }); ``` Este código lee el token desde la cookie `authToken`, lo verifica usando `jsonwebtoken` y devuelve los datos decodificados del token si es válido.
81
**¿Qué es la inyección de dependencias y cuáles son sus principales beneficios en el desarrollo de software?**
La inyección de dependencias (DI) es un patrón de diseño que implementa el principio de Inversión de Control (IoC), donde las dependencias de un componente son proporcionadas externamente en lugar de ser creadas internamente. Sus principales beneficios son: 1. Desacoplamiento de componentes 2. Facilita el testing mediante mocks 3. Mayor flexibilidad y mantenibilidad 4. Reutilización de código 5. Mejor gestión del ciclo de vida de los objetos
82
**¿Cómo implementarías la inyección de dependencias en una aplicación Node.js que necesita diferentes configuraciones de base de datos para desarrollo y producción?**
La implementación ideal sería: 1. Crear una interfaz/clase base para la configuración de DB 2. Implementar configuraciones específicas 3. Usar un contenedor DI Ejemplo conceptual: ```javascript // DatabaseConfig interface/base class DatabaseConfig { getConnectionString() {} } // Implementaciones específicas class DevDatabaseConfig extends DatabaseConfig { getConnectionString() { return 'mongodb://localhost:27017/dev'; } } class ProdDatabaseConfig extends DatabaseConfig { getConnectionString() { return process.env.MONGODB_URI; } } // Contenedor DI class Container { constructor() { this.dependencies = new Map(); } register(key, implementation) { this.dependencies.set(key, implementation); } get(key) { return this.dependencies.get(key); } } // Uso const container = new Container(); container.register('dbConfig', process.env.NODE_ENV === 'production' ? new ProdDatabaseConfig() : new DevDatabaseConfig() ); ```
83
**Q3: Explica el patrón Observer y proporciona un ejemplo práctico de su implementación en un sistema de notificaciones en tiempo real.**
El patrón Observer establece una relación uno-a-muchos entre objetos, donde cuando un objeto cambia su estado, todos sus dependientes son notificados y actualizados automáticamente. Implementación conceptual de un sistema de notificaciones: ```javascript class NotificationCenter { constructor() { this.observers = new Map(); } subscribe(event, callback) { if (!this.observers.has(event)) { this.observers.set(event, []); } this.observers.get(event).push(callback); } unsubscribe(event, callback) { if (!this.observers.has(event)) return; this.observers.set(event, this.observers.get(event).filter(cb => cb !== callback) ); } notify(event, data) { if (!this.observers.has(event)) return; this.observers.get(event).forEach(callback => callback(data)); } } ```
84
**¿Cómo implementarías el patrón Singleton en una clase de conexión a MongoDB asegurando thread-safety en Node.js?**
El patrón Singleton garantiza una única instancia de una clase. Para MongoDB, debemos: 1. Mantener una instancia privada 2. Proporcionar un método de acceso global 3. Implementar conexión lazy 4. Manejar reconexiones ```javascript class MongoDBConnection { static instance; constructor() { if (MongoDBConnection.instance) { return MongoDBConnection.instance; } this.client = null; MongoDBConnection.instance = this; } async connect() { if (this.client) return this.client; try { this.client = await MongoClient.connect(process.env.MONGODB_URI); return this.client; } catch (error) { console.error('MongoDB connection failed:', error); throw error; } } } ```
85
**Explica detalladamente cómo funciona el event loop en JavaScript y cómo se relaciona con la pila de llamadas, la cola de callbacks y el microtask queue.**
El event loop es el mecanismo central que permite la ejecución asíncrona en JavaScript. Funciona así: 1. La pila de llamadas (call stack) ejecuta el código síncrono 2. Las operaciones asíncronas (setTimeout, promesas, etc.) se delegan al navegador/Node.js 3. Cuando estas operaciones terminan, sus callbacks se colocan en colas: - Microtask queue: Para promesas (mayor prioridad) - Callback queue: Para setTimeout/setInterval/eventos 4. El event loop constantemente verifica: - Si la pila está vacía - Si hay microtareas pendientes (las ejecuta todas) - Si hay callbacks pendientes (ejecuta uno por iteración) Este proceso garantiza que JavaScript nunca bloquee el hilo principal mientras maneja operaciones asíncronas.
86
**¿Cómo implementarías un sistema de caché para memoización de funciones pesadas que además tenga un tiempo de expiración?**
Se implementaría creando un objeto que almacene los resultados junto con timestamps (marcas de tiempo). La función de memoización (técnica de optimización que almacena resultados de operaciones costosas) verificaría primero si existe un resultado en caché y si no ha expirado. La clave del caché sería un hash (valor único generado a partir de los argumentos) de los parámetros de entrada. Se incluiría un garbage collector (recolector de basura automático) para limpiar entradas expiradas y evitar memory leaks (fugas de memoria).
87
**Q: ¿Cómo implementarías un sistema de rate limiting en Node.js que funcione en un entorno distribuido con múltiples instancias?**
Se implementaría usando Redis (base de datos en memoria) como store centralizado para mantener los contadores de requests. Cada solicitud incrementaría un counter (contador) asociado a la IP o token del usuario con un TTL (Time To Live: tiempo de vida del dato). Se usaría el comando MULTI de Redis (permite ejecutar múltiples operaciones de forma atómica) para garantizar la consistencia. Se implementaría el algoritmo de Token Bucket (sistema que permite acumular "tokens" para realizar peticiones), permitiendo burst requests (ráfagas controladas de peticiones) de forma controlada.
88
**Explica detalladamente cómo funciona el Event Loop en Node.js**
El Event Loop es el mecanismo central de Node.js que maneja la ejecución asíncrona. Funciona en fases: primero ejecuta los callbacks de timers (setTimeout/setInterval), luego los pending callbacks de operaciones I/O, después la fase poll para nuevos eventos I/O, la fase check para setImmediate, y finalmente close callbacks. Todo esto mientras mantiene un solo hilo de ejecución, permitiendo operaciones no bloqueantes.
89
**¿Cómo manejarías errores en una aplicación Node.js en producción?**
Implementaría un sistema de manejo de errores por capas: uso de try-catch para errores síncronos, promesas con .catch() para errores asíncronos, un middleware de error global en Express, logging de errores con Winston o similar, monitoreo con herramientas como New Relic, y notificaciones automáticas para errores críticos.
90
**P: ¿Cómo implementarías autenticación y autorización en una API REST con Express?**
Implementaría un sistema basado en JWT para autenticación, middleware de verificación de tokens, roles de usuario almacenados en el token, middleware de autorización basado en roles, refresh tokens para seguridad adicional, y almacenamiento seguro de contraseñas con bcrypt.
91
**P: ¿Qué es throttling y cuándo se usa?**
Throttling es una técnica que limita la frecuencia con que se ejecuta una función. Por ejemplo, si un usuario hace scroll rápidamente, podemos limitar la ejecución de la función asociada a una vez cada 100ms, en lugar de ejecutarla en cada evento de scroll. Es útil para mejorar el rendimiento en eventos frecuentes como scroll, resize o mousemove.
92
**P: ¿Qué es debouncing y cuándo se usa?**
Debouncing espera hasta que el usuario "termine" una acción antes de ejecutar la función. Por ejemplo, en una barra de búsqueda, en lugar de hacer una petición al servidor con cada tecla presionada, esperamos hasta que el usuario deje de escribir por cierto tiempo (ejemplo: 500ms). Es ideal para búsquedas en tiempo real o validaciones de formularios.
93
**P: ¿Cuál es la diferencia práctica entre throttling y debouncing?**
Throttling garantiza una ejecución regular (ejemplo: máximo una vez cada segundo), mientras que debouncing espera hasta que la actividad se detenga. Throttling es mejor para actualizaciones en tiempo real (como un juego), debouncing es mejor para acciones que deben ocurrir después de que el usuario termine (como búsquedas).
94
**P: ¿Qué es el patrón Observer y para qué sirve?**
El patrón Observer es un patrón de diseño donde un objeto (el subject) mantiene una lista de otros objetos (los observers) y les notifica automáticamente cuando ocurre algún cambio. Es como un sistema de suscripción donde los suscriptores reciben actualizaciones del publicador.
95
**P: ¿Cuáles son los componentes principales del patrón Observer?**
R: Los componentes son: el Subject (mantiene la lista de observers y los notifica), los Observers (objetos que quieren recibir notificaciones), métodos de suscripción (subscribe/unsubscribe) y el método de notificación (notify). Es similar a los event listeners en JavaScript.
96
**P: ¿Qué es un closure y cómo se relaciona con estos patrones?**
R: Un closure es una función que mantiene acceso a variables de su scope exterior incluso después de que la función externa haya terminado. En throttling y debouncing, usamos closures para mantener el estado del timer. En el patrón Observer, podemos usar closures para mantener privada la lista de observers.
97
**P: ¿Qué son los memory leaks y cómo evitarlos en estos patrones?**
R: Los memory leaks ocurren cuando mantenemos referencias a objetos que ya no necesitamos. En el patrón Observer, debemos asegurarnos de que los observers se desuscriban cuando ya no sean necesarios. En throttling/debouncing, debemos limpiar los timers con clearTimeout cuando el componente se desmonte.
98
Q: Explica el patrón Singleton y cómo lo implementarías en Node.js
A: El Singleton asegura que una clase tenga una única instancia y proporciona un punto global de acceso. En Node.js, podemos lograrlo exportando una instancia ya creada del objeto en lugar de su clase, aprovechando que los módulos se cachean automáticamente. También podemos usar una clase con un constructor privado y un método estático getInstance() que controle la creación de la instancia.
99
: ¿Cómo implementarías el patrón Observer en una aplicación Express?
A: El patrón Observer se puede implementar usando el sistema de eventos de Node.js (EventEmitter). Crearía una clase que extienda EventEmitter para el sujeto observable, y los observadores se suscribirían usando los métodos .on() o .once(). Es especialmente útil para manejar eventos como cambios en la base de datos o notificaciones en tiempo real.
100
Q: Explica cómo manejarías la autenticación en una API REST usando Express
A: Implementaría un sistema de autenticación usando JWT (JSON Web Tokens). El flujo sería: el usuario se autentica con credenciales, el servidor valida y genera un token JWT firmado, y este token se usa en las siguientes peticiones en el header Authorization. Usaría middleware para verificar el token en rutas protegidas y manejaría los roles de usuario en el payload del token.
101
Q: ¿Cómo implementarías transacciones en MongoDB manteniendo la consistencia de datos?
A: MongoDB soporta transacciones ACID en múltiples documentos desde la versión 4.0. Implementaría una sesión de transacción usando el patrón try/catch, donde todas las operaciones se ejecutan dentro de la sesión. Si algo falla, se hace rollback automático. Es importante considerar el timeout de la transacción y manejar los errores adecuadamente.
102
Q: Explica el patrón de diseño Module y cómo se relaciona con los closures
A: El patrón Module usa closures para crear privacidad y encapsulación. Se implementa mediante una IIFE (Función Inmediatamente Invocada) que retorna un objeto con los métodos públicos, mientras que las variables y funciones privadas quedan encapsuladas en el closure. Es la base de los módulos en Node.js y ayuda a evitar la contaminación del espacio global.
103
**¿Qué es TDD (Test-Driven Development) y cuáles son sus beneficios?**
TDD es una metodología de desarrollo en la que se escriben pruebas antes del código. El ciclo TDD consta de tres pasos: 1. **Red:** Escribir una prueba que falle. 2. **Green:** Escribir el código mínimo para que la prueba pase. 3. **Refactor:** Mejorar el código sin cambiar su comportamiento. - **Beneficios:** - Mejora la calidad del código. - Facilita la detección temprana de errores. - Sirve como documentación viva del código.
104
**¿Qué es Jest y cómo se usa para pruebas en Node.js?**
Jest es un framework de testing para JavaScript y TypeScript. Es ampliamente utilizado debido a su simplicidad y características como cobertura de código, mocks, y pruebas asíncronas. - **Ejemplo de prueba con Jest:** ```javascript function sum(a, b) { return a + b; } test('Suma 1 + 2 igual a 3', () => { expect(sum(1, 2)).toBe(3); }); ```
105
**¿Qué es un mock en pruebas y cómo se usa en Jest?**
Un mock es un objeto simulado que imita el comportamiento de un componente real. Se utiliza en pruebas para aislar el código bajo prueba y evitar dependencias externas. - **Ejemplo en Jest:** ```javascript const axios = require('axios'); jest.mock('axios'); test('Obtener datos de la API', async () => { axios.get.mockResolvedValue({ data: 'Datos simulados' }); const response = await axios.get('https://api.example.com'); expect(response.data).toBe('Datos simulados'); }); ```
106
**¿Qué es un stub en pruebas y cómo se diferencia de un mock?**
Un stub es un objeto que devuelve respuestas predefinidas. A diferencia de un mock, un stub no verifica cómo se llama, solo proporciona datos para las pruebas. - **Ejemplo de stub:** ```javascript const database = { getUser: () => ({ id: 1, name: 'John' }) }; test('Obtener usuario', () => { const user = database.getUser(); expect(user.name).toBe('John'); }); ```
107
**¿Qué es el patrón pub/sub en Redis y cómo se implementa?**
El patrón pub/sub (Publicar/Suscribir) en Redis permite que los publicadores envíen mensajes a canales, y los suscriptores reciben mensajes de los canales a los que están suscritos. - **Implementación:** 1. **Publicar mensaje:** ```bash PUBLISH canal1 "Hola, mundo!" ``` 2. **Suscribirse a un canal:** ```bash SUBSCRIBE canal1 ``` 3. **Recibir mensajes:** - Los suscriptores reciben mensajes en tiempo real mientras estén suscritos.
108
**¿Qué es Swagger y cómo se integra con Express.js?**
Swagger es una herramienta para diseñar, documentar y probar APIs RESTful. Se integra con Express.js usando `swagger-ui-express`. - **Ejemplo de integración:** ```javascript const express = require('express'); const swaggerUi = require('swagger-ui-express'); const swaggerDocument = require('./swagger.json'); const app = express(); app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerDocument)); app.listen(3000); ```
109
**¿Qué es OpenAPI y cómo se relaciona con Swagger?**
OpenAPI es una especificación para describir APIs RESTful. Swagger es una implementación de OpenAPI que incluye herramientas para generar documentación y probar APIs. - **Ejemplo de especificación OpenAPI:** ```yaml openapi: 3.0.0 info: title: API de Ejemplo version: 1.0.0 paths: /users: get: summary: Obtener lista de usuarios responses: '200': description: Lista de usuarios ```
110
**¿Qué es YAML y cómo se usa en la documentación de APIs?**
YAML es un formato de serialización de datos legible por humanos, usado principalmente para configuraciones y documentación. En APIs, se usa para definir especificaciones OpenAPI. - **Ejemplo de YAML:** ```yaml openapi: 3.0.0 info: title: API de Ejemplo version: 1.0.0 paths: /users: get: summary: Obtener lista de usuarios responses: '200': description: Lista de usuarios ```
111
**¿Qué es la validación de esquemas en APIs y cómo se implementa?**
La validación de esquemas asegura que los datos enviados a una API cumplan con una estructura definida. Se implementa usando librerías como `joi` o `ajv`. - **Ejemplo con `joi`:** ```javascript const Joi = require('joi'); const schema = Joi.object({ name: Joi.string().required(), age: Joi.number().min(18).required() }); const { error } = schema.validate({ name: "John", age: 25 }); if (error) console.log(error.details); ```
112
**¿Qué es HATEOAS y cómo se implementa en APIs REST?**
HATEOAS (Hypermedia as the Engine of Application State) es un principio de REST que incluye enlaces en las respuestas para permitir la navegación entre recursos. - **Ejemplo de implementación:** ```json { "data": { "id": 1, "name": "John" }, "links": { "next": "/users?page=2" } } ```
113
**¿Cómo se documenta una API REST con Swagger?**
Para documentar una API REST con Swagger, se sigue estos pasos: 1. **Definir la especificación OpenAPI:** - Crear un archivo YAML o JSON que describa los endpoints, parámetros, respuestas y ejemplos. 2. **Integrar Swagger UI:** - Usar librerías como `swagger-ui-express` para servir la documentación desde la API. 3. **Ejemplo de integración en Express:** ```javascript const express = require('express'); const swaggerUi = require('swagger-ui-express'); const swaggerDocument = require('./swagger.json'); const app = express(); app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerDocument)); app.listen(3000); ```
114
**¿Qué es REST y cuáles son sus principios?**
REST (Representational State Transfer) es un estilo arquitectónico para diseñar APIs basado en recursos y verbos HTTP. - **Principios de REST:** 1. **Recursos:** Todo es un recurso identificable por una URL (ej: `/users`). 2. **Verbos HTTP:** GET (leer), POST (crear), PUT (actualizar), DELETE (eliminar). 3. **Stateless:** Cada solicitud debe contener toda la información necesaria. 4. **Representación:** Los recursos pueden representarse en JSON, XML, etc. 5. **HATEOAS:** Incluir enlaces en las respuestas para navegación.
115
**¿Qué es JWT y cómo se usa en autenticación?**
JWT (JSON Web Token) es un estándar para autenticación basado en tokens. - **Estructura de un JWT:** 1. **Header:** Especifica el algoritmo de firma (ej: HS256). 2. **Payload:** Contiene los datos del usuario (ej: ID, roles). 3. **Signature:** Firma digital para verificar la autenticidad. - **Uso en autenticación:** - El servidor genera un JWT firmado y lo envía al cliente. - El cliente incluye el JWT en el header `Authorization` de cada solicitud. - El servidor verifica la firma del token para autenticar al usuario.
116
**¿Qué es OAuth2 y cómo funciona?**
OAuth2 es un protocolo de autorización que permite a aplicaciones de terceros acceder a recursos de un usuario sin compartir sus credenciales. - **Flujo básico:** 1. El cliente redirige al usuario al servidor de autorización. 2. El usuario autoriza la solicitud. 3. El servidor devuelve un código de autorización al cliente. 4. El cliente intercambia el código por un token de acceso. 5. El cliente usa el token para acceder a los recursos del usuario.
117
**¿Qué es una base de datos relacional y cuáles son sus características?**
Una base de datos relacional almacena datos en tablas relacionadas entre sí mediante claves primarias y foráneas. - **Características:** 1. **Estructura fija:** Los datos se organizan en filas y columnas. 2. **Integridad referencial:** Las relaciones entre tablas se mantienen consistentes. 3. **SQL:** Lenguaje estándar para consultar y manipular datos. 4. **Ejemplos:** Oracle, MySQL, PostgreSQL.
118
**¿Qué es una base de datos NoSQL y cuáles son sus ventajas?**
Una base de datos NoSQL almacena datos en formatos no tabulares, como documentos, grafos o pares clave-valor. - **Ventajas:** 1. **Flexibilidad:** No requiere un esquema fijo. 2. **Escalabilidad:** Diseñada para manejar grandes volúmenes de datos. 3. **Rendimiento:** Optimizada para operaciones específicas. 4. **Ejemplos:** MongoDB (documentos), Redis (clave-valor), Neo4j (grafos).
119
**¿Qué es Oracle y cuáles son sus características principales?**
Oracle es un sistema de gestión de bases de datos relacionales (RDBMS) ampliamente utilizado en entornos empresariales. - **Características principales:** 1. **Escalabilidad:** Soporta grandes volúmenes de datos y transacciones concurrentes. 2. **PL/SQL:** Lenguaje procedural para crear procedimientos almacenados, funciones y triggers. 3. **Seguridad:** Ofrece funciones avanzadas como cifrado de datos y auditoría. 4. **Alta disponibilidad:** Soluciones como Oracle RAC (Real Application Clusters) para evitar tiempos de inactividad. 5. **Herramientas de gestión:** Oracle Enterprise Manager para administrar y monitorear la base de datos.
120
**¿Qué es PL/SQL y cómo se usa en Oracle?**
PL/SQL (Procedural Language/SQL) es una extensión de SQL utilizada en Oracle para programar lógica de negocio directamente en la base de datos. - **Características:** 1. **Bloques anónimos:** Fragmentos de código que se ejecutan sin necesidad de ser almacenados. 2. **Procedimientos almacenados:** Bloques de código reutilizables que se guardan en la base de datos. 3. **Funciones:** Similar a los procedimientos, pero devuelven un valor. 4. **Triggers:** Código que se ejecuta automáticamente en respuesta a eventos (ej: INSERT, UPDATE).
121
**¿Qué es Redis y cuáles son sus casos de uso comunes?**
Redis es una base de datos en memoria que se utiliza principalmente como caché, sistema de mensajería y almacén de datos clave-valor. - **Casos de uso comunes:** 1. **Caché:** Almacenar datos frecuentemente accedidos para reducir la carga en la base de datos principal. 2. **Pub/Sub:** Sistema de mensajería donde los mensajes se envían a múltiples suscriptores. 3. **Sesiones:** Almacenar datos de sesión de usuarios en aplicaciones web. 4. **Estructuras de datos avanzadas:** Listas, conjuntos, hashes y sorted sets. - **Ejemplo de uso como caché:** ```bash SET user_1 "John" EX 3600 # Almacena el valor "John" con una clave `user_1` durante 1 hora GET user_1 # Devuelve "John" ```
122
**¿Qué es MongoDB y cuáles son sus ventajas sobre las bases de datos relacionales?**
MongoDB es una base de datos NoSQL basada en documentos que almacena datos en formato BSON (Binary JSON). - **Ventajas:** 1. **Flexibilidad:** No requiere un esquema fijo, lo que permite cambios rápidos en la estructura de los datos. 2. **Escalabilidad:** Diseñada para manejar grandes volúmenes de datos y distribuir la carga en múltiples servidores. 3. **Rendimiento:** Optimizada para operaciones de lectura/escritura rápidas. 4. **Lenguaje de consulta:** Usa un lenguaje similar a JSON para consultas. - **Ejemplo de documento en MongoDB:** ```json { "_id": "12345", "name": "John", "age": 30, "address": { "city": "New York", "zip": "10001" } } ```
123
**¿Qué es TypeScript y cuáles son sus ventajas sobre JavaScript?**
TypeScript es un superset de JavaScript que añade tipado estático opcional. Fue desarrollado por Microsoft y se compila a JavaScript puro. - **Ventajas de TypeScript:** 1. **Tipado estático:** Permite detectar errores en tiempo de compilación, lo que mejora la calidad del código. 2. **Mejor autocompletado:** Los editores como VSCode ofrecen sugerencias más precisas gracias a los tipos. 3. **Documentación implícita:** Los tipos sirven como documentación, haciendo el código más legible. 4. **Compatibilidad con JavaScript:** TypeScript es compatible con todo el ecosistema de JavaScript. 5. **Escalabilidad:** Ideal para proyectos grandes y equipos de desarrollo.
124
**¿Qué es una interfaz en TypeScript y cómo se usa?**
Una interfaz en TypeScript es una estructura que define la forma de un objeto. Permite especificar qué propiedades y métodos debe tener un objeto. - **Ejemplo:** ```typescript interface User { id: number; name: string; email?: string; // Propiedad opcional } const user: User = { id: 1, name: "John" }; ``` - **Interfaces vs tipos:** Las interfaces son extendibles (pueden heredar de otras interfaces), mientras que los tipos (`type`) son más flexibles pero no se pueden extender.
125
**¿Qué son los generics en TypeScript y cómo se usan?**
Los generics permiten crear componentes reutilizables que funcionan con múltiples tipos. Son útiles para funciones, clases e interfaces que deben ser flexibles en cuanto al tipo de datos que manejan. - **Ejemplo de función genérica:** ```typescript function identity(arg: T): T { return arg; } const output1 = identity("Hola"); const output2 = identity(42); ``` - **Ejemplo de clase genérica:** ```typescript class Box { private value: T; constructor(value: T) { this.value = value; } getValue(): T { return this.value; } } const box = new Box(42); console.log(box.getValue()); // 42 ```
126
**¿Qué es `unknown` en TypeScript y cómo se diferencia de `any`?**
`unknown` es un tipo similar a `any`, pero más seguro. A diferencia de `any`, una variable de tipo `unknown` no puede ser usada directamente sin antes verificar su tipo. - **Ejemplo:** ```typescript let value: unknown; if (typeof value === 'string') { console.log(value.toUpperCase()); // Seguro } else { console.log('No es una cadena'); } ``` - **Diferencia con `any`:** - `any` permite cualquier operación sin verificación. - `unknown` requiere una verificación explícita antes de su uso.
127
**¿Qué es el módulo `fs` en Node.js y cómo se usa?**
El módulo `fs` (File System) en Node.js permite interactuar con el sistema de archivos. Proporciona métodos para leer, escribir, y manipular archivos de manera síncrona y asíncrona. - **Ejemplo de lectura asíncrona:** ```javascript const fs = require('fs'); fs.readFile('file.txt', 'utf8', (err, data) => { if (err) throw err; console.log(data); }); ``` - **Ejemplo de escritura asíncrona:** ```javascript fs.writeFile('file.txt', 'Hola, mundo!', (err) => { if (err) throw err; console.log('Archivo guardado'); }); ```
128
**¿Qué es el patrón Factory y cuándo usarlo?**
El patrón Factory es un patrón de creación que proporciona una interfaz para crear objetos sin especificar su clase concreta. - **Cuándo usarlo:** - Cuando la lógica de creación de objetos es compleja. - Cuando se necesita desacoplar la creación de objetos de su uso. - **Ejemplo:** ```javascript class Car { constructor(type) { this.type = type; } } class CarFactory { static createCar(type) { return new Car(type); } } const sedan = CarFactory.createCar('sedan'); console.log(sedan.type); // sedan ```
129
**¿Qué es AWS y cuáles son sus principales servicios?**
AWS (Amazon Web Services) es la plataforma de computación en la nube más grande y utilizada en el mundo, ofreciendo más de 200 servicios integrales en áreas como computación, almacenamiento, bases de datos, machine learning, seguridad, y desarrollo de aplicaciones. Algunos de los servicios más importantes incluyen: - **EC2 (Elastic Compute Cloud):** Para servidores virtuales escalables. - **S3 (Simple Storage Service):** Para almacenamiento de objetos. - **RDS (Relational Database Service):** Para bases de datos relacionales gestionadas. - **Lambda:** Para ejecutar código sin servidor. - **API Gateway:** Para crear y gestionar APIs. - **DynamoDB:** Para bases de datos NoSQL de alto rendimiento. - **CloudFront:** Para distribución de contenido con baja latencia. - **IAM (Identity and Access Management):** Para gestionar accesos y permisos. AWS es esencial para construir aplicaciones escalables, seguras y de alto rendimiento en la nube.
130
**¿Qué es Amazon S3 y para qué se utiliza?**
Amazon S3 (Simple Storage Service) es un servicio de almacenamiento de objetos altamente escalable y duradero, diseñado para almacenar y recuperar cualquier cantidad de datos desde cualquier lugar. Algunos casos de uso comunes incluyen: - Almacenamiento de archivos estáticos para aplicaciones web (por ejemplo, imágenes, CSS, JavaScript). - Backup y recuperación de datos. - Almacenamiento de datos para análisis y big data. - Hosting de sitios web estáticos. - Almacenamiento de logs y datos de aplicaciones. S3 ofrece características avanzadas como versionado de objetos, cifrado en reposo y en tránsito, y políticas de ciclo de vida para automatizar la gestión de datos. Es una opción ideal para aplicaciones que requieren almacenamiento confiable y accesible.
131
**¿Qué es AWS Lambda y cómo funciona?**
AWS Lambda es un servicio de computación sin servidor que permite ejecutar código en respuesta a eventos sin tener que gestionar servidores. Funciona de la siguiente manera: - Subes tu código (por ejemplo, en Node.js, Python, o Java). - Configuras un evento que desencadene la ejecución del código (por ejemplo, una solicitud HTTP, un cambio en S3, o un mensaje en una cola SQS). - Lambda ejecuta el código automáticamente y escala según la demanda. Es ideal para: - Procesamiento de datos en tiempo real. - Automatización de tareas. - Backends de aplicaciones móviles o web. - Integración con otros servicios de AWS como API Gateway, DynamoDB, o S3. Lambda es rentable porque solo pagas por el tiempo de ejecución del código, medido en milisegundos.
132
**¿Qué es Amazon EC2 y cuáles son sus casos de uso comunes?**
Amazon EC2 (Elastic Compute Cloud) es un servicio que permite lanzar y gestionar servidores virtuales (instancias) en la nube. Ofiere una amplia variedad de tipos de instancias optimizadas para diferentes cargas de trabajo, como computación general, memoria intensiva, o GPU. Casos de uso comunes: - Hosting de aplicaciones web y backend. - Ejecución de aplicaciones empresariales. - Procesamiento de big data y análisis. - Desarrollo y pruebas de software. - Escalado automático para manejar picos de tráfico. EC2 es altamente flexible y permite elegir sistemas operativos, configuraciones de red, y opciones de almacenamiento. Además, se integra con otros servicios de AWS como Elastic Load Balancing (ELB) para distribuir tráfico y Auto Scaling para ajustar la capacidad automáticamente.
133
**¿Qué es Amazon RDS y qué tipos de bases de datos soporta?**
Amazon RDS (Relational Database Service) es un servicio gestionado que facilita la configuración, operación, y escalabilidad de bases de datos relacionales en la nube. Soporta varios motores de bases de datos populares: - MySQL - PostgreSQL - Oracle - SQL Server - MariaDB - Aurora (un motor compatible con MySQL y PostgreSQL desarrollado por AWS). RDS automatiza tareas como backups, parches de software, y replicación, lo que permite a los desarrolladores enfocarse en la aplicación en lugar de la gestión de la base de datos. Es ideal para aplicaciones que requieren bases de datos relacionales con alta disponibilidad y escalabilidad.
134
**¿Qué es Amazon API Gateway y cómo se integra con otros servicios de AWS?**
Amazon API Gateway es un servicio completamente gestionado que permite crear, publicar, mantener, monitorear, y proteger APIs REST y WebSocket. Funciona como un punto de entrada para aplicaciones backend y se integra fácilmente con servicios como: - **AWS Lambda:** Para ejecutar lógica de backend sin servidor. - **DynamoDB:** Para almacenar y recuperar datos NoSQL. - **EC2:** Para conectar con servidores virtuales. - **CloudWatch:** Para monitorear el rendimiento de las APIs. API Gateway ofrece características como throttling (limitación de tasa de solicitudes), autorización, y transformación de solicitudes/respuestas. Es ideal para construir APIs escalables y seguras para aplicaciones web, móviles, o IoT.
135
**¿Qué es Amazon DynamoDB y en qué se diferencia de RDS?**
Amazon DynamoDB es una base de datos NoSQL completamente gestionada que ofrece alto rendimiento y escalabilidad automática. A diferencia de RDS, que es una base de datos relacional, DynamoDB está diseñada para manejar grandes volúmenes de datos no estructurados o semi-estructurados con baja latencia. Características clave: - Escalabilidad automática para manejar millones de solicitudes por segundo. - Modelo de datos flexible (clave-valor y documentos). - Integración con AWS Lambda para procesamiento en tiempo real. - Alta disponibilidad y durabilidad. Es ideal para aplicaciones como carritos de compras, juegos, y sistemas de recomendación que requieren baja latencia y alta escalabilidad.
136
**¿Qué es AWS IAM y por qué es importante?**
AWS IAM (Identity and Access Management) es un servicio que permite gestionar el acceso a los recursos de AWS de manera segura. Con IAM, puedes: - Crear y gestionar usuarios, grupos, y roles. - Asignar permisos granularmente para controlar quién puede acceder a qué recursos. - Habilitar autenticación multifactor (MFA) para mayor seguridad. - Integrarse con servicios externos como Google o Facebook para autenticación federada. IAM es fundamental para garantizar la seguridad y el cumplimiento de las aplicaciones en la nube, ya que evita el acceso no autorizado a los recursos de AWS.
137
**¿Qué es Amazon CloudFront y cómo mejora el rendimiento de una aplicación?**
Amazon CloudFront es una red de entrega de contenido (CDN) global que acelera la distribución de contenido estático y dinámico, como imágenes, videos, y APIs, a los usuarios finales. Funciona almacenando copias del contenido en ubicaciones (edge locations) cercanas a los usuarios, lo que reduce la latencia y mejora la velocidad de carga. Beneficios: - Mejora el rendimiento de aplicaciones web y móviles. - Reduce la carga en los servidores de origen. - Ofiere cifrado SSL/TLS para mayor seguridad. - Se integra con S3, EC2, y Lambda@Edge para personalizar el contenido en tiempo real. Es ideal para aplicaciones con usuarios distribuidos globalmente.
138
**¿Qué es AWS CloudWatch y para qué se utiliza?**
AWS CloudWatch es un servicio de monitoreo y observabilidad que recopila y visualiza métricas, logs, y eventos de los recursos de AWS. Se utiliza para: - Monitorear el rendimiento de aplicaciones y recursos (por ejemplo, uso de CPU, latencia, errores). - Configurar alarmas para notificar sobre problemas o cambios en los recursos. - Automatizar respuestas a eventos con acciones como escalado automático o ejecución de funciones Lambda. - Analizar logs para depurar aplicaciones y detectar problemas. CloudWatch es esencial para mantener la salud y el rendimiento de las aplicaciones en la nube.
139
¿Qué es RESTful y en qué se diferencia de REST?
REST (Representational State Transfer) es un estilo de arquitectura de software para sistemas distribuidos, como la web. Se basa en principios como el uso de identificadores únicos (URI), métodos estándar (GET, POST, PUT, DELETE), y la comunicación sin estado entre cliente y servidor. RESTful, por otro lado, es un término que describe un sistema o servicio que sigue los principios y restricciones de REST. En otras palabras, REST es el concepto teórico, mientras que RESTful se refiere a la implementación práctica de ese concepto. Un servicio RESTful es aquel que cumple con las reglas de REST y permite la interacción con recursos web de manera eficiente y escalable.