MEN — Express + Nodejs + MongoDB con mongodb

Mauricio Garcia
9 min readOct 7, 2024

--

Introducción

En la story pasada [API CRUD — Node.js y Express], implementamos una versión funcional del CRUD con una simulación de datos en memoria. Esto significa que, aunque la API funcionaba, los datos se perdían cada vez que se reiniciaba el servidor, ya que no había persistencia en una base de datos real.

En esta nueva story, vamos a conectar la API con MongoDB para almacenar los datos de manera permanente. Para lograr esto, utilizaremos el paquete [mongodb].

Antes de comenzar, te sugiero primero revisar:

mongoDB

Recordemos que MongoDB es una base de datos de documentos que ofrece una gran escalabilidad y flexibilidad, y un modelo de consultas e indexación avanzado [ref].

::Levantando el servicio

Antes de continuar, necesitamos verificar que el servicio de MongoDB esté ejecutándose. Para ello, ejecutamos la siguiente línea en la consola:

ps aux | grep -v grep | grep mongod

Si el servicio está funcionando, deberías ver algo similar a la siguiente imagen:

En caso de que no veas este mensaje, asegúrate de iniciar el servicio. La documentación oficial de MongoDB [ref][ref] recomienda utilizar Homebrew (si estás en macOS) para gestionar e iniciar MongoDB fácilmente con las configuraciones por defecto.

Para ejecutar el servicio de MongoDB con brew, utiliza el siguiente comando (en este caso, estamos ejecutando la versión 5):

brew services start mongodb-community@5.0

:: Configurando y conectando MongoDB

— Obtener URL de la base de datos

Para conectar tu aplicación a MongoDB, primero necesitas obtener la URL de la base de datos. Abre una terminal y ejecuta el comando mongo o mongosh, dependiendo de la versión que estés utilizando. Al conectarte, aparecerá un mensaje en la consola que te mostrará la dirección a la que se ha conectado. Verás algo similar a lo siguiente:

Esta URL (mongodb://127.0.0.1:27017/) es la que necesitas utilizar para que tu aplicación pueda conectarse a MongoDB.

— Almacenamiento de variables en .env

Como buena práctica, es recomendable almacenar la URL y el nombre de la base de datos en el archivo .env. Esto permite que la aplicación pueda cambiar fácilmente entre distintos entornos, como desarrollo, producción, entre otros, sin modificar el código directamente. Agrega las siguientes variables a tu archivo .env:

# Puerto del servidor
PORT = 3000

# Configuración de MongoDB
DATABASE_URL = mongodb://127.0.0.1:27017/
DATABASE = blog-messages

— Instalando y conectando a mongodb

El primer paso es instalar el paquete mongodb en el proyecto. Asegúrate de estar en la raíz del proyecto y ejecuta el siguiente comando en la consola:

npm install mongodb

Es importante saber que cada vez que modifiques las variables en el archivo .env, asegúrate de reiniciar el servidor para que los cambios sean aplicados.

Abre el archivo app.js y añade las siguientes importaciones, incluida la de MongoDB:

require('dotenv').config();
const express = require('express');
const cors = require('cors');

// Importamos mongodb
const { MongoClient, ObjectId } = require("mongodb");

const app = express();
const port = process.env.PORT || 3000;

//-- Middlewares Globales
app.use(cors());
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
  • MongoClient — Es un objeto que actúa como el cliente que se conecta a la base de datos. Proporciona métodos para realizar operaciones CRUD (crear, leer, actualizar, eliminar) sobre una base de datos MongoDB.
  • ObjectId — Se utiliza para consultar documentos en una colección por su campo _id.

A continuación, vamos a crear la conexión a MongoDB utilizando la URL configurada en el archivo .env o la URL por defecto:


// Conectar a MongoDB
const urlDB = process.env.DATABASE_URL || "mongodb://127.0.0.1:27017";
const client = new MongoClient(urlDB);

async function connectToDatabase() {
try {
await client.connect();
console.log("Conectado a la base de datos");
} catch (error) {
console.error("Error al conectar a MongoDB:", error);
}
}

connectToDatabase();

Este código establece la conexión a la base de datos. Si hay algún problema al conectarse, controla el error y se mostrará en la consola.

El siguiente paso es seleccionar la base de datos y la colección de mensajes:

async function connectToDatabase() {
try {
await client.connect();
console.log("Conectado a la base de datos");

// Selecciona la base de datos y la colección de mensajes
const db = client.db(process.env.DATABASE || "blog-messages");
const messagesCollection = db.collection("messages");

} catch (error) {
console.error("Error al conectar a MongoDB:", error);
}
}

connectToDatabase();

Recuerda: Si MongoDB no encuentra la base de datos, la creará automáticamente la primera vez que se inserten datos.

Para facilitar el uso de la colección en las rutas, añadimos un middleware que pase la colección a las rutas a través de req.context:

async function connectToDatabase() {
try {
await client.connect();
console.log("Conectado a la base de datos");

// Selecciona la base de datos y la colección de mensajes
const db = client.db(process.env.DATABASE || "blog-messages");
const messagesCollection = db.collection("messages");

// Middleware para inyectar la base de datos en las rutas
app.use((req, res, next) => {
req.context = { collections: { messages: messagesCollection }, ObjectId };
next();
});

} catch (error) {
console.error("Error al conectar a MongoDB:", error);
}
}

connectToDatabase();

Este middleware permite acceder a la colección de mensajes en cualquier ruta mediante req.context.collections.messages.

Finalmente, importa y monta las rutas de mensajes, añade el middleware de manejo de errores y el que arranca el servidor:

// Importar las rutas de mensajes
const messageRoutes = require("./routes/messages");
app.use("/messages", messageRoutes);

// Middleware de manejo de errores centralizado
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(err.status || 500).json({
message: err.message || "Error interno del servidor",
error: process.env.NODE_ENV === "production" ? {} : err,
});
});

