This content originally appeared on DEV Community and was authored by Nadim Chowdhury
To develop a comprehensive Virtual Shopping Mall using Next.js (App Router), Tailwind CSS, NestJS, PostgreSQL, and Prisma, we can break down the features and functionality based on the documents you provided and the requirements of the project:
1. Core Features
A. Front-End (3D Mall Interface)
-
3D Interface for Browsing Stores and Products:
- Develop a 3D model-based interface for users to explore stores within a virtual mall. Tools like Three.js (for 3D rendering) can integrate with Next.js.
- Users can navigate through different stores and products visually.
- Tailwind CSS will be used to style the components in a responsive manner.
-
Product Listings and Details:
- Provide product categories, search, and filtering options for easy product discovery.
- Product detail pages showing descriptions, images, and pricing options.
-
Interactive Shopping Experience:
- Users can add items to the cart and wishlist while exploring the virtual space.
- Enable 360-degree product views for detailed exploration.
B. Back-End (NestJS with PostgreSQL and Prisma)
-
Store Management:
- Create, update, and delete stores and products (CRUD operations).
- Each store owner should be able to manage their products, pricing, and inventory.
- Include bulk pricing options and inventory tracking.
-
Inventory and Order Management:
- Monitor stock availability and trigger alerts for low-stock items.
- Order tracking system integrated with status updates (pending, shipped, delivered).
-
User Authentication and Role Management:
- OTP-based login using services like AWS SNS or Firebase for authentication.
- JWT for session management, enabling role-based access control (store owner, admin, customer).
-
Notifications:
- Push notifications (via Firebase) and email notifications for order updates, discounts, and promotions.
- SMS notifications for order confirmations and OTP authentication (using services like Fast2SMS).
-
Payment Gateway Integration:
- Integration with payment processors (e.g., Stripe) for seamless checkout and payment handling.
- Support for both online payments and Cash on Delivery (COD).
C. Mobile App (React Native with Tailwind for UI)
-
Browse Virtual Stores:
- Users can explore stores on a mobile-optimized version of the virtual mall.
-
Shopping Cart and Wishlist:
- The ability to add products to a shopping cart or wishlist and synchronize it with the web platform.
-
Push Notifications:
- In-app notifications for order updates and promotional offers.
-
Checkout and Payments:
- Allow mobile users to proceed to checkout and make payments using integrated gateways.
-
Order Tracking:
- Track orders and delivery status within the app, with live updates on shipping status.
2. Feature Breakdown by Role
A. User/Customer
- Authentication (OTP-based login).
- 3D mall navigation with search and filtering capabilities.
- Product wishlist and shopping cart.
- Checkout process with various payment options.
- Order tracking and history.
- Notifications (push, email, SMS) for order status and discounts.
B. Store Owner
- Store and product management (CRUD operations).
- Sales analytics and reporting.
- Inventory management (real-time stock updates).
- Dashboard with orders, sales, and returns.
- Notifications for low stock, new orders, and returns.
C. Delivery Personnel
- Delivery tracking and status update interface.
- Real-time order management (assigned deliveries).
- Notifications for new assignments and status changes.
D. Admin
- System-wide management of users, stores, products, and orders.
- Role and permissions management.
- Reports and analytics dashboard for sales, orders, and performance tracking.
- Ability to manage referral codes and agent-driven marketing efforts.
3. Additional Features
- Referral System: Referral code generation for agents, allowing tracking of user sign-ups and referrals.
- Analytics and Reporting: Detailed reports on sales, revenue, and inventory levels, downloadable in formats like CSV or Excel.
- Multi-Language and Localization: Adding i18n support for global reach.
- SEO and Performance Optimization: Next.js provides image optimization, lazy loading, and enhanced metadata management to improve SEO and user experience.
4. Technology Stack
- Next.js (App Router v14): For building the front-end with server-side rendering, static site generation, and optimized data fetching.
- NestJS: Back-end API with modular architecture.
- PostgreSQL + Prisma: Database management with a powerful ORM.
- Tailwind CSS: To handle responsive design and custom styling.
- React Native (for Mobile App): To build a seamless mobile experience.
- AWS (SNS, S3): For handling notifications and media storage.
5. Development Phases
- Phase 1: Initial setup of Next.js, Tailwind CSS, Prisma, and NestJS architecture.
- Phase 2: Develop 3D mall interface and basic store/product management functionalities.
- Phase 3: Integrate authentication, payment, and notifications.
- Phase 4: Mobile app development with synchronization with the web platform.
- Phase 5: Final optimization, testing, and deployment.
By following this structured approach, you will create a scalable and feature-rich Virtual Shopping Mall. Each part (front-end, back-end, and mobile) will be seamlessly integrated, providing a smooth user experience across platforms.
Let me know if you need further details on any specific part of the project!
For building a Virtual Shopping Mall using Next.js (App Router), Tailwind CSS, and integrating with the NestJS backend, a well-organized folder structure is critical to managing the complexity and scalability of the application. Below is a detailed file and folder structure for the frontend part of the project, along with explanations for each folder and file.
Full Folder Structure for Next.js (Frontend)
my-virtual-mall/
├── app/ # App Router specific folder
│ ├── (store)/ # Grouping route for store-related pages
│ │ ├── [storeId]/ # Dynamic route for individual stores
│ │ │ ├── page.tsx # Store details page
│ │ │ ├── layout.tsx # Layout for store-specific pages
│ │ │ ├── product/ # Nested route for individual product details
│ │ │ │ ├── [productId]/
│ │ │ │ │ └── page.tsx # Product details page
│ │ └── page.tsx # Store listing page (browse all stores)
│ ├── cart/
│ │ ├── page.tsx # Shopping cart page
│ ├── wishlist/
│ │ ├── page.tsx # Wishlist page
│ ├── checkout/
│ │ ├── page.tsx # Checkout page
│ ├── profile/ # User profile management
│ │ ├── page.tsx # Profile main page
│ ├── auth/ # Authentication routes (login, signup)
│ │ ├── login/
│ │ │ └── page.tsx # Login page
│ │ ├── signup/
│ │ │ └── page.tsx # Signup page
│ ├── layout.tsx # Global layout (header, footer)
│ ├── page.tsx # Home page (3D mall interface)
│ └── error.tsx # Global error page
├── components/ # Reusable components
│ ├── Header.tsx # Header component
│ ├── Footer.tsx # Footer component
│ ├── ProductCard.tsx # Product card component (used in lists)
│ ├── StoreCard.tsx # Store card component (used in store list)
│ ├── CartItem.tsx # Cart item component (used in cart)
│ └── WishlistItem.tsx # Wishlist item component (used in wishlist)
├── styles/ # Global and component-specific styles
│ ├── globals.css # Global CSS (importing Tailwind styles)
│ └── components.css # Styles for specific components
├── lib/ # Utility functions and hooks
│ ├── fetcher.ts # Helper for fetching data (with SWR or Fetch API)
│ ├── auth.ts # Authentication-related helpers (JWT, cookies)
│ └── prisma.ts # Prisma client (if needed on the frontend)
├── public/ # Public assets (images, videos, fonts)
│ ├── images/
│ │ └── logo.png # Example logo
├── prisma/ # Prisma schema (optional if using Prisma on frontend)
│ └── schema.prisma # Prisma schema definition
├── middleware.ts # Middleware for handling redirects, authentication
├── tailwind.config.js # Tailwind CSS configuration
├── postcss.config.js # PostCSS configuration (used by Tailwind)
├── tsconfig.json # TypeScript configuration
├── next.config.js # Next.js configuration
├── package.json # Dependencies and scripts
└── README.md # Project documentation
Detailed Breakdown of the Folder Structure
1. app/
Directory
This is the core of your Next.js app, using the App Router. It contains all your routes, layouts, and pages.
-
(store)/: A grouping route for all store-related pages. This uses nested routing to handle stores and their products.
-
[storeId]/
: Dynamic routing for individual store pages. -
product/[productId]/page.tsx
: Nested route for viewing individual product details.
-
- cart/: Page for the user's shopping cart.
- wishlist/: Page for the user's wishlist.
- checkout/: Checkout page where users complete their purchases.
- profile/: A section where users manage their profile information (addresses, orders, etc.).
- auth/: Authentication-related pages, such as login and signup.
- layout.tsx: Global layout for the app, which contains common elements like the header and footer.
- page.tsx: The entry point for the homepage, which could display the 3D mall interface (integrating with libraries like Three.js).
- error.tsx: A global error page for handling 404s and other errors.
2. components/
Directory
Contains reusable React components, which are used throughout the app. Each component is modular and reusable.
- Header.tsx: The global header, including navigation links.
- Footer.tsx: The global footer, including links to social media or company information.
- ProductCard.tsx: A reusable card component for displaying product information (used in product listings).
- StoreCard.tsx: A reusable card component for displaying store information.
- CartItem.tsx: A component to represent individual items in the cart.
- WishlistItem.tsx: A component to represent individual items in the wishlist.
3. styles/
Directory
This contains global styles and component-specific styles.
- globals.css: Your main global styles. Tailwind’s base styles should be imported here.
- components.css: Any additional styling for components that need specific styles beyond Tailwind’s utility classes.
4. lib/
Directory
Contains helper functions, utilities, and hooks that can be reused across the app.
-
fetcher.ts: A helper function to make API requests using
fetch
orSWR
for data fetching. - auth.ts: Helper functions for handling authentication (e.g., working with JWTs or cookies).
- prisma.ts: If Prisma is used on the frontend (for example, in server components), this would hold the Prisma client setup.
5. public/
Directory
Stores static files that can be served publicly, such as images, videos, or fonts.
- images/: A folder containing any static images for the application (e.g., logos, banners).
6. prisma/
Directory (Optional)
Contains Prisma schema if you need database interactions on the frontend or within server-side components.
- schema.prisma: The schema file for defining your database tables and relations.
7. middleware.ts
This is used for custom middleware, such as handling authentication checks, redirects, or other server-side operations that need to occur before rendering a page.
8. tailwind.config.js
Configuration file for Tailwind CSS. You define your theme, colors, fonts, and any custom utility classes here.
9. postcss.config.js
PostCSS configuration for handling CSS transformations, typically used by Tailwind.
10. tsconfig.json
TypeScript configuration file to define paths, types, and compiler options for the project.
11. next.config.js
Configuration file for Next.js to handle custom routing, Webpack modifications, or environment variables.
12. package.json
The core file for managing dependencies and scripts, such as running the development server or building the project.
Key Technologies and Libraries:
- Next.js (App Router): To handle both the server-side rendering and static generation with optimized routes.
- Tailwind CSS: For highly customizable and responsive UI components.
- React Three Fiber (optional): If using Three.js for rendering the 3D mall environment.
- Prisma: For database interactions, especially when querying the PostgreSQL database.
- SWR or React Query: For efficient data fetching and caching on the client side.
This structure will make your Next.js application modular, scalable, and maintainable as the project grows. Let me know if you need further elaboration on any part!
For the backend of your Virtual Shopping Mall, using NestJS, PostgreSQL, and Prisma, the folder structure needs to be modular, maintainable, and scalable. Below is a detailed folder and file structure for the backend, ensuring a clean separation of concerns for authentication, products, stores, users, and other key areas of the application.
Full Folder Structure for NestJS Backend
my-virtual-mall-backend/
├── src/
│ ├── auth/ # Authentication and Authorization logic
│ │ ├── dto/ # Data Transfer Objects (DTOs) for validation
│ │ │ ├── login.dto.ts
│ │ │ ├── signup.dto.ts
│ │ ├── auth.controller.ts # Controller for auth endpoints
│ │ ├── auth.module.ts # NestJS Module for auth
│ │ ├── auth.service.ts # Service for authentication logic
│ │ ├── jwt.strategy.ts # JWT Strategy for securing routes
│ │ ├── local.strategy.ts # Local Strategy for login
│ │ └── auth.guard.ts # Guards for protecting routes (e.g., RolesGuard)
│ ├── users/ # User-related logic
│ │ ├── dto/ # DTOs for user validation
│ │ │ ├── create-user.dto.ts
│ │ │ ├── update-user.dto.ts
│ │ ├── users.controller.ts # Controller for user endpoints
│ │ ├── users.module.ts # Users module
│ │ ├── users.service.ts # Service for user-related logic
│ │ ├── users.entity.ts # User entity definition (Prisma or TypeORM)
│ ├── products/ # Product management
│ │ ├── dto/ # DTOs for product validation
│ │ │ ├── create-product.dto.ts
│ │ │ ├── update-product.dto.ts
│ │ ├── products.controller.ts # Controller for product-related endpoints
│ │ ├── products.module.ts # Products module
│ │ ├── products.service.ts # Service for product-related logic
│ │ ├── product.entity.ts # Product entity definition (Prisma or TypeORM)
│ ├── stores/ # Store management logic
│ │ ├── dto/ # DTOs for store validation
│ │ │ ├── create-store.dto.ts
│ │ │ ├── update-store.dto.ts
│ │ ├── stores.controller.ts # Controller for store-related endpoints
│ │ ├── stores.module.ts # Stores module
│ │ ├── stores.service.ts # Service for store-related logic
│ │ ├── store.entity.ts # Store entity definition (Prisma or TypeORM)
│ ├── cart/ # Shopping cart management
│ │ ├── dto/ # DTOs for cart validation
│ │ │ ├── add-to-cart.dto.ts
│ │ ├── cart.controller.ts # Controller for cart management
│ │ ├── cart.module.ts # Cart module
│ │ ├── cart.service.ts # Cart service logic
│ │ ├── cart.entity.ts # Cart entity definition
│ ├── orders/ # Order processing and management
│ │ ├── dto/ # DTOs for order validation
│ │ │ ├── create-order.dto.ts
│ │ ├── orders.controller.ts # Controller for order-related endpoints
│ │ ├── orders.module.ts # Orders module
│ │ ├── orders.service.ts # Service for order-related logic
│ │ ├── order.entity.ts # Order entity definition
│ ├── payments/ # Payment processing logic
│ │ ├── dto/ # DTOs for payments
│ │ ├── payments.controller.ts # Payment endpoints
│ │ ├── payments.module.ts # Payment module
│ │ ├── payments.service.ts # Payment service logic (Stripe integration, etc.)
│ │ ├── payment.entity.ts # Payment entity definition
│ ├── notifications/ # Notifications (Push, Email, SMS)
│ │ ├── notifications.module.ts # Notification module
│ │ ├── notifications.service.ts # Service for sending notifications
│ ├── prisma/ # Prisma-related configuration and schema
│ │ ├── prisma.service.ts # Prisma client service
│ │ └── schema.prisma # Prisma schema definition
│ ├── common/ # Common utilities and guards
│ │ ├── decorators/ # Custom decorators (e.g., Role-based access)
│ │ ├── guards/ # Global guards (e.g., AuthGuard, RoleGuard)
│ │ ├── pipes/ # Custom pipes for validation/transformation
│ │ └── filters/ # Exception filters (e.g., HttpExceptionFilter)
│ ├── app.module.ts # Root module
│ ├── main.ts # Entry point for the application
├── config/ # Configurations (for env variables, database, etc.)
│ ├── app.config.ts # General app configuration
│ ├── database.config.ts # Database connection configuration (PostgreSQL)
│ └── jwt.config.ts # JWT configuration (secret, expiration)
├── test/ # Test files for unit and integration tests
│ ├── auth.service.spec.ts # Unit test for auth service
│ └── users.service.spec.ts # Unit test for user service
├── node_modules/ # External libraries and dependencies
├── .env # Environment variables (DB credentials, JWT secret)
├── .eslintrc.js # ESLint configuration
├── .prettierrc # Prettier configuration for code formatting
├── nest-cli.json # NestJS CLI configuration
├── package.json # Dependencies and scripts
├── tsconfig.json # TypeScript configuration
└── README.md # Project documentation
Detailed Breakdown of the Folder Structure
1. src/
Directory
This is the main source folder for all backend logic. It's where all your modules, controllers, services, and entity definitions reside.
-
auth/
: Handles authentication and authorization logic. This includes JWT-based authentication and role-based access control (guards).-
auth.controller.ts
: Manages user login and signup endpoints. -
auth.service.ts
: Contains business logic related to authentication, such as password validation, token creation, and verification. -
jwt.strategy.ts
: Defines the JWT strategy to secure routes that require authentication. -
local.strategy.ts
: For local username/password login strategies.
-
-
users/
: Manages user-related functionality, such as creating new users, updating profiles, fetching user data, etc.-
users.entity.ts
: Represents the User entity in the database, managed via Prisma or TypeORM. -
users.service.ts
: Contains business logic related to users.
-
-
products/
: Handles product management for the virtual mall. This includes creating, updating, and fetching product information.-
products.controller.ts
: Exposes endpoints for managing products. -
products.entity.ts
: Represents a product entity in the database.
-
-
stores/
: Handles the logic for creating and managing stores.-
stores.controller.ts
: Manages the store-related endpoints (creating a new store, updating store information). -
stores.entity.ts
: Represents a store entity in the database.
-
-
cart/
: Manages shopping cart operations.-
cart.controller.ts
: Provides endpoints for adding/removing items to/from the cart and viewing the cart. -
cart.entity.ts
: Represents the cart in the database, linking products to users.
-
-
orders/
: Handles order processing, including creating orders and managing order statuses.-
orders.controller.ts
: Manages order-related API endpoints. -
orders.entity.ts
: Represents orders in the database.
-
-
payments/
: Manages payment integrations (e.g., Stripe).-
payments.controller.ts
: Handles payment processing endpoints. -
payments.service.ts
: Business logic for integrating with external payment gateways.
-
-
notifications/
: Handles sending notifications (push, email, SMS).-
notifications.service.ts
: Contains logic to send notifications using Firebase, AWS SNS, or any other service.
-
-
prisma/
: Contains Prisma-specific configurations.-
schema.prisma
: The Prisma schema where all your entities are defined. -
prisma.service.ts
: Handles Prisma client initialization.
-
common/
: A shared utilities folder for common decorators, guards, pipes, and exception filters.
decorators/
: Custom decorators like @CurrentUser()
.
-
guards/
: Guard files, such as JWT guards, for protecting routes. -
pipes/
: Custom pipes for transforming and validating request data. -
filters/
: Exception filters for handling errors globally.
2. config/
Directory
This contains configuration files for the application, database, and any third-party integrations.
-
app.config.ts
: General app configuration, such as environment variables. -
database.config.ts
: PostgreSQL database configuration. -
jwt.config.ts
: JWT settings for authentication.
3. test/
Directory
This directory contains unit and integration tests for your application using Jest or another testing framework.
-
auth.service.spec.ts
: Unit tests for authentication services. -
users.service.spec.ts
: Unit tests for user services.
4. Environment Configuration
-
.env
: Environment variables like database credentials, JWT secret, API keys, etc.
5. NestJS and TypeScript Config Files
-
nest-cli.json
: Nest CLI configuration file. -
tsconfig.json
: TypeScript configuration for the backend project.
6. Code Formatting and Linting
-
.eslintrc.js
: ESLint configuration for maintaining code standards. -
.prettierrc
: Prettier configuration for automatic code formatting.
Key Technologies and Libraries:
- NestJS: Modular backend framework for handling API endpoints, business logic, and more.
- PostgreSQL: Relational database used with Prisma for managing store, product, user, and order data.
- Prisma ORM: Used for defining database schema, querying the database, and handling relations between entities.
- JWT (JsonWebToken): For authentication and session management.
- Stripe (or other): Payment integration for handling purchases.
This structure will help you maintain a clean, organized codebase as the application scales, making it easier to add new features and maintain existing ones. Let me know if you need any further clarifications!
Below is the full code for the structure you mentioned, specifically for the store pages, including dynamic routing for individual stores and products, along with the shopping cart page in a Next.js (App Router) project. This includes dynamic routing, product listing, and layouts for individual store pages.
Folder and Code Structure
1. app/(store)/[storeId]/page.tsx
(Store Details Page)
This file represents a dynamic page for each individual store. It fetches the store details and products based on the storeId
from the URL.
// app/(store)/[storeId]/page.tsx
import { use } from 'react';
import { useRouter } from 'next/router';
interface Product {
id: string;
name: string;
price: number;
image: string;
}
interface Store {
id: string;
name: string;
description: string;
products: Product[];
}
const fetchStoreData = async (storeId: string): Promise<Store> => {
const res = await fetch(`/api/stores/${storeId}`);
if (!res.ok) {
throw new Error('Failed to fetch store data');
}
return res.json();
};
const StorePage = () => {
const router = useRouter();
const { storeId } = router.query;
// Fetching store data
const storeData = use(() => fetchStoreData(storeId as string));
if (!storeData) return <p>Loading...</p>;
return (
<div className="container mx-auto p-4">
<h1 className="text-3xl font-bold mb-4">{storeData.name}</h1>
<p className="mb-4">{storeData.description}</p>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
{storeData.products.map((product) => (
<a key={product.id} href={`/store/${storeId}/product/${product.id}`} className="block border rounded-lg p-4 hover:shadow-lg">
<img src={product.image} alt={product.name} className="w-full h-48 object-cover rounded-lg mb-2" />
<h2 className="text-xl font-semibold">{product.name}</h2>
<p className="text-lg text-gray-500">${product.price.toFixed(2)}</p>
</a>
))}
</div>
</div>
);
};
export default StorePage;
2. app/(store)/[storeId]/layout.tsx
(Store Layout)
This layout is used to provide consistent structure, such as navigation, for store-related pages.
// app/(store)/[storeId]/layout.tsx
export default function StoreLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<div>
<header className="bg-gray-800 text-white p-4">
<nav className="container mx-auto">
<a href="/" className="text-2xl font-bold">Virtual Mall</a>
</nav>
</header>
<main className="container mx-auto py-6">
{children}
</main>
<footer className="bg-gray-800 text-white p-4 mt-6">
<p className="text-center">© 2024 Virtual Mall</p>
</footer>
</div>
);
}
3. app/(store)/[storeId]/product/[productId]/page.tsx
(Product Details Page)
This page dynamically fetches product details based on the storeId
and productId
.
// app/(store)/[storeId]/product/[productId]/page.tsx
import { use } from 'react';
import { useRouter } from 'next/router';
interface Product {
id: string;
name: string;
price: number;
description: string;
image: string;
}
const fetchProductData = async (storeId: string, productId: string): Promise<Product> => {
const res = await fetch(`/api/stores/${storeId}/products/${productId}`);
if (!res.ok) {
throw new Error('Failed to fetch product data');
}
return res.json();
};
const ProductPage = () => {
const router = useRouter();
const { storeId, productId } = router.query;
const productData = use(() => fetchProductData(storeId as string, productId as string));
if (!productData) return <p>Loading...</p>;
return (
<div className="container mx-auto p-4">
<div className="grid grid-cols-1 lg:grid-cols-2 gap-8">
<img src={productData.image} alt={productData.name} className="w-full h-96 object-cover rounded-lg" />
<div>
<h1 className="text-4xl font-bold mb-4">{productData.name}</h1>
<p className="text-2xl text-gray-500 mb-4">${productData.price.toFixed(2)}</p>
<p className="text-lg">{productData.description}</p>
<button className="bg-blue-500 text-white mt-6 py-2 px-4 rounded-lg hover:bg-blue-600">
Add to Cart
</button>
</div>
</div>
</div>
);
};
export default ProductPage;
4. app/(store)/page.tsx
(Store Listing Page)
This page lists all the available stores for users to browse.
// app/(store)/page.tsx
import { use } from 'react';
interface Store {
id: string;
name: string;
description: string;
image: string;
}
const fetchStores = async (): Promise<Store[]> => {
const res = await fetch('/api/stores');
if (!res.ok) {
throw new Error('Failed to fetch stores');
}
return res.json();
};
const StoreListPage = () => {
const stores = use(() => fetchStores());
if (!stores) return <p>Loading...</p>;
return (
<div className="container mx-auto p-4">
<h1 className="text-3xl font-bold mb-4">Browse Stores</h1>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
{stores.map((store) => (
<a key={store.id} href={`/store/${store.id}`} className="block border rounded-lg p-4 hover:shadow-lg">
<img src={store.image} alt={store.name} className="w-full h-48 object-cover rounded-lg mb-2" />
<h2 className="text-xl font-semibold">{store.name}</h2>
<p className="text-gray-500">{store.description}</p>
</a>
))}
</div>
</div>
);
};
export default StoreListPage;
5. app/cart/page.tsx
(Shopping Cart Page)
This page displays the items the user has added to the cart and provides checkout functionality.
// app/cart/page.tsx
import { use } from 'react';
interface CartItem {
id: string;
name: string;
price: number;
quantity: number;
image: string;
}
const fetchCartItems = async (): Promise<CartItem[]> => {
const res = await fetch('/api/cart');
if (!res.ok) {
throw new Error('Failed to fetch cart items');
}
return res.json();
};
const CartPage = () => {
const cartItems = use(fetchCartItems);
if (!cartItems || cartItems.length === 0) {
return <p>Your cart is empty</p>;
}
const total = cartItems.reduce((acc, item) => acc + item.price * item.quantity, 0);
return (
<div className="container mx-auto p-4">
<h1 className="text-3xl font-bold mb-4">Shopping Cart</h1>
<div className="grid grid-cols-1 gap-4">
{cartItems.map((item) => (
<div key={item.id} className="flex items-center border-b pb-4">
<img src={item.image} alt={item.name} className="w-24 h-24 object-cover rounded-lg" />
<div className="ml-4">
<h2 className="text-xl font-semibold">{item.name}</h2>
<p className="text-gray-500">${item.price.toFixed(2)} x {item.quantity}</p>
</div>
</div>
))}
</div>
<div className="mt-6">
<h2 className="text-2xl font-semibold">Total: ${total.toFixed(2)}</h2>
<button className="bg-green-500 text-white py-2 px-4 rounded-lg mt-4 hover:bg-green-600">
Proceed to Checkout
</button>
</div>
</div>
);
};
export default CartPage;
API Endpoints (Example)
You'll need to implement backend API endpoints to serve store and product data, as well as handle cart functionality. Here’s a basic idea of the
routes:
-
GET /api/stores
: Returns a list of stores. -
GET /api/stores/{storeId}
: Returns details of a specific store. -
GET /api/stores/{storeId}/products/{productId}
: Returns details of a specific product. -
GET /api/cart
: Fetch the user's shopping cart items. -
POST /api/cart
: Add an item to the user's cart.
Tailwind CSS Integration
Ensure you have Tailwind CSS set up in your project. You can add styles in globals.css
and Tailwind will take care of responsive, utility-based styling for your components.
# Install Tailwind CSS
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init
Your tailwind.config.js
should look something like this:
module.exports = {
content: [
'./app/**/*.{js,ts,jsx,tsx}',
'./components/**/*.{js,ts,jsx,tsx}',
],
theme: {
extend: {},
},
plugins: [],
}
Conclusion
This structure provides the necessary components and pages for your Virtual Shopping Mall's store and cart functionalities, including dynamic routing with Next.js App Router. You can build upon this foundation to integrate more features, such as authentication, payments, and notifications, as you progress. Let me know if you need further enhancements or explanations!
Below is the full code implementation for the additional pages you requested: Shopping Cart, Wishlist, Checkout, Profile, Login, and Signup in a Next.js (App Router) project.
Folder and Code Structure
1. app/cart/page.tsx
(Shopping Cart Page)
This page displays the items that the user has added to their cart and allows them to proceed to checkout.
// app/cart/page.tsx
import { use } from 'react';
interface CartItem {
id: string;
name: string;
price: number;
quantity: number;
image: string;
}
const fetchCartItems = async (): Promise<CartItem[]> => {
const res = await fetch('/api/cart');
if (!res.ok) {
throw new Error('Failed to fetch cart items');
}
return res.json();
};
const CartPage = () => {
const cartItems = use(fetchCartItems);
if (!cartItems || cartItems.length === 0) {
return <p>Your cart is empty</p>;
}
const total = cartItems.reduce((acc, item) => acc + item.price * item.quantity, 0);
return (
<div className="container mx-auto p-4">
<h1 className="text-3xl font-bold mb-4">Shopping Cart</h1>
<div className="grid grid-cols-1 gap-4">
{cartItems.map((item) => (
<div key={item.id} className="flex items-center border-b pb-4">
<img src={item.image} alt={item.name} className="w-24 h-24 object-cover rounded-lg" />
<div className="ml-4">
<h2 className="text-xl font-semibold">{item.name}</h2>
<p className="text-gray-500">${item.price.toFixed(2)} x {item.quantity}</p>
</div>
</div>
))}
</div>
<div className="mt-6">
<h2 className="text-2xl font-semibold">Total: ${total.toFixed(2)}</h2>
<button className="bg-green-500 text-white py-2 px-4 rounded-lg mt-4 hover:bg-green-600">
Proceed to Checkout
</button>
</div>
</div>
);
};
export default CartPage;
2. app/wishlist/page.tsx
(Wishlist Page)
This page shows the user's saved wishlist items.
// app/wishlist/page.tsx
import { use } from 'react';
interface WishlistItem {
id: string;
name: string;
price: number;
image: string;
}
const fetchWishlistItems = async (): Promise<WishlistItem[]> => {
const res = await fetch('/api/wishlist');
if (!res.ok) {
throw new Error('Failed to fetch wishlist items');
}
return res.json();
};
const WishlistPage = () => {
const wishlistItems = use(fetchWishlistItems);
if (!wishlistItems || wishlistItems.length === 0) {
return <p>Your wishlist is empty</p>;
}
return (
<div className="container mx-auto p-4">
<h1 className="text-3xl font-bold mb-4">Wishlist</h1>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
{wishlistItems.map((item) => (
<div key={item.id} className="block border rounded-lg p-4 hover:shadow-lg">
<img src={item.image} alt={item.name} className="w-full h-48 object-cover rounded-lg mb-2" />
<h2 className="text-xl font-semibold">{item.name}</h2>
<p className="text-lg text-gray-500">${item.price.toFixed(2)}</p>
</div>
))}
</div>
</div>
);
};
export default WishlistPage;
3. app/checkout/page.tsx
(Checkout Page)
This page is responsible for handling the checkout process, where users can confirm their orders and make payments.
// app/checkout/page.tsx
import { use } from 'react';
interface CartItem {
id: string;
name: string;
price: number;
quantity: number;
image: string;
}
const fetchCartItems = async (): Promise<CartItem[]> => {
const res = await fetch('/api/cart');
if (!res.ok) {
throw new Error('Failed to fetch cart items');
}
return res.json();
};
const CheckoutPage = () => {
const cartItems = use(fetchCartItems);
if (!cartItems || cartItems.length === 0) {
return <p>No items in cart to checkout</p>;
}
const total = cartItems.reduce((acc, item) => acc + item.price * item.quantity, 0);
return (
<div className="container mx-auto p-4">
<h1 className="text-3xl font-bold mb-4">Checkout</h1>
<div className="grid grid-cols-1 gap-4">
{cartItems.map((item) => (
<div key={item.id} className="flex items-center border-b pb-4">
<img src={item.image} alt={item.name} className="w-24 h-24 object-cover rounded-lg" />
<div className="ml-4">
<h2 className="text-xl font-semibold">{item.name}</h2>
<p className="text-gray-500">${item.price.toFixed(2)} x {item.quantity}</p>
</div>
</div>
))}
</div>
<div className="mt-6">
<h2 className="text-2xl font-semibold">Total: ${total.toFixed(2)}</h2>
<button className="bg-green-500 text-white py-2 px-4 rounded-lg mt-4 hover:bg-green-600">
Confirm Payment
</button>
</div>
</div>
);
};
export default CheckoutPage;
4. app/profile/page.tsx
(Profile Page)
This page allows the user to manage their profile information, such as their name, email, and addresses.
// app/profile/page.tsx
import { use } from 'react';
interface UserProfile {
name: string;
email: string;
address: string;
}
const fetchProfile = async (): Promise<UserProfile> => {
const res = await fetch('/api/profile');
if (!res.ok) {
throw new Error('Failed to fetch profile data');
}
return res.json();
};
const ProfilePage = () => {
const profile = use(fetchProfile);
if (!profile) return <p>Loading...</p>;
return (
<div className="container mx-auto p-4">
<h1 className="text-3xl font-bold mb-4">Profile</h1>
<div className="bg-white p-6 rounded-lg shadow-md">
<p className="text-lg"><strong>Name:</strong> {profile.name}</p>
<p className="text-lg"><strong>Email:</strong> {profile.email}</p>
<p className="text-lg"><strong>Address:</strong> {profile.address}</p>
<button className="bg-blue-500 text-white py-2 px-4 rounded-lg mt-4 hover:bg-blue-600">
Edit Profile
</button>
</div>
</div>
);
};
export default ProfilePage;
5. app/auth/login/page.tsx
(Login Page)
This page allows users to log in to the application by providing their email and password.
// app/auth/login/page.tsx
import { useState } from 'react';
const LoginPage = () => {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const handleLogin = async () => {
const res = await fetch('/api/auth/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email, password }),
});
if (res.ok) {
// Handle successful login
alert('Logged in successfully!');
} else {
// Handle login failure
alert('Failed to login');
}
};
return (
<div className="container mx-auto p-4">
<h1 className="text-3xl font-bold mb-4">Login</h1>
<div className="bg-white p-6 rounded-lg shadow-md max-w-md mx-auto">
<input
type="email"
placeholder="Email"
className="w-full border p-2 mb-4 rounded-lg"
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
<input
type="password"
placeholder="Password"
className="w-full border p-2 mb-4 rounded-lg"
value={password}
onChange={(e) => setPassword(e.target.value)}
/>
<button
className="bg-blue-500 text-white py-2 px-4 rounded-lg w-full hover:bg-blue-600"
onClick={handleLogin}
>
Login
</button>
</div>
</div>
);
};
export default LoginPage;
6. app/auth/signup/page.tsx
(Signup Page)
This page allows new users to register for the application by providing their name, email, and password.
// app/auth/signup/page.tsx
import { useState } from 'react';
const SignupPage = () => {
const [name, setName] = useState('');
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const handleSignup = async () => {
const res = await fetch('/api/auth/signup', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name, email, password }),
});
if (res.ok) {
// Handle successful signup
alert('Account created successfully!');
} else {
// Handle signup failure
alert('Failed to create account');
}
};
return (
<div className="container mx-auto p-4">
<h1 className="text-3xl font-bold mb-4">Sign Up</h1>
<div className="bg-white p-6 rounded-lg shadow-md max-w-md mx-auto">
<input
type="text"
placeholder="Name"
className="w-full border p-2 mb-4 rounded-lg"
value={name}
onChange={(e) => setName(e.target.value)}
/>
<input
type="email"
placeholder="Email"
className="w-full border p-2 mb-4 rounded-lg"
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
<input
type="password"
placeholder="Password"
className="w-full border p-2 mb-4 rounded-lg"
value={password}
onChange={(e) => setPassword(e.target.value)}
/>
<button
className="bg-green-500 text-white py-2 px-4 rounded-lg w-full hover:bg-green-600"
onClick={handleSignup}
>
Sign Up
</button>
</div>
</div>
);
};
export default SignupPage;
API Endpoints (Example)
You’ll need to create backend API routes (e.g., /api/cart
, /api/wishlist
, /api/auth/login
, etc.) to handle requests made from these frontend pages. These can be built using Next.js API routes or NestJS depending on your backend architecture.
Tailwind CSS Integration
If you haven't already, make sure you have Tailwind CSS installed and configured:
# Install Tailwind CSS and initialize the config file
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init
Then, configure your tailwind.config.js
file:
module.exports = {
content: [
'./app/**/*.{js,ts,jsx,tsx}',
'./components/**/*.{js,ts,jsx,tsx}',
],
theme: {
extend: {},
},
plugins: [],
}
Your global styles should import the Tailwind base, components, and utilities in the globals.css
file.
Conclusion
This structure provides the necessary frontend code for Cart, Wishlist, Checkout, Profile, Login, and Signup pages. You can integrate this with your Next.js App Router-based project and connect it to your backend API for a full-stack implementation. Let me know if you need further details or refinements!
Here's the full code for the additional pieces in your Next.js App Router project, including the global layout, 3D mall homepage, error page, reusable components, and utilities.
1. app/layout.tsx
(Global Layout with Header and Footer)
The global layout defines the overall structure of the site with the Header
and Footer
components applied to every page.
// app/layout.tsx
import Header from '../components/Header';
import Footer from '../components/Footer';
import '../styles/globals.css';
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en">
<body className="bg-gray-100">
<Header />
<main className="container mx-auto py-8">
{children}
</main>
<Footer />
</body>
</html>
);
}
2. app/page.tsx
(Home Page with 3D Mall Interface)
This is the homepage where you can integrate a 3D mall experience using libraries like Three.js for the 3D rendering.
// app/page.tsx
import React, { useEffect } from 'react';
import { Canvas } from '@react-three/fiber';
import { OrbitControls } from '@react-three/drei';
const MallScene = () => {
// You can add custom 3D objects here using Three.js or react-three/fiber
return (
<mesh>
<boxGeometry args={[3, 3, 3]} />
<meshStandardMaterial color="lightblue" />
</mesh>
);
};
const HomePage = () => {
return (
<div className="flex flex-col items-center justify-center min-h-screen">
<h1 className="text-4xl font-bold mb-6">Welcome to the Virtual Mall</h1>
<div className="w-full h-96">
<Canvas>
<ambientLight />
<pointLight position={[10, 10, 10]} />
<MallScene />
<OrbitControls />
</Canvas>
</div>
</div>
);
};
export default HomePage;
This uses react-three-fiber and drei for rendering 3D models in the canvas. To set this up, install the required libraries:
npm install @react-three/fiber @react-three/drei
3. app/error.tsx
(Global Error Page)
This page is displayed whenever an error occurs, like a 404 or other server-side errors.
// app/error.tsx
const ErrorPage = () => {
return (
<div className="flex items-center justify-center min-h-screen bg-red-100">
<h1 className="text-5xl font-bold text-red-600">Oops! Something went wrong.</h1>
</div>
);
};
export default ErrorPage;
4. components/Header.tsx
(Header Component)
A simple header component that is displayed across all pages.
// components/Header.tsx
const Header = () => {
return (
<header className="bg-blue-600 text-white p-4 shadow-lg">
<div className="container mx-auto flex justify-between items-center">
<h1 className="text-2xl font-bold">Virtual Mall</h1>
<nav>
<a href="/" className="mr-4 hover:underline">Home</a>
<a href="/cart" className="mr-4 hover:underline">Cart</a>
<a href="/wishlist" className="mr-4 hover:underline">Wishlist</a>
<a href="/profile" className="hover:underline">Profile</a>
</nav>
</div>
</header>
);
};
export default Header;
5. components/Footer.tsx
(Footer Component)
A simple footer component with placeholder content.
// components/Footer.tsx
const Footer = () => {
return (
<footer className="bg-gray-800 text-white p-4">
<div className="container mx-auto text-center">
<p>© {new Date().getFullYear()} Virtual Mall. All rights reserved.</p>
</div>
</footer>
);
};
export default Footer;
6. components/ProductCard.tsx
(Product Card Component)
A reusable card for displaying product details, used in product listings or in cart/wishlist.
// components/ProductCard.tsx
interface ProductProps {
id: string;
name: string;
price: number;
image: string;
}
const ProductCard: React.FC<ProductProps> = ({ id, name, price, image }) => {
return (
<div className="border p-4 rounded-lg hover:shadow-lg">
<img src={image} alt={name} className="w-full h-48 object-cover rounded-lg mb-2" />
<h2 className="text-lg font-bold">{name}</h2>
<p className="text-gray-500">${price.toFixed(2)}</p>
<a href={`/product/${id}`} className="text-blue-500 hover:underline mt-2 block">View Details</a>
</div>
);
};
export default ProductCard;
7. components/StoreCard.tsx
(Store Card Component)
A reusable card component for displaying stores in a list.
// components/StoreCard.tsx
interface StoreProps {
id: string;
name: string;
description: string;
image: string;
}
const StoreCard: React.FC<StoreProps> = ({ id, name, description, image }) => {
return (
<div className="border p-4 rounded-lg hover:shadow-lg">
<img src={image} alt={name} className="w-full h-48 object-cover rounded-lg mb-2" />
<h2 className="text-lg font-bold">{name}</h2>
<p className="text-gray-500">{description}</p>
<a href={`/store/${id}`} className="text-blue-500 hover:underline mt-2 block">Visit Store</a>
</div>
);
};
export default StoreCard;
8. components/CartItem.tsx
(Cart Item Component)
This component displays an individual item in the user's cart.
// components/CartItem.tsx
interface CartItemProps {
id: string;
name: string;
price: number;
quantity: number;
image: string;
}
const CartItem: React.FC<CartItemProps> = ({ id, name, price, quantity, image }) => {
return (
<div className="flex items-center border-b py-4">
<img src={image} alt={name} className="w-24 h-24 object-cover rounded-lg" />
<div className="ml-4">
<h2 className="text-xl font-semibold">{name}</h2>
<p className="text-gray-500">${price.toFixed(2)} x {quantity}</p>
</div>
</div>
);
};
export default CartItem;
9. components/WishlistItem.tsx
(Wishlist Item Component)
This component displays an individual item in the user's wishlist.
// components/WishlistItem.tsx
interface WishlistItemProps {
id: string;
name: string;
price: number;
image: string;
}
const WishlistItem: React.FC<WishlistItemProps> = ({ id, name, price, image }) => {
return (
<div className="border p-4 rounded-lg hover:shadow-lg">
<img src={image} alt={name} className="w-full h-48 object-cover rounded-lg mb-2" />
<h2 className="text-lg font-bold">{name}</h2>
<p className="text-gray-500">${price.toFixed(2)}</p>
<a href={`/product/${id}`} className="text-blue-500 hover:underline mt-2 block">View Details</a>
</div>
);
};
export default WishlistItem;
10. styles/globals.css
(Global Styles)
Here you import your Tailwind CSS styles and apply global styles.
/* styles/globals.css */
@tailwind base;
@tailwind components;
@tailwind utilities;
body {
@apply font-sans;
}
11. styles/components.css
(Component-Specific Styles)
Use this for additional styles specific to your components.
/* styles/components.css */
/* Add custom styles for components if needed */
12. lib/fetcher.ts
(Fetcher Utility)
A utility function for fetching data, useful for SWR or directly with the Fetch API.
// lib/fetcher.ts
export const fetcher = (url: string) => fetch(url).then((res) => res.json());
13. lib/auth.ts
(Authentication Utility)
Helpers for managing authentication, JWT tokens, or cookies.
// lib/auth.ts
export const setToken = (token: string) => {
document.cookie =
`token=${token}; path=/`;
};
export const getToken = () => {
const cookie = document.cookie
.split('; ')
.find((row) => row.startsWith('token='));
return cookie ? cookie.split('=')[1] : null;
};
export const removeToken = () => {
document.cookie = 'token=; Max-Age=-99999999;';
};
14. lib/prisma.ts
(Prisma Client Setup)
This file sets up the Prisma client if you're using Prisma on the frontend (e.g., for server-side components).
// lib/prisma.ts
import { PrismaClient } from '@prisma/client';
const globalForPrisma = global as unknown as { prisma: PrismaClient };
export const prisma = globalForPrisma.prisma || new PrismaClient();
if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = prisma;
Summary
This full structure provides a complete implementation for the following:
- Global layout, home page (with 3D mall interface), and error page.
- Reusable components for Header, Footer, ProductCard, StoreCard, CartItem, and WishlistItem.
- Utilities for fetching data, handling authentication, and integrating with Prisma (if needed).
Let me know if you need further clarifications or if you'd like more details on any part of this structure!
Here’s the full code for the backend structure you provided, using NestJS, Prisma (or TypeORM), and JWT for authentication, user management, and product management. This setup includes authentication strategies (local and JWT), DTOs for validation, services for business logic, and controllers to handle API requests.
1. src/auth/dto/login.dto.ts
(Login DTO)
This DTO handles the structure of the login request, validating the required fields.
// src/auth/dto/login.dto.ts
import { IsEmail, IsString } from 'class-validator';
export class LoginDto {
@IsEmail()
email: string;
@IsString()
password: string;
}
2. src/auth/dto/signup.dto.ts
(Signup DTO)
This DTO handles the structure of the signup request, validating the user registration fields.
// src/auth/dto/signup.dto.ts
import { IsEmail, IsString, MinLength } from 'class-validator';
export class SignupDto {
@IsString()
name: string;
@IsEmail()
email: string;
@IsString()
@MinLength(6)
password: string;
}
3. src/auth/auth.controller.ts
(Authentication Controller)
The controller for handling login and signup requests.
// src/auth/auth.controller.ts
import { Controller, Post, Body, HttpCode, HttpStatus } from '@nestjs/common';
import { AuthService } from './auth.service';
import { LoginDto } from './dto/login.dto';
import { SignupDto } from './dto/signup.dto';
@Controller('auth')
export class AuthController {
constructor(private readonly authService: AuthService) {}
@Post('login')
@HttpCode(HttpStatus.OK)
async login(@Body() loginDto: LoginDto) {
return this.authService.login(loginDto);
}
@Post('signup')
async signup(@Body() signupDto: SignupDto) {
return this.authService.signup(signupDto);
}
}
4. src/auth/auth.module.ts
(Authentication Module)
The module that bundles the auth-related components.
// src/auth/auth.module.ts
import { Module } from '@nestjs/common';
import { AuthService } from './auth.service';
import { AuthController } from './auth.controller';
import { UsersModule } from '../users/users.module';
import { JwtModule } from '@nestjs/jwt';
import { PassportModule } from '@nestjs/passport';
import { JwtStrategy } from './jwt.strategy';
import { LocalStrategy } from './local.strategy';
@Module({
imports: [
UsersModule,
PassportModule,
JwtModule.register({
secret: process.env.JWT_SECRET || 'default_secret',
signOptions: { expiresIn: '60m' },
}),
],
controllers: [AuthController],
providers: [AuthService, JwtStrategy, LocalStrategy],
})
export class AuthModule {}
5. src/auth/auth.service.ts
(Authentication Service)
The service that implements the authentication logic.
// src/auth/auth.service.ts
import { Injectable, UnauthorizedException } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import { UsersService } from '../users/users.service';
import { LoginDto } from './dto/login.dto';
import { SignupDto } from './dto/signup.dto';
import * as bcrypt from 'bcrypt';
@Injectable()
export class AuthService {
constructor(
private readonly usersService: UsersService,
private readonly jwtService: JwtService,
) {}
async validateUser(email: string, password: string): Promise<any> {
const user = await this.usersService.findByEmail(email);
if (user && (await bcrypt.compare(password, user.password))) {
return user;
}
return null;
}
async login(loginDto: LoginDto) {
const user = await this.usersService.findByEmail(loginDto.email);
if (!user || !(await bcrypt.compare(loginDto.password, user.password))) {
throw new UnauthorizedException('Invalid credentials');
}
const payload = { sub: user.id, email: user.email };
return {
access_token: this.jwtService.sign(payload),
};
}
async signup(signupDto: SignupDto) {
const hashedPassword = await bcrypt.hash(signupDto.password, 10);
const user = await this.usersService.create({
...signupDto,
password: hashedPassword,
});
const payload = { sub: user.id, email: user.email };
return {
access_token: this.jwtService.sign(payload),
};
}
}
6. src/auth/jwt.strategy.ts
(JWT Strategy)
JWT strategy for protecting routes.
// src/auth/jwt.strategy.ts
import { Injectable } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { ExtractJwt, Strategy } from 'passport-jwt';
@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor() {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
ignoreExpiration: false,
secretOrKey: process.env.JWT_SECRET || 'default_secret',
});
}
async validate(payload: any) {
return { userId: payload.sub, email: payload.email };
}
}
7. src/auth/local.strategy.ts
(Local Strategy for Login)
Local strategy for handling username/password login.
// src/auth/local.strategy.ts
import { Injectable, UnauthorizedException } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { Strategy } from 'passport-local';
import { AuthService } from './auth.service';
@Injectable()
export class LocalStrategy extends PassportStrategy(Strategy) {
constructor(private authService: AuthService) {
super({ usernameField: 'email' });
}
async validate(email: string, password: string): Promise<any> {
const user = await this.authService.validateUser(email, password);
if (!user) {
throw new UnauthorizedException();
}
return user;
}
}
8. src/auth/auth.guard.ts
(JWT Guard)
Guard for protecting routes using JWT.
// src/auth/auth.guard.ts
import { Injectable } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
@Injectable()
export class JwtAuthGuard extends AuthGuard('jwt') {}
9. src/users/dto/create-user.dto.ts
(Create User DTO)
Defines the structure for creating a new user.
// src/users/dto/create-user.dto.ts
import { IsEmail, IsString, MinLength } from 'class-validator';
export class CreateUserDto {
@IsString()
name: string;
@IsEmail()
email: string;
@IsString()
@MinLength(6)
password: string;
}
10. src/users/dto/update-user.dto.ts
(Update User DTO)
Defines the structure for updating user details.
// src/users/dto/update-user.dto.ts
import { IsEmail, IsOptional, IsString, MinLength } from 'class-validator';
export class UpdateUserDto {
@IsString()
@IsOptional()
name?: string;
@IsEmail()
@IsOptional()
email?: string;
@IsString()
@MinLength(6)
@IsOptional()
password?: string;
}
11. src/users/users.controller.ts
(Users Controller)
Handles user-related endpoints.
// src/users/users.controller.ts
import { Controller, Get, Param, Patch, Body, UseGuards } from '@nestjs/common';
import { UsersService } from './users.service';
import { UpdateUserDto } from './dto/update-user.dto';
import { JwtAuthGuard } from '../auth/auth.guard';
@Controller('users')
export class UsersController {
constructor(private readonly usersService: UsersService) {}
@UseGuards(JwtAuthGuard)
@Get(':id')
async getUserById(@Param('id') id: string) {
return this.usersService.findById(id);
}
@UseGuards(JwtAuthGuard)
@Patch(':id')
async updateUser(@Param('id') id: string, @Body() updateUserDto: UpdateUserDto) {
return this.usersService.update(id, updateUserDto);
}
}
12. src/users/users.module.ts
(Users Module)
The module for user management.
// src/users/users.module.ts
import { Module } from '@nestjs/common';
import { UsersService } from './users.service';
import { UsersController } from './users.controller';
import { PrismaService } from '../prisma/prisma.service';
@Module({
providers: [UsersService, PrismaService],
controllers: [UsersController],
exports: [UsersService],
})
export class UsersModule {}
13. src/users/users.service.ts
(Users Service)
Service that handles business logic related to users.
// src/users/users.service.ts
import { Injectable } from '@nestjs/common';
import { PrismaService } from '../prisma/prisma.service';
import { CreateUserDto } from './dto/create-user.dto';
import { UpdateUserDto } from './dto/update-user.dto';
@Injectable()
export class UsersService {
constructor(private prisma: PrismaService) {}
async create(data: CreateUserDto) {
return this.prisma.user
.create({ data });
}
async findById(id: string) {
return this.prisma.user.findUnique({ where: { id } });
}
async findByEmail(email: string) {
return this.prisma.user.findUnique({ where: { email } });
}
async update(id: string, data: UpdateUserDto) {
return this.prisma.user.update({
where: { id },
data,
});
}
}
14. src/users/users.entity.ts
(User Entity)
Defines the user entity in Prisma or TypeORM.
For Prisma:
// schema.prisma
model User {
id String @id @default(uuid())
name String
email String @unique
password String
createdAt DateTime @default(now())
}
For TypeORM:
// src/users/users.entity.ts
import { Entity, Column, PrimaryGeneratedColumn, CreateDateColumn } from 'typeorm';
@Entity('users')
export class User {
@PrimaryGeneratedColumn('uuid')
id: string;
@Column()
name: string;
@Column({ unique: true })
email: string;
@Column()
password: string;
@CreateDateColumn()
createdAt: Date;
}
15. src/products/dto/create-product.dto.ts
(Create Product DTO)
Defines the structure for creating a product.
// src/products/dto/create-product.dto.ts
import { IsString, IsNumber, IsOptional } from 'class-validator';
export class CreateProductDto {
@IsString()
name: string;
@IsString()
description: string;
@IsNumber()
price: number;
@IsString()
@IsOptional()
imageUrl?: string;
}
16. src/products/dto/update-product.dto.ts
(Update Product DTO)
Defines the structure for updating product details.
// src/products/dto/update-product.dto.ts
import { IsString, IsNumber, IsOptional } from 'class-validator';
export class UpdateProductDto {
@IsString()
@IsOptional()
name?: string;
@IsString()
@IsOptional()
description?: string;
@IsNumber()
@IsOptional()
price?: number;
@IsString()
@IsOptional()
imageUrl?: string;
}
17. src/products/products.controller.ts
(Products Controller)
Handles product-related endpoints.
// src/products/products.controller.ts
import { Controller, Get, Post, Patch, Param, Body, Delete } from '@nestjs/common';
import { ProductsService } from './products.service';
import { CreateProductDto } from './dto/create-product.dto';
import { UpdateProductDto } from './dto/update-product.dto';
@Controller('products')
export class ProductsController {
constructor(private readonly productsService: ProductsService) {}
@Get()
async getAllProducts() {
return this.productsService.findAll();
}
@Get(':id')
async getProductById(@Param('id') id: string) {
return this.productsService.findById(id);
}
@Post()
async createProduct(@Body() createProductDto: CreateProductDto) {
return this.productsService.create(createProductDto);
}
@Patch(':id')
async updateProduct(
@Param('id') id: string,
@Body() updateProductDto: UpdateProductDto,
) {
return this.productsService.update(id, updateProductDto);
}
@Delete(':id')
async deleteProduct(@Param('id') id: string) {
return this.productsService.delete(id);
}
}
18. src/products/products.module.ts
(Products Module)
The module for managing products.
// src/products/products.module.ts
import { Module } from '@nestjs/common';
import { ProductsService } from './products.service';
import { ProductsController } from './products.controller';
import { PrismaService } from '../prisma/prisma.service';
@Module({
providers: [ProductsService, PrismaService],
controllers: [ProductsController],
})
export class ProductsModule {}
19. src/products/products.service.ts
(Products Service)
Service for handling business logic related to products.
// src/products/products.service.ts
import { Injectable } from '@nestjs/common';
import { PrismaService } from '../prisma/prisma.service';
import { CreateProductDto } from './dto/create-product.dto';
import { UpdateProductDto } from './dto/update-product.dto';
@Injectable()
export class ProductsService {
constructor(private prisma: PrismaService) {}
async findAll() {
return this.prisma.product.findMany();
}
async findById(id: string) {
return this.prisma.product.findUnique({ where: { id } });
}
async create(data: CreateProductDto) {
return this.prisma.product.create({ data });
}
async update(id: string, data: UpdateProductDto) {
return this.prisma.product.update({
where: { id },
data,
});
}
async delete(id: string) {
return this.prisma.product.delete({ where: { id } });
}
}
20. src/products/product.entity.ts
(Product Entity)
Defines the product entity in Prisma or TypeORM.
For Prisma:
// schema.prisma
model Product {
id String @id @default(uuid())
name String
description String
price Float
imageUrl String?
createdAt DateTime @default(now())
}
For TypeORM:
// src/products/product.entity.ts
import { Entity, Column, PrimaryGeneratedColumn, CreateDateColumn } from 'typeorm';
@Entity('products')
export class Product {
@PrimaryGeneratedColumn('uuid')
id: string;
@Column()
name: string;
@Column()
description: string;
@Column('decimal')
price: number;
@Column({ nullable: true })
imageUrl: string;
@CreateDateColumn()
createdAt: Date;
}
Conclusion
This structure covers all essential aspects of authentication, user management, and product management in a NestJS backend with either Prisma or TypeORM for database management. The code provided includes DTOs for validation, controllers to handle API endpoints, services for business logic, and strategies for securing routes with JWT.
Let me know if you need more assistance or further explanations!
Here’s the full code implementation for the Store management, Cart management, and Order processing in a NestJS backend, using either Prisma or TypeORM for data management. This includes controllers, DTOs, services, and entity definitions.
Store Management
1. src/stores/dto/create-store.dto.ts
(Create Store DTO)
// src/stores/dto/create-store.dto.ts
import { IsString, IsOptional, IsUrl } from 'class-validator';
export class CreateStoreDto {
@IsString()
name: string;
@IsString()
description: string;
@IsUrl()
@IsOptional()
logoUrl?: string;
}
2. src/stores/dto/update-store.dto.ts
(Update Store DTO)
// src/stores/dto/update-store.dto.ts
import { IsString, IsOptional, IsUrl } from 'class-validator';
export class UpdateStoreDto {
@IsString()
@IsOptional()
name?: string;
@IsString()
@IsOptional()
description?: string;
@IsUrl()
@IsOptional()
logoUrl?: string;
}
3. src/stores/stores.controller.ts
(Stores Controller)
// src/stores/stores.controller.ts
import { Controller, Get, Post, Patch, Param, Delete, Body } from '@nestjs/common';
import { StoresService } from './stores.service';
import { CreateStoreDto } from './dto/create-store.dto';
import { UpdateStoreDto } from './dto/update-store.dto';
@Controller('stores')
export class StoresController {
constructor(private readonly storesService: StoresService) {}
@Get()
async getAllStores() {
return this.storesService.findAll();
}
@Get(':id')
async getStoreById(@Param('id') id: string) {
return this.storesService.findById(id);
}
@Post()
async createStore(@Body() createStoreDto: CreateStoreDto) {
return this.storesService.create(createStoreDto);
}
@Patch(':id')
async updateStore(@Param('id') id: string, @Body() updateStoreDto: UpdateStoreDto) {
return this.storesService.update(id, updateStoreDto);
}
@Delete(':id')
async deleteStore(@Param('id') id: string) {
return this.storesService.delete(id);
}
}
4. src/stores/stores.module.ts
(Stores Module)
// src/stores/stores.module.ts
import { Module } from '@nestjs/common';
import { StoresService } from './stores.service';
import { StoresController } from './stores.controller';
import { PrismaService } from '../prisma/prisma.service';
@Module({
providers: [StoresService, PrismaService],
controllers: [StoresController],
})
export class StoresModule {}
5. src/stores/stores.service.ts
(Stores Service)
// src/stores/stores.service.ts
import { Injectable } from '@nestjs/common';
import { PrismaService } from '../prisma/prisma.service';
import { CreateStoreDto } from './dto/create-store.dto';
import { UpdateStoreDto } from './dto/update-store.dto';
@Injectable()
export class StoresService {
constructor(private readonly prisma: PrismaService) {}
async findAll() {
return this.prisma.store.findMany();
}
async findById(id: string) {
return this.prisma.store.findUnique({ where: { id } });
}
async create(data: CreateStoreDto) {
return this.prisma.store.create({ data });
}
async update(id: string, data: UpdateStoreDto) {
return this.prisma.store.update({
where: { id },
data,
});
}
async delete(id: string) {
return this.prisma.store.delete({ where: { id } });
}
}
6. src/stores/store.entity.ts
(Store Entity)
For Prisma:
// schema.prisma
model Store {
id String @id @default(uuid())
name String
description String
logoUrl String?
createdAt DateTime @default(now())
}
For TypeORM:
// src/stores/store.entity.ts
import { Entity, Column, PrimaryGeneratedColumn, CreateDateColumn } from 'typeorm';
@Entity('stores')
export class Store {
@PrimaryGeneratedColumn('uuid')
id: string;
@Column()
name: string;
@Column()
description: string;
@Column({ nullable: true })
logoUrl: string;
@CreateDateColumn()
createdAt: Date;
}
Cart Management
1. src/cart/dto/add-to-cart.dto.ts
(Add to Cart DTO)
// src/cart/dto/add-to-cart.dto.ts
import { IsString, IsNumber } from 'class-validator';
export class AddToCartDto {
@IsString()
productId: string;
@IsNumber()
quantity: number;
}
2. src/cart/cart.controller.ts
(Cart Controller)
// src/cart/cart.controller.ts
import { Controller, Get, Post, Patch, Delete, Param, Body } from '@nestjs/common';
import { CartService } from './cart.service';
import { AddToCartDto } from './dto/add-to-cart.dto';
@Controller('cart')
export class CartController {
constructor(private readonly cartService: CartService) {}
@Get(':userId')
async getCart(@Param('userId') userId: string) {
return this.cartService.findByUserId(userId);
}
@Post()
async addToCart(@Body() addToCartDto: AddToCartDto) {
return this.cartService.addToCart(addToCartDto);
}
@Patch(':userId/:productId')
async updateCartItem(
@Param('userId') userId: string,
@Param('productId') productId: string,
@Body('quantity') quantity: number,
) {
return this.cartService.updateCartItem(userId, productId, quantity);
}
@Delete(':userId/:productId')
async removeFromCart(@Param('userId') userId: string, @Param('productId') productId: string) {
return this.cartService.removeFromCart(userId, productId);
}
}
3. src/cart/cart.module.ts
(Cart Module)
// src/cart/cart.module.ts
import { Module } from '@nestjs/common';
import { CartService } from './cart.service';
import { CartController } from './cart.controller';
import { PrismaService } from '../prisma/prisma.service';
@Module({
providers: [CartService, PrismaService],
controllers: [CartController],
})
export class CartModule {}
4. src/cart/cart.service.ts
(Cart Service)
// src/cart/cart.service.ts
import { Injectable } from '@nestjs/common';
import { PrismaService } from '../prisma/prisma.service';
import { AddToCartDto } from './dto/add-to-cart.dto';
@Injectable()
export class CartService {
constructor(private readonly prisma: PrismaService) {}
async findByUserId(userId: string) {
return this.prisma.cartItem.findMany({
where: { userId },
include: { product: true },
});
}
async addToCart(data: AddToCartDto) {
return this.prisma.cartItem.create({
data: {
productId: data.productId,
quantity: data.quantity,
userId: 'some-user-id', // This would typically come from the authenticated user context.
},
});
}
async updateCartItem(userId: string, productId: string, quantity: number) {
return this.prisma.cartItem.updateMany({
where: { userId, productId },
data: { quantity },
});
}
async removeFromCart(userId: string, productId: string) {
return this.prisma.cartItem.deleteMany({
where: { userId, productId },
});
}
}
5. src/cart/cart.entity.ts
(Cart Entity)
For Prisma:
// schema.prisma
model CartItem {
id String @id @default(uuid())
userId String
productId String
quantity Int
product Product @relation(fields: [productId], references: [id])
}
For TypeORM:
// src/cart/cart.entity.ts
import { Entity, Column, PrimaryGeneratedColumn, ManyToOne } from 'typeorm';
import { Product } from '../products/product.entity';
@Entity('cart_items')
export class CartItem {
@PrimaryGeneratedColumn('uuid')
id: string;
@Column()
userId: string;
@Column()
productId: string;
@Column()
quantity: number;
@ManyToOne(() => Product, (product) => product.id)
product: Product;
}
Order Processing
1. src/orders/dto/create-order.dto.ts
(Create Order DTO)
// src/orders/dto/create-order.dto.ts
import { IsString, IsArray } from 'class-validator';
export class CreateOrderDto {
@IsString()
userId: string;
@IsArray()
products: Array<{ productId: string; quantity: number }>;
}
2. src/orders/orders.controller.ts
(Orders Controller)
// src/orders/orders.controller.ts
import { Controller, Get, Post, Param, Body } from '@nestjs/common';
import { OrdersService } from './orders.service';
import { CreateOrderDto } from './dto/create-order.dto';
@Controller('orders')
export class OrdersController {
constructor(private readonly ordersService: OrdersService) {}
@Get(':userId')
async getOrders(@Param('userId') userId: string) {
return this.ordersService.findByUserId(userId);
}
@Post()
async createOrder(@Body() createOrderDto: CreateOrderDto) {
return this.ordersService.createOrder(createOrderDto);
}
}
3. src/orders/orders.module.ts
(Orders Module)
// src/orders/orders.module.ts
import { Module } from '@nestjs/common';
import { OrdersService } from './orders.service';
import { OrdersController } from './orders.controller';
import { PrismaService } from '../prisma/prisma.service';
@Module({
providers: [OrdersService, PrismaService],
controllers: [OrdersController],
})
export class OrdersModule {}
4. src/orders/orders.service.ts
(Orders Service)
// src/orders/orders.service.ts
import { Injectable } from '@nestjs/common';
import { PrismaService } from '../prisma/prisma.service';
import { CreateOrderDto } from './dto/create-order.dto';
@Injectable()
export class OrdersService {
constructor(private readonly prisma: PrismaService) {}
async findByUserId(userId: string) {
return this.prisma.order.findMany({
where: { userId },
include: { products: true },
});
}
async createOrder(data: CreateOrderDto) {
return this.prisma.order.create({
data: {
userId: data.userId,
products: {
create: data.products.map((product) => ({
productId: product.productId,
quantity: product.quantity,
})),
},
},
});
}
}
5. src/orders/order.entity.ts
(Order Entity)
For Prisma:
// schema.prisma
model Order {
id String @id @default(uuid())
userId String
products OrderItem[]
}
model OrderItem {
id String @id @default(uuid())
productId String
quantity Int
order Order @relation(fields: [orderId], references: [id])
orderId String
}
For TypeORM:
// src/orders/order.entity.ts
import { Entity, Column, PrimaryGeneratedColumn, ManyToOne, OneToMany } from 'typeorm';
import { OrderItem } from './order-item.entity';
@Entity('orders')
export class Order {
@PrimaryGeneratedColumn('uuid')
id: string;
@Column()
userId: string;
@OneToMany(() => OrderItem, (orderItem) => orderItem.order)
products: OrderItem[];
}
This code covers Store management, Cart management, and Order processing with DTOs, Controllers, Services, and Entity definitions in both Prisma and TypeORM formats, allowing you to manage stores, handle cart operations, and process orders.
Let me know if you need more details or adjustments!
Here's the full code implementation for the Payment processing, Notification system, Prisma integration, Common utilities, and App configuration in a NestJS backend with PostgreSQL and Stripe integration. This setup includes modules, services, DTOs, Prisma schema, and other necessary components.
Payment Processing Logic
1. src/payments/dto/create-payment.dto.ts
(Payment DTO)
// src/payments/dto/create-payment.dto.ts
import { IsString, IsNumber } from 'class-validator';
export class CreatePaymentDto {
@IsString()
userId: string;
@IsNumber()
amount: number;
@IsString()
currency: string;
}
2. src/payments/payments.controller.ts
(Payments Controller)
// src/payments/payments.controller.ts
import { Controller, Post, Body } from '@nestjs/common';
import { PaymentsService } from './payments.service';
import { CreatePaymentDto } from './dto/create-payment.dto';
@Controller('payments')
export class PaymentsController {
constructor(private readonly paymentsService: PaymentsService) {}
@Post()
async createPayment(@Body() createPaymentDto: CreatePaymentDto) {
return this.paymentsService.createPayment(createPaymentDto);
}
}
3. src/payments/payments.module.ts
(Payments Module)
// src/payments/payments.module.ts
import { Module } from '@nestjs/common';
import { PaymentsService } from './payments.service';
import { PaymentsController } from './payments.controller';
import { PrismaService } from '../prisma/prisma.service';
import { StripeModule } from 'nestjs-stripe';
import { ConfigService } from '@nestjs/config';
@Module({
imports: [
StripeModule.forRootAsync({
inject: [ConfigService],
useFactory: (configService: ConfigService) => ({
apiKey: configService.get('STRIPE_SECRET_KEY'),
}),
}),
],
providers: [PaymentsService, PrismaService],
controllers: [PaymentsController],
})
export class PaymentsModule {}
4. src/payments/payments.service.ts
(Payments Service with Stripe Integration)
// src/payments/payments.service.ts
import { Injectable, BadRequestException } from '@nestjs/common';
import { PrismaService } from '../prisma/prisma.service';
import { Stripe } from 'stripe';
import { InjectStripe } from 'nestjs-stripe';
import { CreatePaymentDto } from './dto/create-payment.dto';
@Injectable()
export class PaymentsService {
constructor(
private readonly prisma: PrismaService,
@InjectStripe() private readonly stripeClient: Stripe,
) {}
async createPayment(data: CreatePaymentDto) {
const paymentIntent = await this.stripeClient.paymentIntents.create({
amount: data.amount * 100, // amount in cents
currency: data.currency,
payment_method_types: ['card'],
});
if (!paymentIntent) {
throw new BadRequestException('Failed to create payment');
}
// Store payment details in the database (optional)
return this.prisma.payment.create({
data: {
userId: data.userId,
amount: data.amount,
currency: data.currency,
stripePaymentIntentId: paymentIntent.id,
status: 'pending',
},
});
}
}
5. src/payments/payment.entity.ts
(Payment Entity)
For Prisma:
// schema.prisma
model Payment {
id String @id @default(uuid())
userId String
amount Float
currency String
stripePaymentIntentId String
status String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
For TypeORM:
// src/payments/payment.entity.ts
import { Entity, Column, PrimaryGeneratedColumn, CreateDateColumn, UpdateDateColumn } from 'typeorm';
@Entity('payments')
export class Payment {
@PrimaryGeneratedColumn('uuid')
id: string;
@Column()
userId: string;
@Column('decimal')
amount: number;
@Column()
currency: string;
@Column()
stripePaymentIntentId: string;
@Column()
status: string;
@CreateDateColumn()
createdAt: Date;
@UpdateDateColumn()
updatedAt: Date;
}
Notification System
1. src/notifications/notifications.module.ts
(Notifications Module)
// src/notifications/notifications.module.ts
import { Module } from '@nestjs/common';
import { NotificationsService } from './notifications.service';
import { MailerModule } from '@nestjs-modules/mailer';
import { ConfigService, ConfigModule } from '@nestjs/config';
@Module({
imports: [
MailerModule.forRootAsync({
imports: [ConfigModule],
inject: [ConfigService],
useFactory: (configService: ConfigService) => ({
transport: {
host: configService.get('MAIL_HOST'),
port: configService.get('MAIL_PORT'),
auth: {
user: configService.get('MAIL_USER'),
pass: configService.get('MAIL_PASS'),
},
},
defaults: {
from: '"Virtual Mall" <no-reply@virtualmall.com>',
},
}),
}),
],
providers: [NotificationsService],
})
export class NotificationsModule {}
2. src/notifications/notifications.service.ts
(Notifications Service)
// src/notifications/notifications.service.ts
import { Injectable } from '@nestjs/common';
import { MailerService } from '@nestjs-modules/mailer';
@Injectable()
export class NotificationsService {
constructor(private readonly mailerService: MailerService) {}
async sendEmail(to: string, subject: string, text: string) {
return this.mailerService.sendMail({
to,
subject,
text,
});
}
async sendSMS(to: string, message: string) {
// Example implementation for sending SMS (e.g., using Twilio)
// Here you would integrate with a service like Twilio or any SMS gateway API
console.log(`Sending SMS to ${to}: ${message}`);
return { status: 'sent' };
}
async sendPushNotification(deviceId: string, title: string, message: string) {
// Example implementation for sending push notifications
// Here you would integrate with a push notification service (e.g., Firebase)
console.log(`Sending Push Notification to ${deviceId}: ${title} - ${message}`);
return { status: 'sent' };
}
}
Prisma Configuration
1. src/prisma/prisma.service.ts
(Prisma Service)
// src/prisma/prisma.service.ts
import { Injectable, OnModuleInit, OnModuleDestroy } from '@nestjs/common';
import { PrismaClient } from '@prisma/client';
@Injectable()
export class PrismaService extends PrismaClient implements OnModuleInit, OnModuleDestroy {
async onModuleInit() {
await this.$connect();
}
async onModuleDestroy() {
await this.$disconnect();
}
}
2. prisma/schema.prisma
(Prisma Schema Definition)
// prisma/schema.prisma
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
generator client {
provider = "prisma-client-js"
}
model User {
id String @id @default(uuid())
name String
email String @unique
password String
createdAt DateTime @default(now())
payments Payment[]
}
model Payment {
id String @id @default(uuid())
userId String
amount Float
currency String
stripePaymentIntentId String
status String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
Common Utilities and Guards
1. src/common/decorators/roles.decorator.ts
(Custom Roles Decorator)
// src/common/decorators/roles.decorator.ts
import { SetMetadata } from '@nestjs/common';
export const ROLES_KEY = 'roles';
export const Roles = (...roles: string[]) => SetMetadata(ROLES_KEY, roles);
2. src/common/guards/roles.guard.ts
(Role Guard)
// src/common/guards/roles.guard.ts
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { ROLES_KEY } from '../decorators/roles.decorator';
@Injectable()
export class RolesGuard implements CanActivate {
constructor(private reflector: Reflector) {}
canActivate(context: ExecutionContext): boolean {
const requiredRoles = this.reflector.getAllAndOverride<string[]>(ROLES_KEY, [
context.getHandler(),
context.getClass(),
]);
if (!requiredRoles) {
return true;
}
const { user } = context.switchToHttp().getRequest();
return requiredRoles.some((role) => user.roles?.includes(role));
}
}
3. src/common/filters/http-exception.filter.ts
(HTTP Exception Filter)
// src/common/filters/http-exception.filter.ts
import { ExceptionFilter, Catch, ArgumentsHost, HttpException
, HttpStatus } from '@nestjs/common';
import { Request, Response } from 'express';
@Catch()
export class HttpExceptionFilter implements ExceptionFilter {
catch(exception: unknown, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse<Response>();
const request = ctx.getRequest<Request>();
const status = exception instanceof HttpException ? exception.getStatus() : HttpStatus.INTERNAL_SERVER_ERROR;
response.status(status).json({
statusCode: status,
timestamp: new Date().toISOString(),
path: request.url,
message: exception instanceof HttpException ? exception.getResponse() : 'Internal server error',
});
}
}
Application Configuration
1. config/app.config.ts
(General App Configuration)
// config/app.config.ts
export default () => ({
appPort: parseInt(process.env.APP_PORT, 10) || 3000,
jwtSecret: process.env.JWT_SECRET || 'defaultSecret',
});
2. config/database.config.ts
(Database Configuration)
// config/database.config.ts
export default () => ({
database: {
url: process.env.DATABASE_URL,
},
});
3. config/jwt.config.ts
(JWT Configuration)
// config/jwt.config.ts
export default () => ({
jwt: {
secret: process.env.JWT_SECRET || 'defaultSecret',
expiresIn: process.env.JWT_EXPIRATION || '60m',
},
});
Root Application and Main Entry Point
1. src/app.module.ts
(Root Module)
// src/app.module.ts
import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import appConfig from '../config/app.config';
import databaseConfig from '../config/database.config';
import jwtConfig from '../config/jwt.config';
import { PrismaService } from './prisma/prisma.service';
import { PaymentsModule } from './payments/payments.module';
import { NotificationsModule } from './notifications/notifications.module';
import { AuthModule } from './auth/auth.module';
@Module({
imports: [
ConfigModule.forRoot({
load: [appConfig, databaseConfig, jwtConfig],
isGlobal: true,
}),
PaymentsModule,
NotificationsModule,
AuthModule,
],
providers: [PrismaService],
})
export class AppModule {}
2. src/main.ts
(Main Entry Point)
// src/main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { HttpExceptionFilter } from './common/filters/http-exception.filter';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalFilters(new HttpExceptionFilter());
await app.listen(process.env.APP_PORT || 3000);
}
bootstrap();
Conclusion
This structure includes the full implementation for:
- Payments (Stripe integration, DTOs, controller, and service).
- Notifications (email, SMS, push notifications).
- Prisma configuration and usage.
- Common utilities (guards, decorators, filters).
- App configuration (config files for the app, JWT, and database).
Let me know if you need further assistance or clarifications!
If you enjoy my content and would like to support my work, you can buy me a coffee. Your support is greatly appreciated!
Disclaimer: This content is generated by AI.
This content originally appeared on DEV Community and was authored by Nadim Chowdhury
Nadim Chowdhury | Sciencx (2024-10-19T23:11:22+00:00) Building a Scalable Virtual Shopping Mall with Next.js, NestJS, Prisma, and Stripe Integration. Retrieved from https://www.scien.cx/2024/10/19/building-a-scalable-virtual-shopping-mall-with-next-js-nestjs-prisma-and-stripe-integration/
Please log in to upload a file.
There are no updates yet.
Click the Upload button above to add an update.