RESTful API for jewelry e-commerce platform built with Express.js, TypeScript, MongoDB, and MercadoPago.
This is the backend API for Morango, a full-stack e-commerce platform providing:
- π User authentication (JWT + httpOnly cookies)
- π¦ Product catalog management
- π Order processing & fulfillment
- π³ MercadoPago payment processing with webhooks
- π€ Admin dashboard data endpoints
- π§ Transactional email (password reset, order confirmations)
- β° Background jobs (order expiry, stock reservations)
- ποΈ MongoDB persistence with Mongoose
See also: Frontend Repository | AI Agent Guide
- Node.js 18+ and npm
- MongoDB instance (local or Atlas)
- MercadoPago account for payment credentials
# Clone and install dependencies
npm install
# Set up environment variables
cp .env.example .env
# Edit .env with your configuration
# Start development server
npm run devServer runs on the configured PORT (default: 3001).
Create .env with:
# Server
PORT=3001
NODE_ENV=development
# Database
DATABASE_URL=mongodb://localhost:27017/morango
# or MongoDB Atlas: mongodb+srv://user:[email protected]/morango
# Authentication
JWT_SECRET=your_jwt_secret_key
JWT_EXPIRY=7d
# MercadoPago
MP_ACCESS_TOKEN=your_mp_access_token
MP_PUBLIC_KEY=your_mp_public_key
MP_WEBHOOK_SECRET=your_mp_webhook_secret
# Email
RESEND_API_KEY=your_resend_api_key
# CORS
FRONTEND_URL=http://localhost:3000server/
βββ src/
β βββ index.ts # Entry point
β βββ server.ts # Express app setup & routing
β βββ config/
β β βββ db.ts # MongoDB connection
β β βββ cors.ts # CORS configuration
β β βββ resend.ts # Resend API key configuration
β β βββ mercadopago.ts # Mercado pago initial config
β βββ routes/ # Route definitions & endpoints test suites
β β βββ __tests__ /
β β | βββ auth / # Auth endpoints tests
β β | βββ products / # Products endpoints tests
β β | βββ orders / # Orders ndpoints tests
β β | βββ payments / # Payments endpoints tests
β β βββ authRouter.ts # /api/auth/*
β β βββ productRouter.ts # /api/products/*
β β βββ orderRouter.ts # /api/orders/*
β β βββ paymentRouter.ts # /api/payments/*
β βββ controllers/ # Business logic
β β βββ authController.ts
β β βββ productController.ts
β β βββ orderController.ts
β β βββ paymentController.ts
β βββ models/ # Mongoose schemas
β β βββ User.ts
β β βββ Product.ts
β β βββ Order.ts
β β βββ Payment.ts
β β βββ Token.ts
β βββ middleware/
β β βββ auth.ts # JWT verification
β β βββ error.ts # Global error handler
β β βββ validation.ts # Request validation
β β βββ mercadopago.ts # Webhook validation
β βββ errors/ # Custom error classes
β β βββ custom-error.ts
β β βββ not-found.ts
β β βββ conflict-error.ts
β β βββ forbidden-error.ts
β β βββ ...
β βββ cron/ # Background jobs
β β βββ deleteExpired.ts # Expire old orders
β β βββ index.ts # Cron job common exports as initCrons function.
β β βββ expireOrders.ts # Delete expired
β βββ emails/ # Email templates & sending
β β βββ resetPassword.ts
β β βββ orderConfirmation.ts
β β βββ ...
β βββ types/ # TypeScript interfaces
β β βββ express.d.ts # Extend Express Request
β β βββ validators.ts
β βββ utils/ # Helper functions
β β βββ jwt.ts
β β βββ json.ts
β β βββ auth.ts
β β βββ payment.ts
β β βββ order.ts
β β βββ product.ts
β β βββ token.ts
β βββ tests/
β βββ setup.ts # Test DB setup (mongodb-memory-server)
β βββ __tests__/ # Test files
βββ tsconfig.json
βββ package.json
βββ .env.example# Development with hot reload
npm run dev
# Build TypeScript
npm run build
# Run compiled JavaScript
node dist/index.js
# Run tests (watch mode)
npm run tests
# Clear Jest cache
npm run tests-clear-cache- Framework: Express.js 5.x
- Language: TypeScript
- Database: MongoDB + Mongoose ODM
- Authentication: JWT (httpOnly cookies)
- Validation: express-validator
- Payment Processing: MercadoPago SDK
- Email: Resend API
- Password Hashing: bcrypt
- Background Jobs: node-cron
- HTTP Logging: morgan
- Testing: Jest + Supertest + mongodb-memory-server
Client Request
β
CORS Middleware
β
Body Parser
β
Route Handler
β
Validation Middleware (express-validator)
β
Auth Middleware (JWT verification)
β
Controller (Business Logic)
β
Mongoose Model (Database Query)
β
Response / Custom Error
β
Error Handler Middleware
β
Client Response-
User registers β
authController.createAccount()- Password hashed with bcrypt
- User created in MongoDB
- Email confirmation token sent
-
User logs in β
authController.login()- Email & password verified
- JWT token signed with
JWT_SECRET - Token stored in httpOnly cookie (secure, sameSite)
- Cookie sent to client
-
Subsequent requests
- Client includes cookie automatically
authmiddleware extracts & validates JWTreq.userpopulated for controllers- Protected routes check
req.userexistence
- Frontend initializes checkout β
CheckoutPaymentcomponent - User completes payment on MercadoPago
- MercadoPago sends webhook to
/api/payments/webhook mercadopagomiddleware validates signaturepaymentController.handleWebhook()processes:- Creates/updates Payment record
- Updates Order status: "Esperando Pago" β "Procesando"
- Commits stock reservation
- Cron job monitors order expiry (24h default)
All controllers throw custom error classes:
throw new NotFoundError("Order not found");
throw new ConflictError("Email already registered");
throw new ForbiddenError("Insufficient permissions");
throw new RequestValidationError([{ field: "email", message: "Invalid email" }]);
...Error middleware catches and formats responses:
{
"errors": [
{
"message": "Order Not Found"
}
]
}User:
- Email uniqueness constraint
- Password hashed with bcrypt
- JWT token refresh support
Product:
- Stock tracking
- Images stored in Cloudinary
- Pricing in CLP
Order:
- Status enum: "Esperando Pago", "Procesando", "En Transito", "Entregado", "Cancelado", "Orden Expirada"
- Stock reservation expires in 24h
- Payment linked via paymentId
-
Create Controller in
src/controllers/FeatureController.ts:export const getFeature = async (req: Request, res: Response) => { try { const feature = await Feature.findById(req.params.id).exec(); if (!feature) throw new NotFoundError("Feature not found"); res.json(feature); } catch (error) { throw error; // Error middleware handles } };
-
Create Router in
src/routes/featureRouter.ts:const router = express.Router(); router.get('/:id', validationMiddleware, featureController.getFeature); router.post('/', validateSchema, featureController.createFeature); export default router;
-
Register Router in
src/server.ts:app.use('/api/features', featureRouter);
Use express-validator for schema validation:
import { body, validationResult } from 'express-validator';
const validateCreateOrder = [
body('email').isEmail().normalizeEmail(),
body('total').isFloat({ gt: 0 }),
];
export const createOrder = [
...validateCreateOrder,
async (req: Request, res: Response) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
throw new RequestValidationError(errors.array());
}
// Process order...
}
];Require auth middleware for endpoints needing authentication. Example:
router.post('/orders', currentUser, requireAuth, orderController.createOrder);
router.get('/admin/orders', currentUser, requireAdmin, orderController.getOrdersAdmin);The currentUser middleware sets req.user if cookie available, if not then pass to the next middleware.
The requireAuth middleware should be called after currentUser and checks wether req.user has any info in it, if it does not, then throw a NotAuthorizedError.
The requireAdmin middleware works the same as requireAuth but checks that the role of the user info set in res.user has a role === "admin" to validate access to the admin resource, if not there then throw 403 ForbiddenError.
Use Resend API in src/emails/:
export const sendResetPasswordEmail = async (email: string, resetLink: string) => {
// Send via Resend
};- JWT Secrets: Use strong, random values (min 32 chars)
- Passwords: Bcrypt with salt rounds β₯ 10
- Cookies: httpOnly, secure, sameSite flags enabled
- CORS: Restrict to
FRONTEND_URLenvironment variable - Webhooks: Validate MercadoPago signature before processing
- Input Validation: Always validate & sanitize incoming data
- Rate Limiting: Consider implementing for login/register endpoints
Tests use Jest with mongodb-memory-server for isolated database testing:
npm run testsTest Setup:
src/tests/setup.tsinitializes in-memory MongoDB- Each test gets clean database
- No external dependencies needed
docker build -t morango-backend .
docker run -p 3001:3001 \
-e DATABASE_URL=mongodb+srv://user:[email protected]/morango \
-e JWT_SECRET=your_secret \
-e MP_ACCESS_TOKEN=your_token \
-e MP_PUBLIC_KEY=your_key \
-e MP_WEBHOOK_SECRET=your_secret \
-e RESEND_API_KEY=your_key \
morango-backend- Set
NODE_ENV=production - Use MongoDB Atlas URI
- Enable HTTPS/SSL
- Set secure cookie flags
- Implement rate limiting
- Set up logging aggregation
- MongoDB: Document database - connection via
Mongoose - MercadoPago: Payment processing - SDK integration for webhooks
- Resend: Email delivery service for transactional emails
- Cloudinary: Image hosting (frontend integration, referenced in DB)
- Language: All business logic uses English; UI/email templates in Spanish
- Currency: Chilean Peso (CLP) - stored and returned as numbers (no special formatting)
- Timezone: Ensure consistent UTC timestamps in MongoDB
- Stock Reservations: Automatically expire after 24 hours via cron job
- Order Status: Immutable transitions - validate status changes in controller
Made with