Features
Docs
CLI
Benchmarks
Examples

© 2024 MoroJs

E-commerce API Example

A complete working e-commerce backend with product catalog, shopping cart, payment processing, order management, and inventory tracking. Copy and customize.

Complete Working Example

1import { createApp } from '@morojs/moro';
2import { z } from 'zod';
3
4const app = createApp({
5  cors: true,
6  compression: true,
7  helmet: true
8});
9
10// Product catalog endpoints
11app.get('/products', {
12  query: z.object({
13    category: z.string().optional(),
14    search: z.string().optional(),
15    minPrice: z.coerce.number().optional(),
16    maxPrice: z.coerce.number().optional(),
17    inStock: z.boolean().optional(),
18    page: z.coerce.number().default(1),
19    limit: z.coerce.number().max(50).default(20)
20  }),
21  handler: async ({ query, db }) => {
22    const products = await db.query(`
23      SELECT p.*, c.name as category_name, i.quantity as stock_quantity
24      FROM products p
25      LEFT JOIN categories c ON p.category_id = c.id
26      LEFT JOIN inventory i ON p.id = i.product_id
27      WHERE ($1::text IS NULL OR c.name ILIKE $1)
28        AND ($2::text IS NULL OR p.name ILIKE $2 OR p.description ILIKE $2)
29        AND ($3::numeric IS NULL OR p.price >= $3)
30        AND ($4::numeric IS NULL OR p.price <= $4)
31        AND ($5::boolean IS NULL OR ($5 = true AND i.quantity > 0))
32      ORDER BY p.created_at DESC
33      LIMIT $6 OFFSET $7
34    `, [
35      query.category ? `%${query.category}%` : null,
36      query.search ? `%${query.search}%` : null,
37      query.minPrice,
38      query.maxPrice,
39      query.inStock,
40      query.limit,
41      (query.page - 1) * query.limit
42    ]);
43    
44    return { success: true, data: products };
45  }
46});
47
48// Shopping cart endpoints
49app.get('/cart', {
50  middleware: [requireAuth],
51  handler: async ({ context, db }) => {
52    const cartItems = await db.query(`
53      SELECT ci.*, p.name, p.price, p.image_url
54      FROM cart_items ci
55      JOIN products p ON ci.product_id = p.id
56      WHERE ci.user_id = $1
57    `, [context.user.id]);
58    
59    const total = cartItems.reduce((sum, item) => sum + (item.price * item.quantity), 0);
60    
61    return {
62      success: true,
63      data: {
64        items: cartItems,
65        total,
66        itemCount: cartItems.length
67      }
68    };
69  }
70});
71
72app.post('/cart/items', {
73  middleware: [requireAuth],
74  body: z.object({
75    productId: z.string().uuid(),
76    quantity: z.number().positive().max(10)
77  }),
78  handler: async ({ body, context, db }) => {
79    // Check product availability
80    const product = await db.query(
81      'SELECT id, price FROM products WHERE id = $1',
82      [body.productId]
83    );
84    
85    if (!product[0]) {
86      return { success: false, error: 'Product not found' };
87    }
88    
89    // Check inventory
90    const inventory = await db.query(
91      'SELECT quantity FROM inventory WHERE product_id = $1',
92      [body.productId]
93    );
94    
95    if (!inventory[0] || inventory[0].quantity < body.quantity) {
96      return { success: false, error: 'Insufficient inventory' };
97    }
98    
99    // Add to cart (or update existing)
100    const cartItem = await db.query(`
101      INSERT INTO cart_items (user_id, product_id, quantity)
102      VALUES ($1, $2, $3)
103      ON CONFLICT (user_id, product_id)
104      DO UPDATE SET quantity = cart_items.quantity + $3
105      RETURNING *
106    `, [context.user.id, body.productId, body.quantity]);
107    
108    return { success: true, data: cartItem[0] };
109  }
110});
111
112// Checkout process
113app.post('/checkout', {
114  middleware: [requireAuth],
115  body: z.object({
116    shippingAddress: z.object({
117      street: z.string(),
118      city: z.string(),
119      state: z.string(),
120      zipCode: z.string(),
121      country: z.string()
122    }),
123    paymentMethod: z.string(), // Stripe payment method ID
124    couponCode: z.string().optional()
125  }),
126  handler: async ({ body, context, db }) => {
127    return await db.transaction(async (tx) => {
128      // Get cart items
129      const cartItems = await tx.query(
130        'SELECT ci.*, p.price FROM cart_items ci JOIN products p ON ci.product_id = p.id WHERE ci.user_id = $1',
131        [context.user.id]
132      );
133      
134      if (cartItems.length === 0) {
135        return { success: false, error: 'Cart is empty' };
136      }
137      
138      // Calculate totals
139      let subtotal = cartItems.reduce((sum, item) => sum + (item.price * item.quantity), 0);
140      let discount = 0;
141      
142      // Apply coupon if provided
143      if (body.couponCode) {
144        const coupon = await tx.query(
145          'SELECT * FROM coupons WHERE code = $1 AND active = true AND expires_at > NOW()',
146          [body.couponCode]
147        );
148        
149        if (coupon[0]) {
150          discount = coupon[0].type === 'percentage' 
151            ? subtotal * (coupon[0].value / 100)
152            : coupon[0].value;
153        }
154      }
155      
156      const total = subtotal - discount;
157      
158      // Create order
159      const order = await tx.query(`
160        INSERT INTO orders (user_id, subtotal, discount, total, status, shipping_address)
161        VALUES ($1, $2, $3, $4, 'pending', $5)
162        RETURNING *
163      `, [context.user.id, subtotal, discount, total, JSON.stringify(body.shippingAddress)]);
164      
165      // Create order items
166      for (const item of cartItems) {
167        await tx.query(`
168          INSERT INTO order_items (order_id, product_id, quantity, price)
169          VALUES ($1, $2, $3, $4)
170        `, [order[0].id, item.product_id, item.quantity, item.price]);
171        
172        // Update inventory
173        await tx.query(
174          'UPDATE inventory SET quantity = quantity - $1 WHERE product_id = $2',
175          [item.quantity, item.product_id]
176        );
177      }
178      
179      // Process payment
180      const paymentResult = await processPayment({
181        amount: total,
182        currency: 'usd',
183        paymentMethod: body.paymentMethod,
184        orderId: order[0].id
185      });
186      
187      if (!paymentResult.success) {
188        return { success: false, error: 'Payment failed' };
189      }
190      
191      // Update order status
192      await tx.query(
193        'UPDATE orders SET status = $1, payment_id = $2 WHERE id = $3',
194        ['paid', paymentResult.paymentId, order[0].id]
195      );
196      
197      // Clear cart
198      await tx.query('DELETE FROM cart_items WHERE user_id = $1', [context.user.id]);
199      
200      // Emit order created event
201      events.emit('order.created', { order: order[0], items: cartItems });
202      
203      return {
204        success: true,
205        data: {
206          order: order[0],
207          payment: paymentResult
208        }
209      };
210    });
211  }
212});
213
214app.listen(3000, () => {
215  console.log('E-commerce API running on http://localhost:3000');
216});

