En este proyecto aprenderás a crear y gestionar clases de personajes para tu MMORPG usando Node.js y Express. Construirás una API que te permitirá crear, leer, actualizar y eliminar clases, al igual que en una ‘raid’ donde un equipo se prepara para enfrentarse a grandes desafíos.
Una ‘raid’ en los MMORPGs es una misión épica en la que varios jugadores se unen para vencer enemigos poderosos.
Para enfrentar las ‘raids’ con éxito, necesitaremos formar un equipo compuesto por diferentes clases, cada una con su función única. Por ejemplo:
Cada clase será esencial para el éxito del equipo. ¡Vamos a por ello!
uuid
Para instalar la librería uuid
, que nos ayudará a generar identificadores únicos (UUIDs) para nuestras clases de personajes, puedes ejecutar el siguiente comando en tu terminal:
npm installuuid
Esto instalará la librería en tu proyecto y te permitirá usarla en el código.
A continuación, te explico brevemente cómo funciona el código de las rutas en Express para gestionar las clases de los héroes.
Las clases de personajes están definidas en el arreglo classes
. Cada clase tiene propiedades como id
, name
, description
, level
, healthPoints
, manaPoints
, attack
, defense
, skills
y weapon
. Cada clase se identifica con un UUID generado por la librería uuid.
El siguiente fragmento de código es parte del archivo router que gestiona las rutas:
const express = require('express');
const { v4: uuidv4 } = require('uuid');
const router = express.Router();
let classes = [
{
id: uuidv4(),
name: 'Warrior',
description: 'Fierce fighter with high strength. ⚔️',
level: 1,
healthPoints: 150,
manaPoints: 30,
attack: 20,
defense: 15,
skills: ['Sword Mastery', 'Shield Block'],
weapon: 'Sword',
},
{
id: uuidv4(),
name: 'Mage',
description: 'Master of magic and elemental forces. 🧙🏻♂',
level: 1,
healthPoints: 80,
manaPoints: 150,
attack: 25,
defense: 10,
skills: ['Fireball', 'Arcane Shield'],
weapon: 'Staff',
},
{
id: uuidv4(),
name: 'Archer',
description: 'Skilled with a bow and arrows. 🏹',
level: 1,
healthPoints: 100,
manaPoints: 50,
attack: 18,
defense: 12,
skills: ['Arrow Barrage', 'Eagle Eye'],
weapon: 'Bow',
},
{
id: uuidv4(),
name: 'Priest',
description: 'Healer and support for the team. ✨',
level: 1,
healthPoints: 90,
manaPoints: 120,
attack: 12,
defense: 18,
skills: ['Healing Touch', 'Holy Shield'],
weapon: 'Mace',
},
];
El método GET / se utiliza para obtener todos los héroes disponibles en el equipo. Esto es útil para saber cuántos héroes tienes en tu “raid” y qué clases forman parte de tu equipo.
Para ello usamos el res.json(classes);
para traernos desde nuestra variable classes
los datos y los enviamos al cliente.
router.get('/', async (req, res) => {
try {
res.json(classes);
} catch (error) {
res.status(500).json({ message: 'Error al obtener las clases', error: error.message });
}
});
Cuando se realiza una petición GET /, el servidor responde con un arreglo de objetos que representan las clases de los héroes.
Ejemplo de respuesta:
[
{
"id": "1cceba61-b865-4ad0-bf69-827f9a90bde6",
"name": "Warrior",
"description": "Fierce fighter with high strength. ⚔️",
"level": 1,
"healthPoints": 150,
"manaPoints": 30,
"attack": 20,
"defense": 15,
"skills": ["Sword Mastery", "Shield Block"],
"weapon": "Sword"
},
{
"id": "95b24be1-cfe6-48c6-8b4d-245b8c497a09",
"name": "Mage 🧙🏻♂",
"description": "Master of magic and elemental forces.",
"level": 1,
"healthPoints": 80,
"manaPoints": 150,
"attack": 25,
"defense": 10,
"skills": ["Fireball", "Arcane Shield"],
"weapon": "Staff"
},
....
]
Para crear un nuevo héroe, envía una solicitud POST a /api/heroes
con los datos del héroe en el cuerpo de la solicitud (req.body)
. Usamos uuid
para generar un ID único para cada héroe, similar a un documento de identidad. Luego, con el método push
, añadimos el nuevo héroe al arreglo classes
que guarda las clases en memoria.
Nota: Al estar en memoria, los héroes no persistirán si reinicias la aplicación.
router.post('/', async (req, res) => {
try {
const {
name,
description,
level,
healthPoints,
manaPoints,
attack,
defense,
skills,
weapon,
} = req.body;
const newClass = {
id: uuidv4(),
name,
description,
level,
healthPoints,
manaPoints,
attack,
defense,
skills,
weapon,
};
classes.push(newClass);
res.status(201).json(newClass);
} catch (error) {
res
.status(500)
.json({ message: 'Error al crear la clase', error: error.message });
}
});
Ejemplo de respuesta:
{
"id": "ca9301c4-7bea-44f7-a669-118d67dc5f5b",
"name": "Rogue",
"description": "Sneaky and quick, master of stealth. 🧝🏼",
"level": 1,
"healthPoints": 110,
"manaPoints": 40,
"attack": 22,
"defense": 14,
"skills": [
"Backstab",
"Invisibility"
],
"weapon": "Dagger"
}
Si quieres obtener los detalles de un héroe específico, puedes hacer una petición GET /:id
, donde id
es el UUID
del héroe que deseas consultar. Esto te devolverá los detalles completos de esa clase.
Para lograrlo, usamos req.params.id
para extraer el id
del héroe directamente desde la ruta de la solicitud. Luego, usamos el método find()
en el arreglo classes
para buscar el héroe correspondiente. Dentro del find()
, empleamos una arrow function(cls) => cls.id === classId
, que compara el id
de cada héroe (cls.id
) con el classId
que obtenemos de la ruta.
Si la condición es verdadera, es decir, si el id
del héroe coincide con el classId
, el método find()
devolverá ese héroe como un objeto completo. Si no se encuentra ningún héroe con ese id
, el resultado será undefined
. Lo que nos devolvera Clase no encontrada
.
router.get('/:id', async (req, res) => {
try {
const classId = req.params.id;
const classItem = classes.find((cls) => cls.id === classId);
if (classItem) {
res.json(classItem);
} else {
res.status(404).json({ message: 'Clase no encontrada' });
}
} catch (error) {
res
.status(500)
.json({ message: 'Error al obtener la clase', error: error.message });
}
});
Ejemplo de solicitud:
GET /api/heroes/ca9301c4-7bea-44f7-a669-118d67dc5f5b
Ejemplo de respuesta:
{
"id": "ca9301c4-7bea-44f7-a669-118d67dc5f5b",
"name": "Rogue",
"description": "Sneaky and quick, master of stealth.",
"level": 1,
"healthPoints": 110,
"manaPoints": 40,
"attack": 22,
"defense": 14,
"skills": [
"Backstab",
"Invisibility"
],
"weapon": "Dagger"
}
Supón que necesitas actualizar un atributo específico de un héroe, como cambiar el nivel de un Rogue
. En lugar de usar PUT
(que reemplaza todo el objeto), utilizamos PATCH
, ya que este método permite modificar solo una parte del objeto, como el nivel o los puntos de salud, sin necesidad de enviar toda la información nuevamente.
En este proceso, usamos un id
específico para actualizar solo el héroe que queremos. Como es una modificación parcial, extraemos los datos a actualizar a través de req.body
.
Primero, buscamos el índice del héroe que queremos modificar. Utilizamos findIndex()
para localizar el índice dentro del arreglo classes
. Si encontramos el héroe (es decir, si el índice es diferente de -1), actualizamos sus datos. Si no lo encontramos, devolvemos un error indicando que el héroe no existe.
Para realizar la actualización, usamos la siguiente línea de código:
classes[classIndex] = { ...classes[classIndex], ...updates };
La línea classes[classIndex] = { ...classes[classIndex], ...updates };
hace lo siguiente:
...classes[classIndex]
: Utiliza el operador de expansión (...
) también conocido como desestructuración de objetos
, para copiar todas las propiedades del héroe original que se encuentra en classes[classIndex]
. Esto significa que estamos tomando el objeto del héroe tal como está antes de la actualización.
...updates
: Toma las propiedades que vienen en req.body
, que son las que deseamos actualizar. Estas propiedades pueden ser, por ejemplo, el nivel o puntos de salud de un héroe.
Al combinar ambos con el operador de expansión (...
), se hace lo siguiente:
updates
), dejando intactas las demás propiedades que no han sido cambiadas.Esto permite realizar una actualización parcial del objeto, sin alterar las propiedades que no se desean modificar.
Finalmente, si el héroe se encuentra, se devuelve el objeto actualizado. Si no, se devuelve un error indicando que el héroe no fue encontrado.
router.patch('/:id', async (req, res) => {
try {
const classId = req.params.id;
const updates = req.body;
const classIndex = classes.findIndex((cls) => cls.id === classId);
if (classIndex !== -1) {
// Actualizar parcialmente las propiedades
classes[classIndex] = { ...classes[classIndex], ...updates };
res.json(classes[classIndex]);
} else {
res.status(404).json({ message: 'Clase no encontrada' });
}
} catch (error) {
res
.status(500)
.json({ message: 'Error al actualizar la clase', error: error.message });
}
});
Ejemplo de solicitud:
PATCH /api/heroes/ca9301c4-7bea-44f7-a669-118d67dc5f5b
Supongomos que nuestro Rogue
consiguio una poción de experiencia que lo llevara nivel 100. ¡Veamos que pasa!
Cuerpo de la solicitud:
{
"level": 100
}
Ejemplo de respuesta:
{
"id": "ca9301c4-7bea-44f7-a669-118d67dc5f5b",
"name": "Rogue",
"description": "Sneaky and quick, master of stealth. 🧝🏼",
"level": 100, // ----> ¡y mira subimos de nivel!
"healthPoints": 110,
"manaPoints": 40,
"attack": 22,
"defense": 14,
"skills": [
"Backstab",
"Invisibility"
],
"weapon": "Dagger"
}
Utilizamos PATCH en lugar de PUT porque PATCH está diseñado para actualizaciones parciales, lo que significa que solo se actualizan los atributos que queremos modificar, dejando el resto intacto. Por el contrario, PUT reemplaza completamente el objeto, lo que no sería eficiente si solo queremos actualizar un atributo pequeño. Pero esto es acorde a el criterio de cada desarrollador sientete libre en usar PUT si asi lo deseas.
En este proyecto decidimos que el ladrón (Rogue), aunque es útil en algunas situaciones, es una mala opción para formar un equipo balanceado, ya que el robo está mal visto en la mayoría de las “raids”. Por eso, decidimos eliminarlo utilizando el método DELETE.
Finalmente, el método DELETE elimina el objeto del arreglo usando splice()
, una función de JavaScript que remueve un elemento en un índice específico.
Cabe Recalcar que lo que estamos haciendo es considerado
como una Eliminación lógica.
Nota: Existen dos tipos de eliminación en el mundo real: la eliminación física y la eliminación lógica.
true
a false
), en lugar de borrarlo por completo.Cuando desarrollamos APIs, es importante considerar que la eliminación lógica es más común y recomendable que la eliminación física, especialmente en aplicaciones donde los datos eliminados pueden necesitar ser recuperados más tarde o donde es necesario llevar un historial de los cambios.
Imagina un juego MMORPG donde un jugador quiere eliminar un héroe de su equipo. En lugar de eliminarlo físicamente de la base de datos, podríamos marcar al héroe como “inactivo” cambiando un atributo como isActive: false
. De esta forma, el héroe no aparecerá en el juego, pero aún estará presente en la base de datos para referencias futuras o análisis.
Este enfoque de eliminación lógica permite realizar auditorías o recuperar datos más fácilmente en el futuro.
router.delete('/:id', async (req, res) => {
try {
const classId = req.params.id;
const classIndex = classes.findIndex((cls) => cls.id === classId);
if (classIndex !== -1) {
const deletedClass = classes.splice(classIndex, 1);
res.json({ message: 'Clase eliminada', deletedClass });
} else {
res.status(404).json({ message: 'Clase no encontrada' });
}
} catch (error) {
res
.status(500)
.json({ message: 'Error al eliminar la clase', error: error.message });
}
});
Ejemplo de solicitud:
DELETE /api/heroes/ca9301c4-7bea-44f7-a669-118d67dc5f5b
Al realizar esta petición, el héroe específico será eliminado del equipo.
Ejemplo de respuesta:
{
"message": "Clase eliminada",
"deletedClass": {
"id": "some-uuid",
"name": "Rogue",
"description": "Sneaky and quick, master of stealth. 🗡️",
"level": 1,
"healthPoints": 110,
"manaPoints": 40,
"attack": 22,
"defense": 14,
"skills": ["Backstab", "Invisibility"],
"weapon": "Dagger"
}
}
Si deseas probar la API, te dejo el repositorio en GitHub y un archivo de Postman para que puedas realizar las pruebas fácilmente.
¿Qué es Postman?
Postman es una herramienta muy útil para probar APIs. Permite realizar solicitudes HTTP, inspeccionar respuestas, y mucho más, lo que facilita la prueba y depuración de APIs durante el desarrollo.
Clona el repositorio:
git clonehttps://github.com/LuckyDg/api_phiz_heroes.git
Instala las dependencias: Entra en la carpeta del proyecto y ejecuta:
npm install
Ejecuta la API en modo desarrollo o Producción(Tu escoges una de ambas): Para ejecutar la API en modo desarrollo (con recarga automática con ayuda de nodemon), usa:
Modo desarrollo:
npm run dev
Modo producción:
npm run start
Mmorpg_Api_Platzi_Node.postman_collection.json
.¡Buena suerte en tu próxima Raid!
Saludos.
increible!, imagina que aprendieras POO, la factorización de ese codigo seria increible