To develop a Virtual Event Hosting Platform using Next.js 14 and Tailwind CSS, you need a clear breakdown of features and functionalities for the front-end, back-end, and mobile app aspects of your project. Here's a comprehensive outline of the features and functionalities for each component:
1. Front-End Features (Next.js 14 + Tailwind CSS)
Event Creation:
- Event Form: Allow users to create events with inputs for event name, description, date, time, price, and category.
- Event Media Upload: Integration with cloud storage (e.g., AWS S3) to upload images or videos for the event.
- Ticket Management: Create different ticket types (general admission, VIP, etc.), set prices, and availability.
- Event Preview: A preview option that lets organizers see what their event page will look like before publishing.
- SEO Optimized Pages: Each event page should have appropriate metadata for better search engine visibility.
- Responsive Design: Ensure a seamless experience across all devices with Tailwind CSS, ensuring the layout adapts to mobile, tablet, and desktop screens.
Ticket Purchasing:
- Authentication: OAuth and social login integration for users to create accounts or log in to purchase tickets.
- Ticket Selection: Users can select event tickets and proceed to checkout.
- Payment Gateway Integration: Integration with Stripe, PayPal, or similar for secure payments.
- Order Summary: Display the final ticket selection and pricing with an order summary before confirmation.
- Email Confirmation: Send a confirmation email with ticket details and a QR code.
Live Streaming:
- Embedded Video Player: Embed live streams directly on the event page, possibly through integrations with streaming platforms (YouTube, Vimeo, or custom RTMP server).
- Real-Time Streaming: Utilize WebRTC or a third-party service (e.g., Agora, Twilio) for low-latency, real-time streaming.
- Live Chat: Real-time chat during the event using WebSockets (e.g., Socket.io or Pusher).
- Streaming Quality Selection: Let users select different streaming qualities (auto, 480p, 720p, 1080p) based on their bandwidth.
2. Back-End Features (Next.js 14 API Routes / Serverless Functions)
Event Management:
- Event CRUD Operations: Organizers can create, update, and delete events. This includes managing event details, ticket types, and ticket availability.
- Payment Processing: Securely handle payment information, ensure successful transactions, and generate payment receipts.
- User Management: Manage user accounts, profiles, and role-based access control (e.g., event organizers vs. attendees).
- Analytics Dashboard: Provide event organizers with insights on ticket sales, attendees, and streaming engagement.
Payment Gateway:
- Integration with Stripe or PayPal: Handle all payment transactions with refunds and disputes.
- Payment Logs: Store transaction details, including payment status, method, amount, and customer details.
- Ticket Validation: Automatically generate unique QR codes or barcodes for each purchased ticket for event access.
Notifications:
- Email Notifications: Trigger email alerts for event updates, ticket purchases, and event reminders (e.g., SendGrid or AWS SES).
- SMS Notifications: Integrate with Twilio or similar services to send SMS notifications for ticket confirmation or event reminders.
- Push Notifications: Integrate with Firebase Cloud Messaging for event updates and reminders for users who opt-in.
3. Mobile App Features (React Native or Expo)
Event Browsing:
- Event Listings: Display all upcoming and past events with search and filter options.
- Event Details Page: Show detailed information for each event, including the option to purchase tickets.
- Responsive UI: The app must adapt to different mobile device screen sizes.
Event Participation:
- Join Virtual Event: Allow users to join a live event directly from the app via embedded streaming.
- In-App Ticketing: Users can purchase tickets through the app with a simplified checkout process.
- Live Q&A: A dedicated section for submitting questions during live events (WebSockets or a third-party service).
- Live Polls and Surveys: Real-time engagement features like polls during the event to enhance interactivity.
Notifications:
- Event Reminders: Push notifications for event reminders before the event start time.
- Real-Time Updates: Push notifications for event updates or live interaction notifications during the event.
4. General Features
- Search and Filter: Full-text search across events and filters by category, price, date, and popularity.
- User Authentication and Profiles: Integration with OAuth providers (Google, Facebook, etc.) for user authentication. Users can update their profiles, view purchase history, and manage notifications.
- Admin Dashboard: A dedicated section for administrators to oversee the entire platform (user management, event moderation, analytics).
- Security and Scalability: Implement OAuth for user management and scalable solutions for streaming and ticketing.
5. Docs and Resources for Implementation
Next.js 14:
- Utilize App Router for building dynamic routes and rendering pages using both server and client components【7†source】.
- Implement Tailwind CSS for styling (Next.js offers built-in support for Tailwind CSS out of the box)【7†source】.
- Follow Next.js best practices for SEO, server-side rendering (SSR), static generation (SSG), and client-side rendering (CSR)【7†source】.
Tailwind CSS:
- Responsive design and utility-first approach to streamline building the UI across the platform.
Streaming:
- Look into services like Agora, Twilio, or WebRTC for implementing real-time streaming【6†source】.
API:
- You can use Next.js API routes to handle back-end logic for ticket purchasing, event management, and notifications【7†source】.
With this breakdown, you can now start building your Virtual Event Hosting Platform, focusing on user experience, real-time engagement, and scalability.
For the frontend of your Virtual Event Hosting Platform using Next.js 14 and Tailwind CSS, the folder structure should follow the conventions for modularity, scalability, and maintainability. Here's a suggested folder structure:
/your-project-name
│
├── /app # Main directory for Next.js App Router (used for routes and pages)
│ ├── /(event) # Event-related routes grouped here
│ │ ├── create # Event creation page
│ │ │ ├── page.tsx # Main page file for creating events
│ │ ├── [eventId] # Dynamic route for a specific event's details
│ │ │ ├── page.tsx # Event details page
│ │ ├── live # Page for live streaming
│ │ │ ├── page.tsx # Live streaming page
│ │ ├── list # List of all events
│ │ │ ├── page.tsx # Event listing page
│ │ ├── qna # Page for live Q&A during events
│ │ ├── page.tsx # Live Q&A page
│ │
│ ├── /(user) # Group for user-related routes
│ │ ├── profile # User profile page
│ │ │ ├── page.tsx # Profile page component
│ │ ├── login # User login page
│ │ │ ├── page.tsx # Login page component
│ │ ├── register # User registration page
│ │ ├── page.tsx # Registration page component
│ │
│ ├── layout.tsx # Global layout for shared UI (header, footer, etc.)
│ ├── global.css # Global CSS (where Tailwind's @import goes)
│ └── _app.tsx # Custom App component for handling global state
│
├── /components # Reusable UI components
│ ├── EventCard.tsx # Card component for displaying event info
│ ├── Navbar.tsx # Navigation bar component
│ ├── Footer.tsx # Footer component
│ ├── Button.tsx # Generic Button component
│ ├── Modal.tsx # Modal component for popups
│ └── Loader.tsx # Loader component for loading states
│
├── /hooks # Custom React hooks
│ ├── useAuth.ts # Hook for authentication management
│ ├── useEvent.ts # Hook for fetching and managing event data
│ └── useStream.ts # Hook for handling live stream interactions
│
├── /lib # Utilities and helper functions
│ ├── api.ts # API calls to back-end services
│ └── validators.ts # Validation functions for forms
│
├── /public # Static assets like images, icons, and fonts
│ ├── /images # Image assets for events, logos, etc.
│ ├── favicon.ico # Favicon for the app
│ └── logo.png # Application logo
│
├── /styles # Additional global styles (if needed)
│ ├── tailwind.config.js # Tailwind CSS configuration file
│ ├── globals.css # Global styles (optional, Tailwind CSS covers most)
│
├── /types # TypeScript types
│ ├── event.ts # Type definitions for event-related data
│ └── user.ts # Type definitions for user data
│
├── next.config.js # Next.js configuration file
├── tailwind.config.js # Tailwind CSS configuration file
├── tsconfig.json # TypeScript configuration
├── package.json # Dependencies and scripts
├── .eslintrc.js # ESLint configuration for linting
├── .prettierrc # Prettier configuration for code formatting
└── .gitignore # Files to be ignored by Git
Folder Structure Breakdown:
-
/app
:-
App Router is the recommended folder structure for Next.js. Each folder here represents a route. The
(event)
and(user)
groups are route groups for event and user-related routes, respectively. -
layout.tsx
: A global layout file that can define common components like the navbar and footer for all pages.
-
App Router is the recommended folder structure for Next.js. Each folder here represents a route. The
-
/components
:- Contains all reusable UI components like buttons, cards, and modals.
- This encourages code reuse and modularity, making it easier to scale the project.
-
/hooks
:- Stores custom hooks for managing logic such as authentication, event data fetching, and live streaming interactions.
-
/lib
:- Utility functions and helpers, like API calls or form validators, which can be reused across different components.
-
/public
:- Contains static assets such as images and icons, which are served directly by Next.js without processing.
-
/styles
:- This folder contains any additional styles you might need, although most styling will be handled through Tailwind CSS.
- Tailwind's configuration file lives here for customization.
-
/types
:- Stores TypeScript type definitions to ensure strong typing across the app for entities like
event
,user
, etc.
- Stores TypeScript type definitions to ensure strong typing across the app for entities like
-
Config Files:
-
next.config.js
: Configuration specific to Next.js like optimizing images, custom webpack, or routing settings. -
tailwind.config.js
: For configuring Tailwind CSS to extend it with custom colors, spacing, and themes. -
tsconfig.json
: TypeScript configuration file to set rules for TypeScript compilation. -
package.json
: Manages dependencies, scripts, and meta information about the project.
-
Key Points:
- App Router is used for Next.js 14, supporting server and client components, dynamic routing, and streaming.
- Tailwind CSS simplifies styling and promotes responsive design out of the box.
- Folder grouping (
event
,user
) helps organize different routes logically, which is essential for large applications.
This structure provides a modular, scalable setup that makes it easy to maintain and expand as your platform grows.
To develop the backend for your Virtual Event Hosting Platform using NestJS and PostgreSQL, it's crucial to structure your project for scalability, modularity, and maintainability. Below is a folder structure that organizes your code into feature-based modules while adhering to NestJS's best practices.
Suggested Folder Structure for NestJS Backend:
/your-backend-project
│
├── /src # Main source folder for the backend
│ ├── /auth # Module for authentication
│ │ ├── auth.controller.ts
│ │ ├── auth.module.ts
│ │ ├── auth.service.ts
│ │ ├── jwt.strategy.ts
│ │ ├── local.strategy.ts
│ │ ├── dto # DTOs for request validation
│ │ │ ├── login.dto.ts
│ │ │ ├── register.dto.ts
│ │ ├── guards # Guards for role-based access
│ │ │ ├── jwt-auth.guard.ts
│ │ │ ├── roles.guard.ts
│ │ ├── decorators # Custom decorators like roles
│ │ ├── roles.decorator.ts
│ ├── /users # User module for managing users
│ │ ├── users.controller.ts
│ │ ├── users.module.ts
│ │ ├── users.service.ts
│ │ ├── entities # TypeORM entity for users
│ │ │ ├── user.entity.ts
│ │ ├── dto # User-related DTOs
│ │ ├── create-user.dto.ts
│ │ ├── update-user.dto.ts
│ ├── /events # Event module for managing events
│ │ ├── events.controller.ts
│ │ ├── events.module.ts
│ │ ├── events.service.ts
│ │ ├── entities # TypeORM entity for events
│ │ │ ├── event.entity.ts
│ │ ├── dto # Event-related DTOs
│ │ ├── create-event.dto.ts
│ │ ├── update-event.dto.ts
│ ├── /tickets # Ticketing module for event tickets
│ │ ├── tickets.controller.ts
│ │ ├── tickets.module.ts
│ │ ├── tickets.service.ts
│ │ ├── entities # TypeORM entity for tickets
│ │ │ ├── ticket.entity.ts
│ │ ├── dto # DTOs for ticket-related operations
│ │ ├── create-ticket.dto.ts
│ │ ├── update-ticket.dto.ts
│ ├── /payments # Payment module for managing transactions
│ │ ├── payments.controller.ts
│ │ ├── payments.module.ts
│ │ ├── payments.service.ts
│ │ ├── entities # TypeORM entity for payments
│ │ │ ├── payment.entity.ts
│ │ ├── dto # Payment-related DTOs
│ │ ├── create-payment.dto.ts
│ ├── /notifications # Notifications module for email, SMS, and push notifications
│ │ ├── notifications.controller.ts
│ │ ├── notifications.module.ts
│ │ ├── notifications.service.ts
│ │ ├── dto # Notification-related DTOs
│ │ ├── create-notification.dto.ts
│ ├── /database # Database module for TypeORM configurations
│ │ ├── database.module.ts
│ │ ├── database.service.ts
│ │ ├── ormconfig.ts # TypeORM configuration
│ ├── /common # Shared utilities, guards, interceptors, and exceptions
│ │ ├── /filters # Global exception filters
│ │ │ ├── all-exceptions.filter.ts
│ │ ├── /interceptors # Interceptors for response transformation, logging, etc.
│ │ │ ├── transform.interceptor.ts
│ │ ├── /decorators # Reusable decorators
│ │ │ ├── api-response.decorator.ts
│ │ ├── /pipes # Global pipes for validation, etc.
│ │ │ ├── validation.pipe.ts
│ ├── app.module.ts # Root application module
│ ├── main.ts # Main entry point of the application
│
├── /test # Tests for the application
│ ├── /e2e # End-to-end tests
│ ├── /unit # Unit tests
│
├── /config # Configuration files for different environments
│ ├── config.ts # Configuration for database, security, etc.
├── .env # Environment variables
├── .eslintrc.js # ESLint configuration for linting
├── .prettierrc # Prettier configuration for code formatting
├── tsconfig.json # TypeScript configuration
├── package.json # Dependencies and scripts
├── README.md # Project documentation
└── .gitignore # Files and folders to ignore in Git
Folder Structure Breakdown:
-
/src
:- Main directory for the entire application, housing all modules, services, controllers, and entities.
-
/auth
:- Handles authentication and authorization.
- Includes strategies for JWT and local authentication, along with guards and custom decorators like
@Roles
.
-
/users
:- Handles user management, including user CRUD operations.
- Stores user entities and related DTOs for request validation.
-
/events
:- Manages events including event creation, updates, and retrieval.
- Contains event-specific entities and DTOs.
-
/tickets
:- Manages event ticketing, including creating, updating, and retrieving tickets.
- Includes ticket-specific entities and DTOs.
-
/payments
:- Manages payment processing and integration with payment gateways like Stripe or PayPal.
- Handles transaction logging and payment-related operations.
-
/notifications
:- Handles email, SMS, and push notifications.
- Integrates with services like Twilio, Firebase, or SendGrid for notifications.
-
/database
:- Manages database connections and configurations using TypeORM.
- Stores ORM configurations and the database module setup.
-
/common
:- Contains shared utilities like global exception filters, interceptors, pipes, and decorators that can be reused across the app.
-
/test
:- Organizes unit tests and end-to-end (e2e) tests using Jest.
-
/config
:- Manages environment-specific configurations, including database connection strings, API keys, and security settings.
- Uses
.env
files for sensitive information.
-
Root Files:
-
app.module.ts
: The root module that imports all other modules. -
main.ts
: The entry point for the application where the NestJS server is bootstrapped. -
.env
: Stores environment variables, like database credentials, secret keys, etc.
-
Key Concepts:
-
Feature Modules: NestJS promotes feature modules, where each domain (e.g.,
auth
,users
,events
) has its own isolated module, making the app scalable and easier to maintain. -
TypeORM: Used for database management, including defining entities (like
User
,Event
,Ticket
,Payment
) and running queries with the PostgreSQL database. - DTOs: Used for input validation and data transfer between controllers and services.
- Guards and Interceptors: Help manage request authentication, roles, and response transformations.
- Testing: Includes unit and e2e tests for controllers, services, and modules to ensure the application works as expected.
This structure will ensure that your backend is scalable, modular, and easy to maintain as you build your platform.
Below is the full code structure for each file mentioned in your request using Next.js 14 and TypeScript. This includes creating events, displaying event details, handling live streaming, listing events, and a Q&A page for virtual events. This structure follows Next.js App Router conventions.
Folder Structure Overview
/app
├── /(event)
│ ├── create
│ │ └── page.tsx
│ ├── [eventId]
│ │ └── page.tsx
│ ├── live
│ │ └── page.tsx
│ ├── list
│ │ └── page.tsx
│ ├── qna
│ └── page.tsx
1. Event Creation Page - /app/(event)/create/page.tsx
// /app/(event)/create/page.tsx
import React, { useState } from 'react';
const CreateEventPage: React.FC = () => {
const [formData, setFormData] = useState({
name: '',
description: '',
date: '',
time: '',
price: '',
});
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
const { name, value } = e.target;
setFormData((prevData) => ({
...prevData,
[name]: value,
}));
};
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
console.log('Event Data:', formData);
// Call an API to create the event
};
return (
<div className="container mx-auto p-4">
<h1 className="text-2xl font-bold mb-4">Create New Event</h1>
<form onSubmit={handleSubmit} className="space-y-4">
<div>
<label className="block">Event Name</label>
<input
name="name"
type="text"
className="w-full p-2 border rounded"
value={formData.name}
onChange={handleInputChange}
required
/>
</div>
<div>
<label className="block">Description</label>
<textarea
name="description"
className="w-full p-2 border rounded"
value={formData.description}
onChange={handleInputChange}
required
/>
</div>
<div>
<label className="block">Date</label>
<input
name="date"
type="date"
className="w-full p-2 border rounded"
value={formData.date}
onChange={handleInputChange}
required
/>
</div>
<div>
<label className="block">Time</label>
<input
name="time"
type="time"
className="w-full p-2 border rounded"
value={formData.time}
onChange={handleInputChange}
required
/>
</div>
<div>
<label className="block">Price</label>
<input
name="price"
type="number"
className="w-full p-2 border rounded"
value={formData.price}
onChange={handleInputChange}
required
/>
</div>
<button type="submit" className="bg-blue-500 text-white p-2 rounded">Create Event</button>
</form>
</div>
);
};
export default CreateEventPage;
2. Event Details Page - /app/(event)/[eventId]/page.tsx
// /app/(event)/[eventId]/page.tsx
import { useRouter } from 'next/router';
import React from 'react';
const EventDetailsPage: React.FC = () => {
const router = useRouter();
const { eventId } = router.query; // Accessing eventId from the route
// Fetch event details using eventId (replace with actual API call)
const event = {
id: eventId,
name: 'Sample Event',
description: 'This is a description of the event.',
date: '2024-10-30',
time: '14:00',
price: 20,
};
return (
<div className="container mx-auto p-4">
<h1 className="text-3xl font-bold">{event.name}</h1>
<p className="mt-2">{event.description}</p>
<p className="mt-4">Date: {event.date}</p>
<p className="mt-1">Time: {event.time}</p>
<p className="mt-4">Price: ${event.price}</p>
<button className="bg-green-500 text-white p-2 rounded mt-4">Buy Tickets</button>
</div>
);
};
export default EventDetailsPage;
3. Live Streaming Page - /app/(event)/live/page.tsx
// /app/(event)/live/page.tsx
import React from 'react';
const LiveStreamPage: React.FC = () => {
// Placeholder: Replace with actual live stream integration
const liveStreamUrl = 'https://www.example.com/livestream'; // Replace with actual stream URL
return (
<div className="container mx-auto p-4">
<h1 className="text-3xl font-bold">Live Event Stream</h1>
<div className="mt-4">
<iframe
width="100%"
height="500"
src={liveStreamUrl}
frameBorder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowFullScreen
></iframe>
</div>
</div>
);
};
export default LiveStreamPage;
4. Event Listing Page - /app/(event)/list/page.tsx
// /app/(event)/list/page.tsx
import Link from 'next/link';
import React from 'react';
const EventListPage: React.FC = () => {
// Placeholder: Replace with actual event list from API
const events = [
{ id: '1', name: 'Event 1', date: '2024-11-01', price: 30 },
{ id: '2', name: 'Event 2', date: '2024-11-05', price: 20 },
];
return (
<div className="container mx-auto p-4">
<h1 className="text-2xl font-bold">Upcoming Events</h1>
<ul className="mt-4">
{events.map((event) => (
<li key={event.id} className="mb-4">
<Link href={`/event/${event.id}`}>
<a className="block p-4 border rounded hover:bg-gray-100">
<h2 className="text-xl">{event.name}</h2>
<p>Date: {event.date}</p>
<p>Price: ${event.price}</p>
</a>
</Link>
</li>
))}
</ul>
</div>
);
};
export default EventListPage;
5. Live Q&A Page - /app/(event)/qna/page.tsx
// /app/(event)/qna/page.tsx
import React, { useState } from 'react';
const QnAPage: React.FC = () => {
const [question, setQuestion] = useState('');
const [questions, setQuestions] = useState<string[]>([]);
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
setQuestions((prevQuestions) => [...prevQuestions, question]);
setQuestion('');
};
return (
<div className="container mx-auto p-4">
<h1 className="text-2xl font-bold mb-4">Live Q&A</h1>
<form onSubmit={handleSubmit} className="mb-6">
<input
type="text"
className="w-full p-2 border rounded mb-2"
placeholder="Ask your question"
value={question}
onChange={(e) => setQuestion(e.target.value)}
required
/>
<button type="submit" className="bg-blue-500 text-white p-2 rounded">Submit Question</button>
</form>
<ul>
{questions.map((q, index) => (
<li key={index} className="mb-2 border p-2 rounded">
{q}
</li>
))}
</ul>
</div>
);
};
export default QnAPage;
Key Points:
- Event Creation: A form where users can input event details (name, description, date, etc.) and submit the event.
-
Event Details: A dynamic route using
[eventId]
to display details for each event. It fetches event data based on theeventId
. - Live Stream: An embedded iframe to display live streaming video.
- Event Listing: A simple list of events that links to the details page for each event
.
- Q&A: A live Q&A section where users can submit questions, and it dynamically updates the list of questions.
You can expand this code further by integrating APIs for fetching event data, handling live streams, and submitting Q&A questions to the backend.
Here is the full code for the user-related routes and additional shared components in a Next.js 14 project. This includes the profile page, login page, registration page, and shared components such as the global layout and global CSS file. These pages will also use Tailwind CSS for styling.
Folder Structure Overview
/app
├── /(user) # User-related routes
│ ├── profile # User profile page
│ │ └── page.tsx # Profile page component
│ ├── login # User login page
│ │ └── page.tsx # Login page component
│ ├── register # User registration page
│ └── page.tsx # Registration page component
│
├── layout.tsx # Global layout for shared UI (header, footer, etc.)
├── global.css # Global CSS (Tailwind CSS imports)
└── _app.tsx # Custom App component for global state or context
1. User Profile Page - /app/(user)/profile/page.tsx
// /app/(user)/profile/page.tsx
import React from 'react';
const ProfilePage: React.FC = () => {
// Placeholder user data (fetch real user data via an API)
const user = {
name: 'John Doe',
email: 'john.doe@example.com',
joined: '2023-01-01',
};
return (
<div className="container mx-auto p-4">
<h1 className="text-3xl font-bold">User Profile</h1>
<div className="mt-4">
<p><strong>Name:</strong> {user.name}</p>
<p><strong>Email:</strong> {user.email}</p>
<p><strong>Joined:</strong> {user.joined}</p>
</div>
<button className="bg-blue-500 text-white p-2 mt-4 rounded">Edit Profile</button>
</div>
);
};
export default ProfilePage;
2. User Login Page - /app/(user)/login/page.tsx
// /app/(user)/login/page.tsx
import React, { useState } from 'react';
import { useRouter } from 'next/router';
const LoginPage: React.FC = () => {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const router = useRouter();
const handleLogin = (e: React.FormEvent) => {
e.preventDefault();
// Placeholder login logic (replace with API call)
console.log('Logging in:', { email, password });
router.push('/'); // Redirect to homepage after login
};
return (
<div className="container mx-auto p-4">
<h1 className="text-2xl font-bold mb-4">Login</h1>
<form onSubmit={handleLogin} className="space-y-4">
<div>
<label className="block">Email</label>
<input
type="email"
className="w-full p-2 border rounded"
value={email}
onChange={(e) => setEmail(e.target.value)}
required
/>
</div>
<div>
<label className="block">Password</label>
<input
type="password"
className="w-full p-2 border rounded"
value={password}
onChange={(e) => setPassword(e.target.value)}
required
/>
</div>
<button type="submit" className="bg-blue-500 text-white p-2 rounded">
Login
</button>
</form>
<p className="mt-4">
Don't have an account?{' '}
<a href="/user/register" className="text-blue-500">Register here</a>.
</p>
</div>
);
};
export default LoginPage;
3. User Registration Page - /app/(user)/register/page.tsx
// /app/(user)/register/page.tsx
import React, { useState } from 'react';
import { useRouter } from 'next/router';
const RegisterPage: React.FC = () => {
const [formData, setFormData] = useState({
name: '',
email: '',
password: '',
});
const router = useRouter();
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const { name, value } = e.target;
setFormData((prevData) => ({
...prevData,
[name]: value,
}));
};
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
// Placeholder registration logic (replace with API call)
console.log('Registering user:', formData);
router.push('/user/login'); // Redirect to login page after registration
};
return (
<div className="container mx-auto p-4">
<h1 className="text-2xl font-bold mb-4">Register</h1>
<form onSubmit={handleSubmit} className="space-y-4">
<div>
<label className="block">Name</label>
<input
name="name"
type="text"
className="w-full p-2 border rounded"
value={formData.name}
onChange={handleInputChange}
required
/>
</div>
<div>
<label className="block">Email</label>
<input
name="email"
type="email"
className="w-full p-2 border rounded"
value={formData.email}
onChange={handleInputChange}
required
/>
</div>
<div>
<label className="block">Password</label>
<input
name="password"
type="password"
className="w-full p-2 border rounded"
value={formData.password}
onChange={handleInputChange}
required
/>
</div>
<button type="submit" className="bg-green-500 text-white p-2 rounded">
Register
</button>
</form>
<p className="mt-4">
Already have an account?{' '}
<a href="/user/login" className="text-blue-500">Login here</a>.
</p>
</div>
);
};
export default RegisterPage;
4. Global Layout - /app/layout.tsx
This global layout includes a header and footer that will be displayed across all pages.
// /app/layout.tsx
import React from 'react';
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en">
<body className="bg-gray-50 text-gray-900">
<Header />
<main className="min-h-screen">{children}</main>
<Footer />
</body>
</html>
);
}
const Header: React.FC = () => (
<header className="bg-blue-500 text-white p-4">
<div className="container mx-auto">
<a href="/" className="text-xl font-bold">Event Hosting Platform</a>
<nav className="mt-2">
<a href="/user/profile" className="mr-4">Profile</a>
<a href="/user/login">Login</a>
</nav>
</div>
</header>
);
const Footer: React.FC = () => (
<footer className="bg-gray-800 text-white p-4 mt-8">
<div className="container mx-auto text-center">
<p>© 2024 Event Hosting Platform. All rights reserved.</p>
</div>
</footer>
);
5. Global CSS - /app/global.css
This file is where you import Tailwind CSS and apply any global styles. Ensure that Tailwind CSS is correctly configured in your project (tailwind.config.js
).
/* /app/global.css */
@tailwind base;
@tailwind components;
@tailwind utilities;
/* Additional custom styles if needed */
body {
font-family: 'Inter', sans-serif;
}
6. Custom App Component - /app/_app.tsx
In Next.js 14+, you can use the App component to manage things like global state, context providers, etc. This is optional depending on your application needs.
// /app/_app.tsx
import React from 'react';
export default function MyApp({ Component, pageProps }: any) {
return (
<Component {...pageProps} />
);
}
Key Points:
- Profile Page: Displays user information and includes a button to edit the profile (extend it with API calls for real data).
- Login Page: Simple login form that logs the user in and redirects them.
- Registration Page: Registration form that collects user details and redirects to the login page upon successful registration.
- Global Layout: Contains a header and footer, which will be shared across all pages.
- Tailwind CSS:
Applied globally with @tailwind base
, @tailwind components
, and @tailwind utilities
.
This code provides a basic setup for user authentication pages and shared components in your Next.js project, and it can be extended with real authentication and API integration.
Here’s the full implementation of the components, hooks, utilities, and other related files for your Next.js project as outlined in your folder structure.
1. Reusable UI Components
EventCard.tsx
// /components/EventCard.tsx
import React from 'react';
interface EventCardProps {
name: string;
description: string;
date: string;
price: number;
imageUrl: string;
}
const EventCard: React.FC<EventCardProps> = ({ name, description, date, price, imageUrl }) => {
return (
<div className="max-w-sm rounded overflow-hidden shadow-lg">
<img className="w-full h-48 object-cover" src={imageUrl} alt={name} />
<div className="px-6 py-4">
<h2 className="font-bold text-xl mb-2">{name}</h2>
<p className="text-gray-700 text-base">{description}</p>
<p className="text-gray-600 mt-2">Date: {date}</p>
<p className="text-gray-600">Price: ${price}</p>
</div>
</div>
);
};
export default EventCard;
Navbar.tsx
// /components/Navbar.tsx
import React from 'react';
const Navbar: React.FC = () => {
return (
<nav className="bg-blue-500 text-white p-4">
<div className="container mx-auto flex justify-between items-center">
<a href="/" className="text-xl font-bold">Event Hosting Platform</a>
<div>
<a href="/user/profile" className="mr-4">Profile</a>
<a href="/user/login" className="mr-4">Login</a>
</div>
</div>
</nav>
);
};
export default Navbar;
Footer.tsx
// /components/Footer.tsx
import React from 'react';
const Footer: React.FC = () => {
return (
<footer className="bg-gray-800 text-white text-center p-4 mt-8">
<div className="container mx-auto">
<p>© 2024 Event Hosting Platform. All rights reserved.</p>
</div>
</footer>
);
};
export default Footer;
Button.tsx
// /components/Button.tsx
import React from 'react';
interface ButtonProps {
label: string;
onClick: () => void;
className?: string;
}
const Button: React.FC<ButtonProps> = ({ label, onClick, className }) => {
return (
<button
className={`bg-blue-500 text-white p-2 rounded ${className}`}
onClick={onClick}
>
{label}
</button>
);
};
export default Button;
Modal.tsx
// /components/Modal.tsx
import React from 'react';
interface ModalProps {
isOpen: boolean;
title: string;
children: React.ReactNode;
onClose: () => void;
}
const Modal: React.FC<ModalProps> = ({ isOpen, title, children, onClose }) => {
if (!isOpen) return null;
return (
<div className="fixed inset-0 flex items-center justify-center z-50">
<div className="bg-white p-4 rounded shadow-lg">
<h2 className="text-xl font-bold mb-2">{title}</h2>
<div>{children}</div>
<button
className="bg-red-500 text-white p-2 rounded mt-4"
onClick={onClose}
>
Close
</button>
</div>
<div className="fixed inset-0 bg-black opacity-50" onClick={onClose}></div>
</div>
);
};
export default Modal;
Loader.tsx
// /components/Loader.tsx
import React from 'react';
const Loader: React.FC = () => {
return (
<div className="flex justify-center items-center">
<div className="spinner-border animate-spin inline-block w-8 h-8 border-4 rounded-full border-t-transparent border-blue-500"></div>
</div>
);
};
export default Loader;
2. Custom Hooks
useAuth.ts
// /hooks/useAuth.ts
import { useState, useEffect } from 'react';
export const useAuth = () => {
const [isAuthenticated, setIsAuthenticated] = useState(false);
useEffect(() => {
// Example: check localStorage or make an API call to verify authentication
const token = localStorage.getItem('token');
setIsAuthenticated(!!token);
}, []);
const login = (token: string) => {
localStorage.setItem('token', token);
setIsAuthenticated(true);
};
const logout = () => {
localStorage.removeItem('token');
setIsAuthenticated(false);
};
return { isAuthenticated, login, logout };
};
useEvent.ts
// /hooks/useEvent.ts
import { useState, useEffect } from 'react';
import { Event } from '../types/event';
import { fetchEvents } from '../lib/api';
export const useEvent = () => {
const [events, setEvents] = useState<Event[]>([]);
const [loading, setLoading] = useState<boolean>(true);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
const loadEvents = async () => {
try {
const data = await fetchEvents();
setEvents(data);
} catch (err) {
setError('Failed to load events');
} finally {
setLoading(false);
}
};
loadEvents();
}, []);
return { events, loading, error };
};
useStream.ts
// /hooks/useStream.ts
import { useState } from 'react';
export const useStream = () => {
const [isStreaming, setIsStreaming] = useState(false);
const startStream = () => {
setIsStreaming(true);
// Trigger actual live stream logic here
};
const stopStream = () => {
setIsStreaming(false);
// Stop live stream logic here
};
return { isStreaming, startStream, stopStream };
};
3. Utilities and Helper Functions
api.ts
// /lib/api.ts
import { Event } from '../types/event';
const API_URL = 'https://api.example.com';
export const fetchEvents = async (): Promise<Event[]> => {
const response = await fetch(`${API_URL}/events`);
if (!response.ok) {
throw new Error('Failed to fetch events');
}
return response.json();
};
export const loginUser = async (email: string, password: string): Promise<string> => {
const response = await fetch(`${API_URL}/login`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email, password }),
});
if (!response.ok) {
throw new Error('Failed to log in');
}
const { token } = await response.json();
return token;
};
validators.ts
// /lib/validators.ts
export const validateEmail = (email: string): boolean => {
const regex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return regex.test(email);
};
export const validatePassword = (password: string): boolean => {
// Example validation: password must be at least 6 characters
return password.length >= 6;
};
4. Static Assets
- /public/images: Add your static images like event banners or logos.
- favicon.ico: Place your favicon here.
- logo.png: Add the logo for your application here.
Example structure:
/public
├── /images
│ ├── event1.jpg
│ ├── event2.jpg
│ └── logo.png
├── favicon.ico
5. Styles
tailwind.config.js
Make sure Tailwind CSS is properly set up. Here’s the config file to enable Tailwind in your project.
// /styles/tailwind.config.js
module.exports = {
content: ['./app/**/*.{js,ts,jsx,tsx}', './components/**/*.{js,ts,jsx,tsx}'],
theme: {
extend: {},
},
plugins: [],
};
globals.css
This is an optional file for any additional global CSS. Tailwind CSS will cover most styling needs.
/* /styles/globals.css */
@tailwind base;
@tailwind components;
@tailwind utilities;
/* Additional global styles can go here */
6. TypeScript Types
event.ts
// /types/event.ts
export interface Event {
id: string;
name: string;
description: string;
date: string;
price: number;
imageUrl: string;
}
user.ts
// /types/user.ts
export interface User {
id: string;
name: string;
email: string;
joined: string;
}
Summary:
This structure provides a well-organized and scalable architecture for building your Next.js app with reusable components, custom hooks, and modular utility functions.
- The components like
EventCard
,Navbar
, andButton
are reusable across your application. -
Custom hooks like
useAuth
,useEvent
, anduseStream
encapsulate logic and simplify interaction with the app's state and APIs. - Utility functions handle API requests and validations.
Here’s the full implementation for the authentication and user modules in a NestJS application using TypeORM and JWT-based authentication.
Folder Structure Overview
/src
├── /auth
│ ├── auth.controller.ts
│ ├── auth.module.ts
│ ├── auth.service.ts
│ ├── jwt.strategy.ts
│ ├── local.strategy.ts
│ ├── dto
│ │ ├── login.dto.ts
│ │ ├── register.dto.ts
│ ├── guards
│ │ ├── jwt-auth.guard.ts
│ │ ├── roles.guard.ts
│ ├── decorators
│ ├── roles.decorator.ts
├── /users
│ ├── users.controller.ts
│ ├── users.module.ts
│ ├── users.service.ts
│ ├── entities
│ │ ├── user.entity.ts
│ ├── dto
│ ├── create-user.dto.ts
│ ├── update-user.dto.ts
1. Auth Module
auth.controller.ts
Handles user login and registration.
// /src/auth/auth.controller.ts
import { Controller, Post, Body, UseGuards, Request } from '@nestjs/common';
import { AuthService } from './auth.service';
import { LoginDto } from './dto/login.dto';
import { RegisterDto } from './dto/register.dto';
import { LocalAuthGuard } from './guards/local-auth.guard';
import { JwtAuthGuard } from './guards/jwt-auth.guard';
@Controller('auth')
export class AuthController {
constructor(private readonly authService: AuthService) {}
@Post('login')
@UseGuards(LocalAuthGuard)
async login(@Request() req, @Body() loginDto: LoginDto) {
return this.authService.login(req.user);
}
@Post('register')
async register(@Body() registerDto: RegisterDto) {
return this.authService.register(registerDto);
}
@UseGuards(JwtAuthGuard)
@Post('profile')
getProfile(@Request() req) {
return req.user;
}
}
auth.module.ts
Defines the dependencies and imports required for the authentication module.
// /src/auth/auth.module.ts
import { Module } from '@nestjs/common';
import { JwtModule } from '@nestjs/jwt';
import { PassportModule } from '@nestjs/passport';
import { AuthService } from './auth.service';
import { AuthController } from './auth.controller';
import { UsersModule } from '../users/users.module';
import { JwtStrategy } from './jwt.strategy';
import { LocalStrategy } from './local.strategy';
import { JwtAuthGuard } from './guards/jwt-auth.guard';
@Module({
imports: [
UsersModule,
PassportModule,
JwtModule.register({
secret: 'jwt_secret', // Use environment variable for production
signOptions: { expiresIn: '1d' },
}),
],
controllers: [AuthController],
providers: [AuthService, LocalStrategy, JwtStrategy, JwtAuthGuard],
})
export class AuthModule {}
auth.service.ts
Handles the logic for registering users, validating credentials, and generating JWT tokens.
// /src/auth/auth.service.ts
import { Injectable, UnauthorizedException } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import { UsersService } from '../users/users.service';
import { RegisterDto } from './dto/register.dto';
import { User } from '../users/entities/user.entity';
@Injectable()
export class AuthService {
constructor(
private usersService: UsersService,
private jwtService: JwtService,
) {}
async validateUser(email: string, pass: string): Promise<any> {
const user = await this.usersService.findByEmail(email);
if (user && user.password === pass) {
const { password, ...result } = user;
return result;
}
return null;
}
async login(user: any) {
const payload = { email: user.email, sub: user.id };
return {
access_token: this.jwtService.sign(payload),
};
}
async register(registerDto: RegisterDto): Promise<User> {
return this.usersService.create(registerDto);
}
}
jwt.strategy.ts
Handles JWT token verification.
// /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: 'jwt_secret', // Use environment variable for production
});
}
async validate(payload: any) {
return { userId: payload.sub, email: payload.email };
}
}
local.strategy.ts
Handles username/password validation.
// /src/auth/local.strategy.ts
import { Strategy } from 'passport-local';
import { PassportStrategy } from '@nestjs/passport';
import { Injectable, UnauthorizedException } from '@nestjs/common';
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;
}
}
login.dto.ts
Data Transfer Object for login requests.
// /src/auth/dto/login.dto.ts
import { IsEmail, IsNotEmpty, MinLength } from 'class-validator';
export class LoginDto {
@IsEmail()
email: string;
@IsNotEmpty()
@MinLength(6)
password: string;
}
register.dto.ts
Data Transfer Object for registration requests.
// /src/auth/dto/register.dto.ts
import { IsEmail, IsNotEmpty, MinLength } from 'class-validator';
export class RegisterDto {
@IsNotEmpty()
name: string;
@IsEmail()
email: string;
@IsNotEmpty()
@MinLength(6)
password: string;
}
jwt-auth.guard.ts
Guard for protecting routes with JWT authentication.
// /src/auth/guards/jwt-auth.guard.ts
import { Injectable } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
@Injectable()
export class JwtAuthGuard extends AuthGuard('jwt') {}
roles.guard.ts
Guard for role-based access control.
// /src/auth/guards/roles.guard.ts
import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
@Injectable()
export class RolesGuard implements CanActivate {
constructor(private reflector: Reflector) {}
canActivate(context: ExecutionContext): boolean {
const roles = this.reflector.get<string[]>('roles', context.getHandler());
if (!roles) {
return true;
}
const request = context.switchToHttp().getRequest();
const user = request.user;
return roles.includes(user.role);
}
}
roles.decorator.ts
Custom decorator for assigning roles to routes.
// /src/auth/decorators/roles.decorator.ts
import { SetMetadata } from '@nestjs/common';
export const Roles = (...roles: string[]) => SetMetadata('roles', roles);
2. User Module
users.controller.ts
Handles user-related requests.
// /src/users/users.controller.ts
import { Controller, Get, Post, Body, Param, Patch } from '@nestjs/common';
import { UsersService } from './users.service';
import { CreateUserDto } from './dto/create-user.dto';
import { UpdateUserDto } from './dto/update-user.dto';
@Controller('users')
export class UsersController {
constructor(private readonly usersService: UsersService) {}
@Post()
create(@Body() createUserDto: CreateUserDto) {
return this.usersService.create(createUserDto);
}
@Get(':id')
findOne(@Param('id') id: string) {
return this.usersService.findOne(id);
}
@Patch(':id')
update(@Param('id') id: string, @Body() updateUserDto: UpdateUserDto) {
return this.usersService.update(id, updateUserDto);
}
}
users.module.ts
Imports the necessary modules and services for the user module.
// /src/users/users.module.ts
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { UsersService } from './users.service';
import { UsersController } from './users.controller';
import { User } from './entities/user.entity';
@Module({
imports: [TypeOrmModule.forFeature([User])],
controllers: [UsersController],
providers: [UsersService],
exports: [UsersService],
})
export class UsersModule {}
users.service.ts
Handles the logic for managing user data.
// /src/users/users.service.ts
import { Injectable } from '@nestjs/common';
import { InjectRepository } from
'@nestjs/typeorm';
import { Repository } from 'typeorm';
import { CreateUserDto } from './dto/create-user.dto';
import { UpdateUserDto } from './dto/update-user.dto';
import { User } from './entities/user.entity';
@Injectable()
export class UsersService {
constructor(
@InjectRepository(User)
private usersRepository: Repository<User>,
) {}
async create(createUserDto: CreateUserDto): Promise<User> {
const user = this.usersRepository.create(createUserDto);
return this.usersRepository.save(user);
}
async findOne(id: string): Promise<User> {
return this.usersRepository.findOneBy({ id });
}
async findByEmail(email: string): Promise<User> {
return this.usersRepository.findOneBy({ email });
}
async update(id: string, updateUserDto: UpdateUserDto): Promise<User> {
await this.usersRepository.update(id, updateUserDto);
return this.findOne(id);
}
}
user.entity.ts
Defines the user schema for TypeORM.
// /src/users/entities/user.entity.ts
import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm';
@Entity()
export class User {
@PrimaryGeneratedColumn('uuid')
id: string;
@Column()
name: string;
@Column({ unique: true })
email: string;
@Column()
password: string;
@Column({ default: 'user' })
role: string;
}
create-user.dto.ts
Data Transfer Object for creating a user.
// /src/users/dto/create-user.dto.ts
import { IsEmail, IsNotEmpty, MinLength } from 'class-validator';
export class CreateUserDto {
@IsNotEmpty()
name: string;
@IsEmail()
email: string;
@IsNotEmpty()
@MinLength(6)
password: string;
}
update-user.dto.ts
Data Transfer Object for updating a user.
// /src/users/dto/update-user.dto.ts
import { IsOptional, MinLength } from 'class-validator';
export class UpdateUserDto {
@IsOptional()
name?: string;
@IsOptional()
@MinLength(6)
password?: string;
}
Summary
This setup provides a modular authentication and user management system in NestJS. It includes:
- Authentication module: Handles user login and registration with JWT authentication and role-based guards.
- User module: Manages user data using TypeORM with CRUD operations.
- DTOs and Guards: Ensure type safety and request validation, while protecting routes with JWT tokens.
You can extend this code to integrate with your PostgreSQL database using TypeORM, and the JWT secret can be stored securely in environment variables for production use.
Here’s the full code implementation for the Event and Ticketing modules in a NestJS backend using TypeORM for managing event and ticket data.
Folder Structure Overview
/src
├── /events # Event module
│ ├── events.controller.ts
│ ├── events.module.ts
│ ├── events.service.ts
│ ├── entities
│ │ ├── event.entity.ts
│ ├── dto
│ ├── create-event.dto.ts
│ ├── update-event.dto.ts
├── /tickets # Ticket module
│ ├── tickets.controller.ts
│ ├── tickets.module.ts
│ ├── tickets.service.ts
│ ├── entities
│ │ ├── ticket.entity.ts
│ ├── dto
│ ├── create-ticket.dto.ts
│ ├── update-ticket.dto.ts
1. Event Module
events.controller.ts
Handles HTTP requests related to events (e.g., create, update, list events).
// /src/events/events.controller.ts
import { Controller, Get, Post, Body, Param, Patch, Delete } from '@nestjs/common';
import { EventsService } from './events.service';
import { CreateEventDto } from './dto/create-event.dto';
import { UpdateEventDto } from './dto/update-event.dto';
@Controller('events')
export class EventsController {
constructor(private readonly eventsService: EventsService) {}
@Post()
create(@Body() createEventDto: CreateEventDto) {
return this.eventsService.create(createEventDto);
}
@Get()
findAll() {
return this.eventsService.findAll();
}
@Get(':id')
findOne(@Param('id') id: string) {
return this.eventsService.findOne(id);
}
@Patch(':id')
update(@Param('id') id: string, @Body() updateEventDto: UpdateEventDto) {
return this.eventsService.update(id, updateEventDto);
}
@Delete(':id')
remove(@Param('id') id: string) {
return this.eventsService.remove(id);
}
}
events.module.ts
Defines the Event module, which imports the necessary services and controllers.
// /src/events/events.module.ts
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { EventsService } from './events.service';
import { EventsController } from './events.controller';
import { Event } from './entities/event.entity';
@Module({
imports: [TypeOrmModule.forFeature([Event])],
controllers: [EventsController],
providers: [EventsService],
})
export class EventsModule {}
events.service.ts
Contains the business logic for managing events, such as creating, updating, and finding events.
// /src/events/events.service.ts
import { Injectable, NotFoundException } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { CreateEventDto } from './dto/create-event.dto';
import { UpdateEventDto } from './dto/update-event.dto';
import { Event } from './entities/event.entity';
@Injectable()
export class EventsService {
constructor(
@InjectRepository(Event)
private eventsRepository: Repository<Event>,
) {}
create(createEventDto: CreateEventDto): Promise<Event> {
const event = this.eventsRepository.create(createEventDto);
return this.eventsRepository.save(event);
}
findAll(): Promise<Event[]> {
return this.eventsRepository.find();
}
async findOne(id: string): Promise<Event> {
const event = await this.eventsRepository.findOneBy({ id });
if (!event) {
throw new NotFoundException(`Event with ID ${id} not found`);
}
return event;
}
async update(id: string, updateEventDto: UpdateEventDto): Promise<Event> {
await this.eventsRepository.update(id, updateEventDto);
return this.findOne(id);
}
async remove(id: string): Promise<void> {
await this.findOne(id); // Ensure event exists
await this.eventsRepository.delete(id);
}
}
event.entity.ts
Defines the database schema for events using TypeORM.
// /src/events/entities/event.entity.ts
import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm';
@Entity()
export class Event {
@PrimaryGeneratedColumn('uuid')
id: string;
@Column()
name: string;
@Column()
description: string;
@Column()
date: string;
@Column('decimal')
price: number;
@Column({ nullable: true })
imageUrl: string;
}
create-event.dto.ts
Data Transfer Object for creating a new event.
// /src/events/dto/create-event.dto.ts
import { IsNotEmpty, IsString, IsDecimal, IsOptional } from 'class-validator';
export class CreateEventDto {
@IsNotEmpty()
@IsString()
name: string;
@IsNotEmpty()
@IsString()
description: string;
@IsNotEmpty()
date: string;
@IsNotEmpty()
@IsDecimal()
price: number;
@IsOptional()
@IsString()
imageUrl?: string;
}
update-event.dto.ts
Data Transfer Object for updating an event.
// /src/events/dto/update-event.dto.ts
import { PartialType } from '@nestjs/mapped-types';
import { CreateEventDto } from './create-event.dto';
export class UpdateEventDto extends PartialType(CreateEventDto) {}
2. Ticketing Module
tickets.controller.ts
Handles HTTP requests related to tickets (e.g., create, update, list tickets).
// /src/tickets/tickets.controller.ts
import { Controller, Get, Post, Body, Param, Patch, Delete } from '@nestjs/common';
import { TicketsService } from './tickets.service';
import { CreateTicketDto } from './dto/create-ticket.dto';
import { UpdateTicketDto } from './dto/update-ticket.dto';
@Controller('tickets')
export class TicketsController {
constructor(private readonly ticketsService: TicketsService) {}
@Post()
create(@Body() createTicketDto: CreateTicketDto) {
return this.ticketsService.create(createTicketDto);
}
@Get()
findAll() {
return this.ticketsService.findAll();
}
@Get(':id')
findOne(@Param('id') id: string) {
return this.ticketsService.findOne(id);
}
@Patch(':id')
update(@Param('id') id: string, @Body() updateTicketDto: UpdateTicketDto) {
return this.ticketsService.update(id, updateTicketDto);
}
@Delete(':id')
remove(@Param('id') id: string) {
return this.ticketsService.remove(id);
}
}
tickets.module.ts
Defines the Ticket module, which imports the necessary services and controllers.
// /src/tickets/tickets.module.ts
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { TicketsService } from './tickets.service';
import { TicketsController } from './tickets.controller';
import { Ticket } from './entities/ticket.entity';
@Module({
imports: [TypeOrmModule.forFeature([Ticket])],
controllers: [TicketsController],
providers: [TicketsService],
})
export class TicketsModule {}
tickets.service.ts
Contains the business logic for managing tickets, such as creating, updating, and finding tickets.
// /src/tickets/tickets.service.ts
import { Injectable, NotFoundException } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { CreateTicketDto } from './dto/create-ticket.dto';
import { UpdateTicketDto } from './dto/update-ticket.dto';
import { Ticket } from './entities/ticket.entity';
@Injectable()
export class TicketsService {
constructor(
@InjectRepository(Ticket)
private ticketsRepository: Repository<Ticket>,
) {}
create(createTicketDto: CreateTicketDto): Promise<Ticket> {
const ticket = this.ticketsRepository.create(createTicketDto);
return this.ticketsRepository.save(ticket);
}
findAll(): Promise<Ticket[]> {
return this.ticketsRepository.find();
}
async findOne(id: string): Promise<Ticket> {
const ticket = await this.ticketsRepository.findOneBy({ id });
if (!ticket) {
throw new NotFoundException(`Ticket with ID ${id} not found`);
}
return ticket;
}
async update(id: string, updateTicketDto: UpdateTicketDto): Promise<Ticket> {
await this.ticketsRepository.update(id, updateTicketDto);
return this.findOne(id);
}
async remove(id: string): Promise<void> {
await this.findOne(id); // Ensure ticket exists
await this.ticketsRepository.delete(id);
}
}
ticket.entity.ts
Defines the database schema for tickets using TypeORM.
// /src/tickets/entities/ticket.entity.ts
import { Entity, Column, PrimaryGeneratedColumn, ManyToOne } from 'typeorm';
import { Event } from '../../events/entities/event.entity';
@Entity()
export class Ticket {
@PrimaryGeneratedColumn('uuid')
id: string
;
@Column()
seatNumber: string;
@Column('decimal')
price: number;
@ManyToOne(() => Event, (event) => event.id)
event: Event;
}
create-ticket.dto.ts
Data Transfer Object for creating a new ticket.
// /src/tickets/dto/create-ticket.dto.ts
import { IsNotEmpty, IsDecimal, IsString } from 'class-validator';
export class CreateTicketDto {
@IsNotEmpty()
@IsString()
seatNumber: string;
@IsNotEmpty()
@IsDecimal()
price: number;
@IsNotEmpty()
@IsString()
eventId: string;
}
update-ticket.dto.ts
Data Transfer Object for updating a ticket.
// /src/tickets/dto/update-ticket.dto.ts
import { PartialType } from '@nestjs/mapped-types';
import { CreateTicketDto } from './create-ticket.dto';
export class UpdateTicketDto extends PartialType(CreateTicketDto) {}
Summary
This setup provides two modules:
-
Event Module:
- Manages creating, updating, and retrieving events.
- Uses TypeORM for interacting with the PostgreSQL database.
- Implements DTOs for validation.
-
Ticketing Module:
- Handles ticket creation and management related to events.
- Uses TypeORM for the ticket database schema.
- Each ticket is associated with an event.
Both modules are designed to be extensible and scalable, allowing for further features like ticket purchase, availability management, and more complex queries across events and tickets.
Here is the complete code implementation for the Payment and Notification modules in a NestJS backend using TypeORM for managing payment transactions and sending notifications via email, SMS, or push notifications.
Folder Structure Overview
/src
├── /payments # Payment module
│ ├── payments.controller.ts
│ ├── payments.module.ts
│ ├── payments.service.ts
│ ├── entities
│ │ ├── payment.entity.ts
│ ├── dto
│ ├── create-payment.dto.ts
├── /notifications # Notification module
│ ├── notifications.controller.ts
│ ├── notifications.module.ts
│ ├── notifications.service.ts
│ ├── dto
│ ├── create-notification.dto.ts
1. Payment Module
payments.controller.ts
Handles the HTTP requests related to payments (e.g., processing a payment, fetching payment details).
// /src/payments/payments.controller.ts
import { Controller, Get, Post, Body, Param } 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()
processPayment(@Body() createPaymentDto: CreatePaymentDto) {
return this.paymentsService.processPayment(createPaymentDto);
}
@Get(':id')
findOne(@Param('id') id: string) {
return this.paymentsService.findOne(id);
}
}
payments.module.ts
Defines the Payment module, which imports the necessary services and controllers.
// /src/payments/payments.module.ts
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { PaymentsService } from './payments.service';
import { PaymentsController } from './payments.controller';
import { Payment } from './entities/payment.entity';
@Module({
imports: [TypeOrmModule.forFeature([Payment])],
controllers: [PaymentsController],
providers: [PaymentsService],
})
export class PaymentsModule {}
payments.service.ts
Contains the business logic for managing payments, such as processing and retrieving payments.
// /src/payments/payments.service.ts
import { Injectable, NotFoundException } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { CreatePaymentDto } from './dto/create-payment.dto';
import { Payment } from './entities/payment.entity';
@Injectable()
export class PaymentsService {
constructor(
@InjectRepository(Payment)
private paymentsRepository: Repository<Payment>,
) {}
async processPayment(createPaymentDto: CreatePaymentDto): Promise<Payment> {
// Placeholder logic for payment processing (integrate with a real payment provider like Stripe or PayPal)
const payment = this.paymentsRepository.create(createPaymentDto);
return this.paymentsRepository.save(payment);
}
async findOne(id: string): Promise<Payment> {
const payment = await this.paymentsRepository.findOneBy({ id });
if (!payment) {
throw new NotFoundException(`Payment with ID ${id} not found`);
}
return payment;
}
}
payment.entity.ts
Defines the database schema for payments using TypeORM.
// /src/payments/entities/payment.entity.ts
import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm';
@Entity()
export class Payment {
@PrimaryGeneratedColumn('uuid')
id: string;
@Column()
userId: string;
@Column()
eventId: string;
@Column('decimal')
amount: number;
@Column()
paymentMethod: string; // e.g., 'credit card', 'paypal'
@Column()
status: string; // e.g., 'pending', 'completed', 'failed'
@Column({ type: 'timestamp', default: () => 'CURRENT_TIMESTAMP' })
createdAt: Date;
}
create-payment.dto.ts
Data Transfer Object for creating a new payment.
// /src/payments/dto/create-payment.dto.ts
import { IsNotEmpty, IsDecimal } from 'class-validator';
export class CreatePaymentDto {
@IsNotEmpty()
userId: string;
@IsNotEmpty()
eventId: string;
@IsNotEmpty()
@IsDecimal()
amount: number;
@IsNotEmpty()
paymentMethod: string; // e.g., 'credit card', 'paypal'
}
2. Notification Module
notifications.controller.ts
Handles the HTTP requests related to notifications (e.g., sending an email or SMS notification).
// /src/notifications/notifications.controller.ts
import { Controller, Post, Body } from '@nestjs/common';
import { NotificationsService } from './notifications.service';
import { CreateNotificationDto } from './dto/create-notification.dto';
@Controller('notifications')
export class NotificationsController {
constructor(private readonly notificationsService: NotificationsService) {}
@Post()
sendNotification(@Body() createNotificationDto: CreateNotificationDto) {
return this.notificationsService.sendNotification(createNotificationDto);
}
}
notifications.module.ts
Defines the Notification module, which imports the necessary services and controllers.
// /src/notifications/notifications.module.ts
import { Module } from '@nestjs/common';
import { NotificationsService } from './notifications.service';
import { NotificationsController } from './notifications.controller';
@Module({
controllers: [NotificationsController],
providers: [NotificationsService],
})
export class NotificationsModule {}
notifications.service.ts
Contains the business logic for sending notifications, such as emails, SMS, or push notifications.
// /src/notifications/notifications.service.ts
import { Injectable } from '@nestjs/common';
import { CreateNotificationDto } from './dto/create-notification.dto';
import * as nodemailer from 'nodemailer'; // For email notifications (use an actual provider for SMS)
@Injectable()
export class NotificationsService {
async sendNotification(createNotificationDto: CreateNotificationDto): Promise<any> {
const { recipient, message, type } = createNotificationDto;
if (type === 'email') {
return this.sendEmail(recipient, message);
}
if (type === 'sms') {
return this.sendSms(recipient, message);
}
if (type === 'push') {
return this.sendPushNotification(recipient, message);
}
}
private async sendEmail(recipient: string, message: string) {
const transporter = nodemailer.createTransport({
service: 'Gmail', // Use your actual email service provider here
auth: {
user: 'your-email@gmail.com',
pass: 'your-email-password',
},
});
const mailOptions = {
from: 'your-email@gmail.com',
to: recipient,
subject: 'Notification',
text: message,
};
return transporter.sendMail(mailOptions);
}
private async sendSms(recipient: string, message: string) {
// Implement SMS sending logic (e.g., using Twilio)
console.log(`Sending SMS to ${recipient}: ${message}`);
return { success: true };
}
private async sendPushNotification(recipient: string, message: string) {
// Implement push notification logic (e.g., using Firebase Cloud Messaging)
console.log(`Sending push notification to ${recipient}: ${message}`);
return { success: true };
}
}
create-notification.dto.ts
Data Transfer Object for creating a notification request.
// /src/notifications/dto/create-notification.dto.ts
import { IsNotEmpty, IsEmail, IsIn } from 'class-validator';
export class CreateNotificationDto {
@IsNotEmpty()
@IsEmail()
recipient: string; // Can be email or phone number depending on the type
@IsNotEmpty()
message: string;
@IsNotEmpty()
@IsIn(['email', 'sms', 'push'])
type: string; // e.g., 'email', 'sms', 'push'
}
Summary
This setup provides two modules:
-
Payment Module:
- Manages creating and retrieving payments.
- Uses TypeORM for interacting with the PostgreSQL database.
- Implements DTOs for validating incoming payment data.
-
Notification Module:
- Handles sending email, SMS, and push notifications.
- Uses nodemailer for email notifications (you can extend it to use Twilio for SMS or Firebase for push notifications).
- Implements DTOs for validating the notification requests.
You can extend this solution by integrating with payment gateways (e.g., Stripe, PayPal) and notification services (e.g., Twilio for SMS, Firebase for push notifications).
Here is the full implementation of the database module and the common utilities like exception filters, interceptors, decorators, and pipes in a NestJS application.
Folder Structure Overview
/src
├── /database # Database module
│ ├── database.module.ts
│ ├── database.service.ts
│ ├── ormconfig.ts # TypeORM configuration
├── /common # Shared utilities, guards, interceptors, and exceptions
│ ├── /filters # Global exception filters
│ │ ├── all-exceptions.filter.ts
│ ├── /interceptors # Interceptors for response transformation, logging, etc.
│ │ ├── transform.interceptor.ts
│ ├── /decorators # Reusable decorators
│ │ ├── api-response.decorator.ts
│ ├── /pipes # Global pipes for validation, etc.
│ │ ├── validation.pipe.ts
├── app.module.ts # Root application module
├── main.ts # Main entry point of the application
1. Database Module
database.module.ts
This module configures TypeORM to connect to the database. The connection details are imported from the ormconfig.ts file.
// /src/database/database.module.ts
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { DatabaseService } from './database.service';
import ormconfig from './ormconfig';
@Module({
imports: [TypeOrmModule.forRoot(ormconfig)],
providers: [DatabaseService],
exports: [DatabaseService],
})
export class DatabaseModule {}
database.service.ts
This service can be used to perform raw database operations if needed. Typically, for most cases, you will use TypeORM repositories.
// /src/database/database.service.ts
import { Injectable } from '@nestjs/common';
import { DataSource } from 'typeorm';
@Injectable()
export class DatabaseService {
constructor(private readonly dataSource: DataSource) {}
async runRawQuery(query: string): Promise<any> {
return this.dataSource.query(query);
}
}
ormconfig.ts
The TypeORM configuration file, where you define your database connection details and entities.
// /src/database/ormconfig.ts
import { TypeOrmModuleOptions } from '@nestjs/typeorm';
import { User } from '../users/entities/user.entity';
import { Event } from '../events/entities/event.entity';
import { Ticket } from '../tickets/entities/ticket.entity';
import { Payment } from '../payments/entities/payment.entity';
const ormconfig: TypeOrmModuleOptions = {
type: 'postgres',
host: process.env.DB_HOST || 'localhost',
port: Number(process.env.DB_PORT) || 5432,
username: process.env.DB_USERNAME || 'postgres',
password: process.env.DB_PASSWORD || 'postgres',
database: process.env.DB_NAME || 'event_platform',
entities: [User, Event, Ticket, Payment],
synchronize: true, // Set to false in production
};
export default ormconfig;
2. Common Module
Exception Filters
all-exceptions.filter.ts
A global exception filter to catch and handle all exceptions in a uniform way.
// /src/common/filters/all-exceptions.filter.ts
import {
ExceptionFilter,
Catch,
ArgumentsHost,
HttpException,
HttpStatus,
} from '@nestjs/common';
import { Request, Response } from 'express';
@Catch()
export class AllExceptionsFilter 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;
const message =
exception instanceof HttpException
? exception.getResponse()
: 'Internal server error';
response.status(status).json({
statusCode: status,
timestamp: new Date().toISOString(),
path: request.url,
message,
});
}
}
Interceptors
transform.interceptor.ts
This interceptor automatically transforms responses before sending them to the client, making it easier to standardize API responses.
// /src/common/interceptors/transform.interceptor.ts
import {
Injectable,
NestInterceptor,
ExecutionContext,
CallHandler,
} from '@nestjs/common';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
@Injectable()
export class TransformInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
return next.handle().pipe(
map((data) => ({
success: true,
data,
})),
);
}
}
Decorators
api-response.decorator.ts
This reusable decorator can be used to annotate API responses with a consistent structure.
// /src/common/decorators/api-response.decorator.ts
import { applyDecorators } from '@nestjs/common';
import { ApiResponse, ApiOperation } from '@nestjs/swagger';
export const ApiStandardResponse = (description: string) => {
return applyDecorators(
ApiOperation({ description }),
ApiResponse({
status: 200,
description: 'Request was successful',
}),
ApiResponse({
status: 400,
description: 'Bad Request',
}),
ApiResponse({
status: 500,
description: 'Internal Server Error',
}),
);
};
Pipes
validation.pipe.ts
A global validation pipe that validates incoming requests against the DTOs using class-validator.
// /src/common/pipes/validation.pipe.ts
import {
ArgumentMetadata,
BadRequestException,
Injectable,
PipeTransform,
} from '@nestjs/common';
import { plainToInstance } from 'class-transformer';
import { validate } from 'class-validator';
@Injectable()
export class ValidationPipe implements PipeTransform {
async transform(value: any, { metatype }: ArgumentMetadata) {
if (!metatype || !this.toValidate(metatype)) {
return value;
}
const object = plainToInstance(metatype, value);
const errors = await validate(object);
if (errors.length > 0) {
throw new BadRequestException('Validation failed');
}
return value;
}
private toValidate(metatype: Function): boolean {
const types: Function[] = [String, Boolean, Number, Array, Object];
return !types.includes(metatype);
}
}
3. App Module and Main Entry Point
app.module.ts
The root module of your application that imports all feature modules, including the DatabaseModule and CommonModule.
// /src/app.module.ts
import { Module } from '@nestjs/common';
import { DatabaseModule } from './database/database.module';
import { EventsModule } from './events/events.module';
import { TicketsModule } from './tickets/tickets.module';
import { PaymentsModule } from './payments/payments.module';
import { NotificationsModule } from './notifications/notifications.module';
import { APP_FILTER, APP_INTERCEPTOR, APP_PIPE } from '@nestjs/core';
import { AllExceptionsFilter } from './common/filters/all-exceptions.filter';
import { TransformInterceptor } from './common/interceptors/transform.interceptor';
import { ValidationPipe } from './common/pipes/validation.pipe';
@Module({
imports: [
DatabaseModule,
EventsModule,
TicketsModule,
PaymentsModule,
NotificationsModule,
],
providers: [
{
provide: APP_FILTER,
useClass: AllExceptionsFilter,
},
{
provide: APP_INTERCEPTOR,
useClass: TransformInterceptor,
},
{
provide: APP_PIPE,
useClass: ValidationPipe,
},
],
})
export class AppModule {}
main.ts
The main entry point of the application. This file configures the global filters, pipes, and starts the NestJS server.
// /src/main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { ValidationPipe } from './common/pipes/validation.pipe';
import { AllExceptionsFilter } from './common/filters/all-exceptions.filter';
import { TransformInterceptor } from './common/interceptors/transform.interceptor';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
// Apply global validation pipe
app.useGlobalPipes(new ValidationPipe());
// Apply global exception filter
app.useGlobalFilters(new AllExceptionsFilter());
// Apply global transform interceptor
app.useGlobalInterceptors(new TransformInterceptor());
await app.listen(3000);
}
bootstrap();
Summary
This structure includes:
-
Database Module:
- Configures TypeORM with connection details and entities.
- Can be extended to perform raw database queries or custom operations.
-
Common Utilities:
- Global Exception Filter: Catches and handles all exceptions uniformly.
- Interceptors: Automatically transform API responses into a consistent structure.
- Validation Pipe: Validates incoming requests using DTOs and class-validator.
- Decorators: Reusable decorators for annotating APIs.
-
AppModule and Main Entry:
- The root NestJS application module that ties everything together.
- Configures global pipes, filters, and
interceptors to ensure consistent behavior across the application.
This setup is highly modular, maintainable, and follows NestJS best practices. You can now add additional modules or extend existing ones with this foundation.
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.
Top comments (0)