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:

mkdir ecommerce-api
cd ecommerce-api
npm init -y

1.2 Instalar Dependencias

Instala las dependencias necesarias para el proyecto:

npm install express mongoose dotenv bcryptjs jsonwebtoken

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:

MONGO_URI=mongodb://localhost:27017/ecommerce
PORT=5000
JWT_SECRET=your_jwt_secret

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

  1. Añadir Funcionalidad de Carrito de Compras: Implementa un modelo y controlador para gestionar el carrito de compras de los usuarios.
  2. Mejorar la Seguridad: Añade validación de datos y manejo de errores más robusto en las rutas y controladores.
  3. 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

Módulo 5: NPM y Gestión de Paquetes

Módulo 6: Framework Express.js

Módulo 7: Bases de Datos y ORMs

Módulo 8: Autenticación y Autorización

Módulo 9: Pruebas y Depuración

Módulo 10: Temas Avanzados

Módulo 11: Despliegue y DevOps

Módulo 12: Proyectos del Mundo Real

© Copyright 2024. Todos los derechos reservados