En este módulo, aprenderás a construir una API de comercio electrónico utilizando Node.js y Express. Este proyecto te permitirá aplicar muchos de los conceptos que has aprendido a lo largo del curso, como la gestión de rutas, la autenticación, la interacción con bases de datos y más.
Objetivos del Módulo
- Configurar un proyecto de Node.js con Express.
- Implementar operaciones CRUD para productos.
- Gestionar usuarios y autenticación.
- Manejar carritos de compra y pedidos.
- Implementar seguridad básica y validación de datos.
Estructura del Proyecto
Antes de comenzar, es útil tener una estructura clara del proyecto. Aquí hay una posible estructura de directorios para nuestra API de comercio electrónico:
ecommerce-api/ ├── config/ │ └── db.js ├── controllers/ │ ├── authController.js │ ├── productController.js │ ├── userController.js │ └── orderController.js ├── models/ │ ├── Product.js │ ├── User.js │ └── Order.js ├── routes/ │ ├── authRoutes.js │ ├── productRoutes.js │ ├── userRoutes.js │ └── orderRoutes.js ├── middleware/ │ └── authMiddleware.js ├── .env ├── app.js └── package.json
Paso 1: Configuración del Proyecto
1.1 Inicializar el Proyecto
Primero, crea una carpeta para tu proyecto y navega a ella en tu terminal. Luego, inicializa un nuevo proyecto de Node.js:
1.2 Instalar Dependencias
Instala las dependencias necesarias para el proyecto:
1.3 Configurar el Servidor
Crea un archivo app.js
y configura el servidor básico de Express:
// app.js const express = require('express'); const mongoose = require('mongoose'); const dotenv = require('dotenv'); // Configuración de variables de entorno dotenv.config(); const app = express(); // Middleware para parsear JSON app.use(express.json()); // Conexión a la base de datos mongoose.connect(process.env.MONGO_URI, { useNewUrlParser: true, useUnifiedTopology: true, }) .then(() => console.log('Conectado a la base de datos')) .catch((err) => console.error('Error al conectar a la base de datos', err)); // Rutas app.get('/', (req, res) => { res.send('API de Comercio Electrónico'); }); // Iniciar el servidor const PORT = process.env.PORT || 5000; app.listen(PORT, () => { console.log(`Servidor corriendo en el puerto ${PORT}`); });
1.4 Configurar Variables de Entorno
Crea un archivo .env
en la raíz del proyecto y añade las siguientes variables:
Paso 2: Modelos de Datos
2.1 Modelo de Usuario
Crea un archivo User.js
en la carpeta models
y define el esquema del usuario:
// models/User.js const mongoose = require('mongoose'); const bcrypt = require('bcryptjs'); const userSchema = new mongoose.Schema({ name: { type: String, required: true, }, email: { type: String, required: true, unique: true, }, password: { type: String, required: true, }, isAdmin: { type: Boolean, default: false, }, }, { timestamps: true, }); // Encriptar la contraseña antes de guardar el usuario userSchema.pre('save', async function (next) { if (!this.isModified('password')) { next(); } const salt = await bcrypt.genSalt(10); this.password = await bcrypt.hash(this.password, salt); }); const User = mongoose.model('User', userSchema); module.exports = User;
2.2 Modelo de Producto
Crea un archivo Product.js
en la carpeta models
y define el esquema del producto:
// models/Product.js const mongoose = require('mongoose'); const productSchema = new mongoose.Schema({ name: { type: String, required: true, }, description: { type: String, required: true, }, price: { type: Number, required: true, }, countInStock: { type: Number, required: true, }, imageUrl: { type: String, required: true, }, }, { timestamps: true, }); const Product = mongoose.model('Product', productSchema); module.exports = Product;
2.3 Modelo de Pedido
Crea un archivo Order.js
en la carpeta models
y define el esquema del pedido:
// models/Order.js const mongoose = require('mongoose'); const orderSchema = new mongoose.Schema({ user: { type: mongoose.Schema.Types.ObjectId, required: true, ref: 'User', }, orderItems: [ { name: { type: String, required: true }, qty: { type: Number, required: true }, image: { type: String, required: true }, price: { type: Number, required: true }, product: { type: mongoose.Schema.Types.ObjectId, required: true, ref: 'Product', }, }, ], shippingAddress: { address: { type: String, required: true }, city: { type: String, required: true }, postalCode: { type: String, required: true }, country: { type: String, required: true }, }, paymentMethod: { type: String, required: true, }, paymentResult: { id: { type: String }, status: { type: String }, update_time: { type: String }, email_address: { type: String }, }, taxPrice: { type: Number, required: true, default: 0.0, }, shippingPrice: { type: Number, required: true, default: 0.0, }, totalPrice: { type: Number, required: true, default: 0.0, }, isPaid: { type: Boolean, required: true, default: false, }, paidAt: { type: Date, }, isDelivered: { type: Boolean, required: true, default: false, }, deliveredAt: { type: Date, }, }, { timestamps: true, }); const Order = mongoose.model('Order', orderSchema); module.exports = Order;
Paso 3: Rutas y Controladores
3.1 Rutas de Autenticación
Crea un archivo authRoutes.js
en la carpeta routes
y define las rutas de autenticación:
// routes/authRoutes.js const express = require('express'); const { registerUser, loginUser } = require('../controllers/authController'); const router = express.Router(); router.post('/register', registerUser); router.post('/login', loginUser); module.exports = router;
3.2 Controlador de Autenticación
Crea un archivo authController.js
en la carpeta controllers
y define las funciones de autenticación:
// controllers/authController.js const User = require('../models/User'); const bcrypt = require('bcryptjs'); const jwt = require('jsonwebtoken'); // Registrar usuario const registerUser = async (req, res) => { const { name, email, password } = req.body; try { const userExists = await User.findOne({ email }); if (userExists) { return res.status(400).json({ message: 'El usuario ya existe' }); } const user = await User.create({ name, email, password, }); if (user) { res.status(201).json({ _id: user._id, name: user.name, email: user.email, token: generateToken(user._id), }); } else { res.status(400).json({ message: 'Datos de usuario no válidos' }); } } catch (error) { res.status(500).json({ message: 'Error del servidor' }); } }; // Iniciar sesión const loginUser = async (req, res) => { const { email, password } = req.body; try { const user = await User.findOne({ email }); if (user && (await bcrypt.compare(password, user.password))) { res.json({ _id: user._id, name: user.name, email: user.email, token: generateToken(user._id), }); } else { res.status(401).json({ message: 'Credenciales no válidas' }); } } catch (error) { res.status(500).json({ message: 'Error del servidor' }); } }; // Generar token JWT const generateToken = (id) => { return jwt.sign({ id }, process.env.JWT_SECRET, { expiresIn: '30d', }); }; module.exports = { registerUser, loginUser, };
3.3 Middleware de Autenticación
Crea un archivo authMiddleware.js
en la carpeta middleware
y define el middleware de autenticación:
// middleware/authMiddleware.js const jwt = require('jsonwebtoken'); const User = require('../models/User'); const protect = async (req, res, next) => { let token; if (req.headers.authorization && req.headers.authorization.startsWith('Bearer')) { try { token = req.headers.authorization.split(' ')[1]; const decoded = jwt.verify(token, process.env.JWT_SECRET); req.user = await User.findById(decoded.id).select('-password'); next(); } catch (error) { res.status(401).json({ message: 'No autorizado, token fallido' }); } } if (!token) { res.status(401).json({ message: 'No autorizado, no hay token' }); } }; module.exports = { protect };
3.4 Rutas de Productos
Crea un archivo productRoutes.js
en la carpeta routes
y define las rutas de productos:
// routes/productRoutes.js const express = require('express'); const { getProducts, getProductById, createProduct, updateProduct, deleteProduct } = require('../controllers/productController'); const { protect } = require('../middleware/authMiddleware'); const router = express.Router(); router.route('/') .get(getProducts) .post(protect, createProduct); router.route('/:id') .get(getProductById) .put(protect, updateProduct) .delete(protect, deleteProduct); module.exports = router;
3.5 Controlador de Productos
Crea un archivo productController.js
en la carpeta controllers
y define las funciones de productos:
// controllers/productController.js const Product = require('../models/Product'); // Obtener todos los productos const getProducts = async (req, res) => { try { const products = await Product.find({}); res.json(products); } catch (error) { res.status(500).json({ message: 'Error del servidor' }); } }; // Obtener producto por ID const getProductById = async (req, res) => { try { const product = await Product.findById(req.params.id); if (product) { res.json(product); } else { res.status(404).json({ message: 'Producto no encontrado' }); } } catch (error) { res.status(500).json({ message: 'Error del servidor' }); } }; // Crear nuevo producto const createProduct = async (req, res) => { const { name, description, price, countInStock, imageUrl } = req.body; try { const product = new Product({ name, description, price, countInStock, imageUrl, }); const createdProduct = await product.save(); res.status(201).json(createdProduct); } catch (error) { res.status(500).json({ message: 'Error del servidor' }); } }; // Actualizar producto const updateProduct = async (req, res) => { const { name, description, price, countInStock, imageUrl } = req.body; try { const product = await Product.findById(req.params.id); if (product) { product.name = name; product.description = description; product.price = price; product.countInStock = countInStock; product.imageUrl = imageUrl; const updatedProduct = await product.save(); res.json(updatedProduct); } else { res.status(404).json({ message: 'Producto no encontrado' }); } } catch (error) { res.status(500).json({ message: 'Error del servidor' }); } }; // Eliminar producto const deleteProduct = async (req, res) => { try { const product = await Product.findById(req.params.id); if (product) { await product.remove(); res.json({ message: 'Producto eliminado' }); } else { res.status(404).json({ message: 'Producto no encontrado' }); } } catch (error) { res.status(500).json({ message: 'Error del servidor' }); } }; module.exports = { getProducts, getProductById, createProduct, updateProduct, deleteProduct, };
Paso 4: Integrar Todo
4.1 Integrar Rutas en app.js
Actualiza app.js
para incluir las rutas de autenticación y productos:
// app.js const express = require('express'); const mongoose = require('mongoose'); const dotenv = require('dotenv'); const authRoutes = require('./routes/authRoutes'); const productRoutes = require('./routes/productRoutes'); // Configuración de variables de entorno dotenv.config(); const app = express(); // Middleware para parsear JSON app.use(express.json()); // Conexión a la base de datos mongoose.connect(process.env.MONGO_URI, { useNewUrlParser: true, useUnifiedTopology: true, }) .then(() => console.log('Conectado a la base de datos')) .catch((err) => console.error('Error al conectar a la base de datos', err)); // Rutas app.use('/api/auth', authRoutes); app.use('/api/products', productRoutes); app.get('/', (req, res) => { res.send('API de Comercio Electrónico'); }); // Iniciar el servidor const PORT = process.env.PORT || 5000; app.listen(PORT, () => { console.log(`Servidor corriendo en el puerto ${PORT}`); });
Conclusión
En este módulo, has aprendido a crear una API de comercio electrónico utilizando Node.js y Express. Has configurado el proyecto, definido modelos de datos, implementado rutas y controladores, y protegido las rutas con middleware de autenticación. Este proyecto te proporciona una base sólida para construir aplicaciones más complejas y escalables en el futuro.
Ejercicio Práctico
- Añadir Funcionalidad de Carrito de Compras: Implementa un modelo y controlador para gestionar el carrito de compras de los usuarios.
- Mejorar la Seguridad: Añade validación de datos y manejo de errores más robusto en las rutas y controladores.
- Implementar Pagos: Integra una pasarela de pagos como Stripe para procesar pagos en la API.
Solución del Ejercicio
Para añadir la funcionalidad de carrito de compras, puedes seguir un enfoque similar al utilizado para los productos y pedidos. Define un modelo Cart
, crea rutas y controladores para gestionar el carrito, y asegúrate de proteger las rutas con el middleware de autenticación.
¡Buena suerte y sigue practicando!
Curso de Node.js
Módulo 1: Introducción a Node.js
Módulo 2: Conceptos Básicos
Módulo 3: Sistema de Archivos y E/S
Módulo 4: HTTP y Servidores Web
- Creando un Servidor HTTP Simple
- Manejo de Solicitudes y Respuestas
- Sirviendo Archivos Estáticos
- Enrutamiento
Módulo 5: NPM y Gestión de Paquetes
- Introducción a NPM
- Instalación y Uso de Paquetes
- Creación y Publicación de Paquetes
- Versionado Semántico
Módulo 6: Framework Express.js
- Introducción a Express.js
- Configuración de una Aplicación Express
- Middleware
- Enrutamiento en Express
- Manejo de Errores
Módulo 7: Bases de Datos y ORMs
- Introducción a las Bases de Datos
- Usando MongoDB con Mongoose
- Usando Bases de Datos SQL con Sequelize
- Operaciones CRUD
Módulo 8: Autenticación y Autorización
- Introducción a la Autenticación
- Usando Passport.js
- Autenticación JWT
- Control de Acceso Basado en Roles
Módulo 9: Pruebas y Depuración
- Introducción a las Pruebas
- Pruebas Unitarias con Mocha y Chai
- Pruebas de Integración
- Depuración de Aplicaciones Node.js
Módulo 10: Temas Avanzados
- Módulo Cluster
- Hilos de Trabajo
- Optimización del Rendimiento
- Construcción de APIs RESTful
- GraphQL con Node.js
Módulo 11: Despliegue y DevOps
- Variables de Entorno
- Usando PM2 para la Gestión de Procesos
- Desplegando en Heroku
- Integración y Despliegue Continuos