// Iniciar el servidor
app.listen(port, () => {
console.log(`Servidor escuchando en http://localhost:${port}`);
});

A partir de aquí, tus rutas podrán acceder a la base de datos de manera eficiente.

Código completo de app.js

require('dotenv').config();
const express = require('express');
const cors = require('cors');
const { MongoClient, ObjectId } = require('mongodb'); // Importamos mongodb

const app = express();
const port = process.env.PORT || 3000;

//-- Middlewares Globales
app.use(cors());
app.use(express.json());
app.use(express.urlencoded({ extended: true }));

// Conectar a MongoDB
const urlDB = process.env.DATABASE_URL || 'mongodb://127.0.0.1:27017';
const client = new MongoClient(urlDB);

async function connectToDatabase() {
try {
await client.connect();
console.log('Conectado a la base de datos');

// Selecciona la base de datos y la colección de mensajes
const db = client.db(process.env.DATABASE || 'blog-messages');
const messagesCollection = db.collection('messages');

// Middleware para inyectar la base de datos en las rutas
app.use((req, res, next) => {
req.context = { collections: { messages: messagesCollection }, ObjectId };
next();
});

// Importamos y montamos las rutas
const messageRoutes = require('./routes/messages');
app.use('/messages', messageRoutes);

// Middleware de manejo de errores centralizado
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(err.status || 500).json({
message: err.message || 'Error interno del servidor',
error: process.env.NODE_ENV === 'production' ? {} : err,
});
});

// Iniciar el servidor
app.listen(port, () => {
console.log(`Servidor escuchando en http://localhost:${port}`);
});
} catch (error) {
console.error('Error al conectar a MongoDB:', error);
}
}

connectToDatabase();

Si has seguido todos los pasos correctamente, las peticiones deberán de seguir funcionando sin ningún problema. Recuerda que en routes/messages.js aún estamos utilizando una simulación de datos en memoria, por lo que las operaciones no están interactuando directamente con MongoDB.

— Implementando mongodb en las rutas

El siguiente paso es actualizar las peticiones para que interactúen con la base de datos MongoDB.

Vamos a comenzar con la petición más sencilla. Abre el archivo src/routes/messages.js y actualiza la ruta de la siguiente manera:

// Obtener todos los mensajes
router.get('/', async (req, res) => {
const messages = await req.context.collections.messages.find().toArray();
return res.json(messages);
});

Para seguir las buenas prácticas en el manejo de errores y asegurarnos de que los errores se gestionen correctamente en toda la aplicación, podemos mejorar el código añadiendo un mejor cacheo de errores y controlarlo de forma centralizada:

// Obtener todos los mensajes
router.get('/', async (req, res, next) => {
try {
const messages = await req.context.collections.messages.find().toArray();
return res.json(messages);
} catch (er) {
const error = new Error(
'Error al obtener los mensajes de la base de datos'
);
error.status = 500;
return next(error);
}
});

Donde:

  • req.context.collections.messages— Es la referencia a la colección messages de MongoDB que almacenamos previamente en req.context en la app.js.
  • find() — Para obtener todos los mensajes desde MongoDB.
  • toArray() — Convierte el resultado de la consulta en un arreglo de objetos.
  • Si ocurre un error, lo capturamos en el bloque catch y lo enviamos al middleware de manejo de errores utilizando next(error).

