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