API Integration Guide
This guide explains how to use the new API-based data fetching in your Elite Events application.
Overview
The application now has two approaches for data management:
- Legacy (Static Data): Original Redux slices using static TypeScript data files
- API-Based (Database): New Redux slices and hooks that fetch from the MySQL database via REST API
You can gradually migrate components from static data to API-based data.
Quick Start
Start the Development Server
npm run dev
The API routes are automatically available at:
- Products:
http://localhost:3000/api/products - Categories:
http://localhost:3000/api/categories - Blog:
http://localhost:3000/api/blog - Cart:
http://localhost:3000/api/cart - Wishlist:
http://localhost:3000/api/wishlist
Using Custom Hooks (Recommended)
The easiest way to fetch data from the API is using custom hooks.
Fetch Products
import { useProducts } from "@/hooks/useProducts";
function ProductList() {
const { products, loading, error, pagination } = useProducts({
page: 1,
limit: 10,
categoryId: 2, // optional
minPrice: 10, // optional
maxPrice: 100, // optional
});
if (loading) return <div>Loading products...</div>;
if (error) return <div>Error: {error}</div>;
return (
<div>
{products.map((product) => (
<div key={product.id}>
<h3>{product.title}</h3>
<p>${product.discountedPrice}</p>
</div>
))}
<div>
Page {pagination.page} of {pagination.totalPages}
</div>
</div>
);
}
Fetch Categories
import { useCategories } from "@/hooks/useCategories";
function CategoryList() {
const { categories, loading, error } = useCategories(true); // true = include product count
if (loading) return <div>Loading categories...</div>;
if (error) return <div>Error: {error}</div>;
return (
<div>
{categories.map((category) => (
<div key={category.id}>
<h3>{category.title}</h3>
{category.productCount && <p>{category.productCount} products</p>}
</div>
))}
</div>
);
}
Fetch Blog Posts
import { useBlogPosts } from "@/hooks/useBlogPosts";
function BlogList() {
const { posts, loading, error, pagination } = useBlogPosts({
page: 1,
limit: 10,
search: "ecommerce", // optional
});
if (loading) return <div>Loading blog posts...</div>;
if (error) return <div>Error: {error}</div>;
return (
<div>
{posts.map((post) => (
<div key={post.id}>
<h2>{post.title}</h2>
<p>{post.excerpt}</p>
<small>{post.views} views</small>
</div>
))}
</div>
);
}
Using Redux with API (Advanced)
Cart Management with API
"use client";
import { useDispatch, useSelector } from "react-redux";
import { AppDispatch } from "@/redux/store";
import {
fetchCart,
addToCartAsync,
updateCartItemAsync,
removeFromCartAsync,
selectCartItems,
selectCartLoading,
selectCartError,
} from "@/redux/features/cartSliceApi";
import { useEffect } from "react";
function CartComponent() {
const dispatch = useDispatch<AppDispatch>();
const cartItems = useSelector(selectCartItems);
const loading = useSelector(selectCartLoading);
const error = useSelector(selectCartError);
// Fetch cart on component mount
useEffect(() => {
dispatch(fetchCart());
}, [dispatch]);
const handleAddToCart = (productId: number, quantity: number) => {
dispatch(addToCartAsync({ productId, quantity }));
};
const handleUpdateQuantity = (id: number, quantity: number) => {
dispatch(updateCartItemAsync({ id, quantity }));
};
const handleRemove = (id: number) => {
dispatch(removeFromCartAsync(id));
};
if (loading) return <div>Loading cart...</div>;
if (error) return <div>Error: {error}</div>;
return (
<div>
{cartItems.map((item) => (
<div key={item.id}>
<h3>{item.title}</h3>
<p>Quantity: {item.quantity}</p>
<button onClick={() => handleUpdateQuantity(item.id, item.quantity + 1)}>
+
</button>
<button onClick={() => handleUpdateQuantity(item.id, item.quantity - 1)}>
-
</button>
<button onClick={() => handleRemove(item.id)}>Remove</button>
</div>
))}
</div>
);
}
Wishlist Management with API
"use client";
import { useDispatch, useSelector } from "react-redux";
import { AppDispatch } from "@/redux/store";
import {
fetchWishlist,
addToWishlistAsync,
removeFromWishlistAsync,
selectWishlistItems,
selectWishlistLoading,
selectWishlistError,
} from "@/redux/features/wishlistSliceApi";
import { useEffect } from "react";
function WishlistComponent() {
const dispatch = useDispatch<AppDispatch>();
const wishlistItems = useSelector(selectWishlistItems);
const loading = useSelector(selectWishlistLoading);
const error = useSelector(selectWishlistError);
// Fetch wishlist on component mount
useEffect(() => {
dispatch(fetchWishlist());
}, [dispatch]);
const handleAddToWishlist = (productId: number) => {
dispatch(addToWishlistAsync(productId));
};
const handleRemove = (productId: number) => {
dispatch(removeFromWishlistAsync(productId));
};
if (loading) return <div>Loading wishlist...</div>;
if (error) return <div>Error: {error}</div>;
return (
<div>
{wishlistItems.map((item) => (
<div key={item.id}>
<h3>{item.title}</h3>
<p>${item.discountedPrice}</p>
<button onClick={() => handleRemove(item.id)}>Remove</button>
</div>
))}
</div>
);
}
Direct API Calls (Low-Level)
You can also use the API client functions directly without hooks or Redux:
import { getProducts, getProductById } from "@/lib/api/products";
import { getCategories } from "@/lib/api/categories";
import { addToCart, getCart } from "@/lib/api/cart";
async function example() {
// Get all products
const { products, pagination } = await getProducts({ page: 1, limit: 10 });
// Get single product
const product = await getProductById(1);
// Get categories
const categories = await getCategories();
// Add to cart
await addToCart(1, 2); // productId: 1, quantity: 2
// Get cart
const cart = await getCart();
}
Migration Strategy
Option 1: Gradual Migration (Recommended)
Keep existing components working with static data while gradually migrating to API:
- Start with non-critical pages (e.g., blog)
- Update one component at a time
- Test thoroughly before moving to next component
- Keep both old and new Redux slices during transition
Option 2: Complete Migration
Replace all static data fetching at once:
- Update
src/redux/store.tsto use new API slices - Update all components to use new hooks
- Remove old static data files (keep for backup)
File Structure
src/
├── lib/
│ ├── prisma.ts # Prisma client singleton
│ └── api/ # API client functions
│ ├── products.ts
│ ├── categories.ts
│ ├── cart.ts
│ ├── wishlist.ts
│ └── blog.ts
├── hooks/ # Custom React hooks
│ ├── useProducts.ts
│ ├── useCategories.ts
│ └── useBlogPosts.ts
├── redux/
│ └── features/
│ ├── cart-slice.ts # Legacy (static data)
│ ├── cart-slice-api.ts # New (API-based)
│ ├── wishlist-slice.ts # Legacy (static data)
│ └── wishlist-slice-api.ts # New (API-based)
└── app/
└── api/ # Next.js API routes
├── products/
├── categories/
├── cart/
├── wishlist/
└── blog/
Testing the API
Using Browser
Open these URLs in your browser:
- http://localhost:3000/api/products
- http://localhost:3000/api/categories
- http://localhost:3000/api/blog
Using curl
# Get all products
curl http://localhost:3000/api/products
# Get products with filters
curl "http://localhost:3000/api/products?page=1&limit=5&categoryId=1"
# Get single product
curl http://localhost:3000/api/products/1
# Get categories with product count
curl "http://localhost:3000/api/categories?includeProductCount=true"
# Get blog posts
curl http://localhost:3000/api/blog
Using Prisma Studio
View and edit database data directly:
npx prisma studio
Opens at http://localhost:5555
Error Handling
All API functions and hooks include error handling:
const { products, loading, error } = useProducts();
if (error) {
// Show error message to user
return <div className="error">Error: {error}</div>;
}
Redux slices also track errors:
const error = useSelector(selectCartError);
if (error) {
toast.error(error);
dispatch(clearError()); // Clear error after showing
}
Performance Considerations
Caching
The API routes don't currently implement caching. Consider adding:
- Client-side caching: Use React Query or SWR
- Server-side caching: Add Redis or Next.js cache headers
- Database caching: Prisma query result caching
Pagination
Always use pagination for large datasets:
const { products, pagination } = useProducts({
page: currentPage,
limit: 20, // Adjust based on your needs
});
Loading States
Show loading indicators for better UX:
if (loading) {
return <Skeleton count={10} />; // Use skeleton loading
}
Authentication (Coming Soon)
Cart and Wishlist APIs require authentication. Currently using placeholder userId = 1.
To add authentication:
- Install NextAuth.js
- Add session management
- Update API routes to use authenticated user ID
- Add protected route middleware
See DATABASE_MIGRATION_PLAN.md Phase 7 for details.
Troubleshooting
API returns empty data
- Check if database is seeded:
npx tsx test-db.ts - Verify database connection in
.env - Check Prisma Studio:
npx prisma studio
"Failed to fetch" errors
- Ensure dev server is running:
npm run dev - Check browser console for CORS issues
- Verify API route exists in
src/app/api/
TypeScript errors
- Run Prisma generate:
npx prisma generate - Restart TypeScript server in VS Code
- Check import paths use
@/alias
Next Steps
- ✅ Database setup complete
- ✅ API routes implemented
- ✅ API client utilities created
- ✅ Custom hooks created
- ✅ Redux slices with API integration
- ⏳ Add authentication (NextAuth.js)
- ⏳ Migrate components to use API
- ⏳ Add loading skeletons
- ⏳ Implement error boundaries
- ⏳ Add caching strategy
Refer to DATABASE_MIGRATION_PLAN.md for the complete roadmap.