Este cambio asegura que los mensajes ahora se obtengan directamente de MongoDB en lugar de la simulación en memoria.

Vamos a continuar con la petición para obtener un mensaje específico por su messageId. Abre el archivo src/routes/messages.js y actualiza la siguiente ruta:

// Obtener un mensaje específico por ID
router.get('/:messageId', async (req, res, next) => {
const { messageId } = req.params;
try {
const myid = new req.context.ObjectId(messageId);
const message = await req.context.collections.messages.findOne({
_id: myid,
});

if (!message) {
const error = new Error(`Mensaje con ID ${messageId} no encontrado`);
error.status = 404;
return next(error);
}

return res.json(message);
} catch (er) {
const error = new Error(
`Error al obtener el mensaje con ID ${messageId} desde la base de datos`
);
error.status = 500;
return next(error);
}
});

Donde:

  • req.params — Se extrae el parámetro messageId de la URL y se utiliza para buscar el mensaje específico.
  • req.context.ObjectId — Para asegurar MongoDB reconozca el valor como un objeto de tipo ObjectId, en lugar de una cadena normal.
  • req.context.collections.messages— Es la referencia a la colección messages de MongoDB que almacenamos previamente en req.context en la app.js.
  • findOne() — Para buscar el mensaje que tenga el _id igual al messageId.

A continuación, vamos a implementar la lógica para guardar un nuevo mensaje. Abre el archivo src/routes/messages.js y actualiza la siguiente ruta:

// Agregar un nuevo mensaje
router.post('/', async (req, res, next) => {
try {
const { message } = req.body;

if (!message) {
const error = new Error('La propiedad "message" es requerida');
error.status = 404;
return next(error);
}

// Crear el nuevo mensaje
const newMessage = { message };
// Insertar el mensaje en la colección
const result = await req.context.collections.messages.insertOne(newMessage);

// Validar si la inserción fue exitosa
if (result.insertedCount === 0) {
const error = new Error('Error al guardar el mensaje');
error.status = 500;
return next(error);
}

return res.status(201).json(newMessage);
} catch (er) {
const error = new Error('Error al agregar el mensaje a la base de datos');
error.status = 500;
return next(error);
}
});

Donde:

  • req.body — Cuerpo de la solicitud para extraer la propiedad message.
  • req.context.collections.messages— Es la referencia a la colección messages de MongoDB que almacenamos previamente en req.context en la app.js.
  • insertOne() — Para guardar el nuevo mensaje en la colección messages.
  • insertedCount — Para verificar si el mensaje fue correctamente insertado. Si no lo fue, retrona 0.

Finalmente, vamos a continuar con la petición para eliminar un mensaje específico por su messageId. Abre el archivo src/routes/messages.js y actualiza la siguiente ruta:

// Eliminar un mensaje por ID
router.delete('/:messageId', async (req, res, next) => {
const { messageId } = req.params;

try {
const myid = new req.context.ObjectId(messageId);
// Buscar y eliminar el mensaje
const result = await req.context.collections.messages.deleteOne({
_id: myid,
});

if (result.deletedCount === 0) {
const error = new Error(`Mensaje con ID ${messageId} no encontrado`);
error.status = 404;
return next(error);
}

return res.send(`Mensaje con ID ${messageId} eliminado.`);
} catch (err) {
return next(
new Error(
`Error al eliminar el mensaje de la base de datos con ID ${messageId}`
)
);
}
});

Donde:

  • req.params — Se extrae el parámetro messageId de la URL y se utiliza para buscar el mensaje específico.
  • req.context.ObjectId — Para asegurar MongoDB reconozca el valor como un objeto de tipo ObjectId, en lugar de una cadena normal.
  • req.context.collections.messages— Es la referencia a la colección messages de MongoDB que almacenamos previamente en req.context en la app.js.
  • deleteOne() — Para eliminar el mensaje en la colección messages que coincida con el id proporcionado.
  • deletedCount — Para verificar si el mensaje fue correctamente eliminado. Si no lo fue, retrona 0.

— Validando las respuestas

Si has seguido los pasos correctamente, las peticiones deberán de funcionar como la siguiente animación.

— Descarga el código

Github[branch: with-mongodb][ref].

Conclusión

Has aprendido a levantar un servidor desde cero utilizando Node.js y Express, además de aplicar buenas prácticas como el uso de variables de entorno y el ruteo modular. También has logrado conectar tu aplicación a MongoDB de manera eficiente con mongodb, lo que te fue posible crear, leer, actualizar y eliminar (CRUD) registros de una base de datos.

--

--

No responses yet