What This Does

Shopping Cart

  • • Add/remove items
  • • Quantity management
  • • Price calculations
  • • Cart persistence

Payment Processing

  • • Stripe integration
  • • Secure transactions
  • • Payment webhooks
  • • Refund handling

Order Management

  • • Order tracking
  • • Inventory management
  • • Shipping integration
  • • Order history

Key API Endpoints

Product Catalog

typescript

1// Get products with filtering
2GET /products?category=electronics&minPrice=100&maxPrice=1000&inStock=true
3
4// Response
5{
6  "success": true,
7  "data": [
8    {
9      "id": "uuid",
10      "name": "Product Name",
11      "price": 99.99,
12      "category_name": "Electronics",
13      "stock_quantity": 50
14    }
15  ]
16}

Shopping Cart

typescript

1// Get cart
2GET /cart
3Authorization: Bearer <token>
4
5// Add item to cart
6POST /cart/items
7Authorization: Bearer <token>
8{
9  "productId": "uuid",
10  "quantity": 2
11}
12
13// Response
14{
15  "success": true,
16  "data": {
17    "items": [...],
18    "total": 199.98,
19    "itemCount": 1
20  }
21}

Checkout

typescript

1// Process checkout
2POST /checkout
3Authorization: Bearer <token>
4{
5  "shippingAddress": {
6    "street": "123 Main St",
7    "city": "New York",
8    "state": "NY",
9    "zipCode": "10001",
10    "country": "USA"
11  },
12  "paymentMethod": "pm_xxx",
13  "couponCode": "SAVE10"
14}
15
16// Response
17{
18  "success": true,
19  "data": {
20    "order": {
21      "id": "uuid",
22      "total": 179.98,
23      "status": "paid"
24    },
25    "payment": {
26      "paymentId": "pi_xxx",
27      "status": "succeeded"
28    }
29  }
30}

Run the Example

Getting Started

typescript

1# Clone and setup
2git clone https://github.com/Moro-JS/examples.git
3cd examples/ecommerce-api
4
5# Install dependencies
6npm install
7
8# Setup environment
9cp .env.example .env
10# Add your Stripe keys and database URL
11
12# Setup database
13npm run db:setup
14npm run db:migrate
15npm run db:seed  # Load sample products
16
17# Start development
18npm run dev
19
20# Test the API:
21curl http://localhost:3000/products
22curl http://localhost:3000/categories
23
24# Available scripts:
25npm run dev          # Development server
26npm run build        # Build for production
27npm run start        # Production server
28npm run test         # Run tests
29npm run db:migrate   # Database migrations
30npm run db:seed      # Seed sample data

What You'll Learn

  • • E-commerce data modeling
  • • Payment processing with Stripe
  • • Inventory management
  • • Order workflow design
  • • Transaction handling
  • • Webhook processing

Next Steps