To develop a Car Rental System using Next.js, NestJS, Tailwind CSS, and PostgreSQL, you can follow a structured approach to create both the frontend and backend, with proper state management, UI design, and database interaction. Here's a high-level plan with suggested technologies and architecture.
Architecture Overview
1. Front End (Next.js with Tailwind CSS):
The frontend will allow users to:
- Search for available cars.
- Filter cars by price, brand, and availability.
- View detailed car listings with specifications and price comparison.
- Book rentals and manage their bookings.
You can leverage Next.js to build a fast, server-rendered React application. Key features like routing, data fetching, and SSR (Server Side Rendering) can be used to ensure smooth and efficient page loads. You can also integrate Tailwind CSS to manage the styling efficiently.
2. Back End (NestJS with PostgreSQL):
The backend will manage:
- Car listings (CRUD operations for admins).
- User profiles, bookings, and rental history.
- Payment gateways integration for rental fees.
- Admin interface to track rental operations, available cars, and bookings.
NestJS will provide a scalable backend API with features like authentication (JWT), ORM (Object Relational Mapping) via TypeORM to interact with PostgreSQL, and other modular services like notifications.
3. Mobile App:
For mobile, you can use React Native for a cross-platform mobile experience where users can:
- Browse and book rentals.
- Track their rental history.
- Receive push notifications for updates.
Key Technologies
-
Frontend:
- Next.js: For building a scalable and performant frontend.
- Tailwind CSS: For styling and responsive design.
- Axios (or fetch): For interacting with the NestJS backend.
-
Backend:
- NestJS: For building the API, managing routes, authentication, and business logic.
- PostgreSQL: For storing car listings, user profiles, and bookings.
- TypeORM: For seamless interaction between NestJS and PostgreSQL.
-
Mobile App:
- React Native: For building the cross-platform mobile app.
Step-by-Step Breakdown
Step 1: Set up the Frontend (Next.js)
- Install Next.js with Tailwind CSS:
npx create-next-app@latest car-rental-frontend
cd car-rental-frontend
npx tailwindcss init -p
Configure Tailwind CSS in your Next.js project by editing tailwind.config.js
and including Tailwind directives in your global CSS file.
-
Create Pages:
- /cars: List available cars with filters for price, brand, and availability.
- /car/[id]: Dynamic route to display individual car details.
- /bookings: User dashboard to manage bookings.
-
Use Next.js Features:
- Use API routes to communicate with the backend.
- Implement SSR/SSG for SEO optimization and faster loading.
Step 2: Set up the Backend (NestJS with PostgreSQL)
- Create NestJS Project:
nest new car-rental-backend
-
Install Required Packages:
- TypeORM and PostgreSQL for database:
npm install @nestjs/typeorm typeorm pg
-
JWT Authentication:
npm install @nestjs/jwt passport-jwt
Payment integration (e.g., Stripe or PayPal).
-
Set up Modules:
- AuthModule: Handle user registration, login, JWT authentication.
- CarsModule: Manage car listings (CRUD operations for admin).
- BookingModule: Manage bookings and rental logic.
- PaymentModule: Manage payment transactions.
Set up Database:
Define your entities (Car, User, Booking) with TypeORM and handle migrations.
@Entity()
export class Car {
@PrimaryGeneratedColumn()
id: number;
@Column()
name: string;
@Column()
brand: string;
@Column('decimal')
pricePerDay: number;
@Column()
availability: boolean;
}
-
Create API Endpoints:
-
POST /auth/login
: Handle user authentication. -
GET /cars
: Fetch available cars. -
POST /bookings
: Handle bookings. -
GET /bookings/:id
: Get booking details.
-
Step 3: Mobile App with React Native
- Set up React Native:
npx react-native init CarRentalApp
Integrate the API from the backend using Axios or fetch.
-
Create Screens:
- Home: Browse available cars.
- Car Details: View car details and proceed with booking.
- Bookings: View past and current bookings.
Notifications:
Use Firebase Cloud Messaging (FCM) or another service to notify users about updates on their rentals.
Deployment Considerations
- Frontend: Deploy on platforms like Vercel (ideal for Next.js apps).
- Backend: Host the backend API on Heroku, AWS, or any cloud provider.
- Database: Use AWS RDS or another cloud-hosted PostgreSQL service.
This modular architecture will help you scale the application in the future while keeping the code maintainable. Let me know if you need help with any specific step, such as setting up authentication or database relations!
For a Next.js frontend of your Car Rental System, using Tailwind CSS for styling and organizing the project with best practices, here’s a proper file and folder structure:
Project Structure
car-rental-frontend/
├── public/
│ ├── images/
│ └── favicon.ico
├── src/
│ ├── components/
│ │ ├── CarCard.tsx
│ │ ├── CarFilter.tsx
│ │ ├── Navbar.tsx
│ │ └── Footer.tsx
│ ├── pages/
│ │ ├── api/
│ │ │ └── auth.ts
│ │ ├── cars/
│ │ │ ├── index.tsx
│ │ │ └── [id].tsx
│ │ ├── bookings/
│ │ │ ├── index.tsx
│ │ └── index.tsx
│ ├── styles/
│ │ ├── globals.css
│ │ └── tailwind.css
│ ├── utils/
│ │ ├── axios.ts
│ │ └── helpers.ts
│ ├── hooks/
│ │ └── useAuth.ts
│ ├── contexts/
│ │ └── AuthContext.tsx
│ ├── services/
│ │ └── carService.ts
│ └── layouts/
│ └── MainLayout.tsx
├── tailwind.config.js
├── postcss.config.js
├── next.config.js
├── package.json
└── tsconfig.json
Folder Breakdown
1. public/
This folder holds all static assets that need to be served directly, such as images, logos, and the favicon.
-
images/
: Store your static images (car photos, logos, etc.). -
favicon.ico
: The website's favicon.
2. src/ (Source Directory)
The main application code lives here, structured for scalability and modularity.
2.1 components/
Reusable UI components are placed here. Each component is self-contained, making the code modular and easy to maintain.
-
CarCard.tsx
: Displays a car in the list with details like name, price, and image. -
CarFilter.tsx
: A filter component for searching by price, availability, or brand. -
Navbar.tsx
: The navigation bar used across the website. -
Footer.tsx
: The footer component.
2.2 pages/ (Next.js Routing)
Next.js uses the pages/
directory for routing. Each file inside this folder becomes a route.
-
index.tsx
: The home page, showing car listings and filters. -
cars/
:-
index.tsx
: The list of all available cars with filtering options. -
[id].tsx
: Dynamic route to display individual car details (e.g.,/cars/123
for a car with id123
).
-
-
bookings/
:-
index.tsx
: The page where users can view their bookings and manage them.
-
Next.js automatically handles routing based on the folder and file structure. Dynamic routes are defined by wrapping parts of filenames with square brackets ([id].tsx
for individual car pages).
2.3 styles/
For Tailwind CSS and any global styles.
-
globals.css
: General styles or global overrides. -
tailwind.css
: Tailwind CSS imports and configuration.
2.4 utils/
Helper functions and utility code.
-
axios.ts
: Configure your Axios instance for making API calls to the NestJS backend. -
helpers.ts
: General utility functions (e.g., format prices, date handling).
2.5 hooks/
Custom React hooks to reuse logic.
-
useAuth.ts
: A custom hook for managing authentication state (login, logout, JWT token handling).
2.6 contexts/
Context API files for managing global state.
-
AuthContext.tsx
: Context provider to manage user authentication globally (provides user data, login, and logout functionality).
2.7 services/
Service files for API interactions.
-
carService.ts
: Functions for fetching car data, car availability, booking a car, etc.
2.8 layouts/
Layout components for structuring your pages. Layouts are used for consistent page structures like the header, footer, and navigation.
-
MainLayout.tsx
: A common layout used by most pages. It can include components likeNavbar
,Footer
, and have achildren
prop for rendering specific page content.
Root-Level Files
-
tailwind.config.js
: Configuration for Tailwind CSS. -
postcss.config.js
: PostCSS configuration (for transforming Tailwind CSS). -
next.config.js
: Next.js configuration file, where custom settings can be defined. -
package.json
: Holds project dependencies and scripts. -
tsconfig.json
: Configuration file for TypeScript.
Example Code Snippets
components/CarCard.tsx
import React from 'react';
interface CarCardProps {
id: number;
name: string;
brand: string;
pricePerDay: number;
imageUrl: string;
}
const CarCard: React.FC<CarCardProps> = ({ id, name, brand, pricePerDay, imageUrl }) => {
return (
<div className="bg-white shadow-md rounded-lg overflow-hidden">
<img src={imageUrl} alt={name} className="w-full h-48 object-cover" />
<div className="p-4">
<h3 className="text-lg font-bold">{name}</h3>
<p className="text-sm text-gray-600">{brand}</p>
<p className="text-md font-semibold text-indigo-600">${pricePerDay} / day</p>
</div>
</div>
);
};
export default CarCard;
layouts/MainLayout.tsx
import React from 'react';
import Navbar from '../components/Navbar';
import Footer from '../components/Footer';
const MainLayout: React.FC<{ children: React.ReactNode }> = ({ children }) => {
return (
<div className="min-h-screen flex flex-col">
<Navbar />
<main className="flex-grow">{children}</main>
<Footer />
</div>
);
};
export default MainLayout;
pages/cars/[id].tsx
import { GetServerSideProps } from 'next';
import MainLayout from '../../layouts/MainLayout';
import { fetchCarById } from '../../services/carService';
interface CarPageProps {
car: {
id: number;
name: string;
brand: string;
pricePerDay: number;
description: string;
};
}
const CarPage: React.FC<CarPageProps> = ({ car }) => {
return (
<MainLayout>
<div className="container mx-auto p-4">
<h1 className="text-2xl font-bold">{car.name}</h1>
<p className="text-md">{car.brand}</p>
<p className="text-xl text-indigo-600">${car.pricePerDay} / day</p>
<p className="mt-4">{car.description}</p>
</div>
</MainLayout>
);
};
export const getServerSideProps: GetServerSideProps = async (context) => {
const { id } = context.params!;
const car = await fetchCarById(id as string);
return { props: { car } };
};
export default CarPage;
This structure will allow for an organized and scalable frontend. If you need additional details for specific functionality or setup (like authentication, booking, or API integration), feel free to ask!
For the Car Rental System backend, using NestJS, PostgreSQL, and TypeORM, you'll want to organize your project in a modular, scalable way. Below is a complete file and folder structure for your NestJS backend, along with brief descriptions for each part.
Backend File and Folder Structure
car-rental-backend/
├── src/
│ ├── auth/
│ │ ├── dto/
│ │ │ ├── login.dto.ts
│ │ │ ├── register.dto.ts
│ │ ├── auth.controller.ts
│ │ ├── auth.module.ts
│ │ ├── auth.service.ts
│ │ ├── jwt.strategy.ts
│ │ └── local.strategy.ts
│ ├── cars/
│ │ ├── dto/
│ │ │ ├── create-car.dto.ts
│ │ │ ├── update-car.dto.ts
│ │ ├── entities/
│ │ │ └── car.entity.ts
│ │ ├── cars.controller.ts
│ │ ├── cars.module.ts
│ │ ├── cars.service.ts
│ ├── bookings/
│ │ ├── dto/
│ │ │ ├── create-booking.dto.ts
│ │ │ ├── update-booking.dto.ts
│ │ ├── entities/
│ │ │ └── booking.entity.ts
│ │ ├── bookings.controller.ts
│ │ ├── bookings.module.ts
│ │ ├── bookings.service.ts
│ ├── users/
│ │ ├── dto/
│ │ │ └── create-user.dto.ts
│ │ ├── entities/
│ │ │ └── user.entity.ts
│ │ ├── users.controller.ts
│ │ ├── users.module.ts
│ │ ├── users.service.ts
│ ├── payments/
│ │ ├── dto/
│ │ │ └── create-payment.dto.ts
│ │ ├── payments.controller.ts
│ │ ├── payments.module.ts
│ │ ├── payments.service.ts
│ ├── common/
│ │ ├── decorators/
│ │ │ └── roles.decorator.ts
│ │ ├── guards/
│ │ │ └── jwt-auth.guard.ts
│ │ ├── interceptors/
│ │ │ └── logging.interceptor.ts
│ │ ├── filters/
│ │ │ └── http-exception.filter.ts
│ │ └── constants.ts
│ ├── database/
│ │ ├── entities/
│ │ │ └── base.entity.ts
│ │ ├── database.module.ts
│ │ └── database.service.ts
│ ├── app.module.ts
│ ├── main.ts
├── config/
│ ├── jwt.config.ts
│ ├── typeorm.config.ts
│ └── app.config.ts
├── migrations/
│ ├── 1682570845798-CreateCarEntity.ts
│ └── 1682570845799-CreateBookingEntity.ts
├── .env
├── .gitignore
├── nest-cli.json
├── tsconfig.json
├── package.json
└── README.md
Folder Breakdown
1. src/ (Source Code Directory)
This is the main directory where all the application logic lives.
1.1 auth/
Handles the authentication process, including user login, registration, JWT token management, and strategies.
-
auth.controller.ts
: Handles HTTP routes related to authentication (e.g.,/login
,/register
). -
auth.module.ts
: Encapsulates the authentication-related services and controllers. -
auth.service.ts
: Contains the business logic for user authentication and token management. -
jwt.strategy.ts
: JWT-based strategy for protecting routes. -
local.strategy.ts
: For handling local username/password authentication. -
dto/: Data Transfer Objects for validating and typing data during registration and login processes.
-
login.dto.ts
: DTO for login requests. -
register.dto.ts
: DTO for registration requests.
-
1.2 cars/
Handles car-related operations, such as CRUD operations on car listings.
-
cars.controller.ts
: API routes for car listings (e.g.,/cars
,/cars/:id
). -
cars.module.ts
: Encapsulates the car-related services and controllers. -
cars.service.ts
: Contains the logic for managing car data. -
entities/: Defines the data models for the cars.
-
car.entity.ts
: The TypeORM entity for theCar
model.
-
1.3 bookings/
Manages the booking process for renting cars.
-
bookings.controller.ts
: API routes for booking operations (e.g.,/bookings
,/bookings/:id
). -
bookings.module.ts
: Encapsulates the booking-related services and controllers. -
bookings.service.ts
: Business logic for managing bookings. -
entities/: TypeORM entity for the bookings.
-
booking.entity.ts
: TheBooking
entity representing the booking data in the database.
-
1.4 users/
Manages user profiles and related operations.
-
users.controller.ts
: API routes for managing user profiles (e.g.,/users
,/users/:id
). -
users.module.ts
: Encapsulates user services and controllers. -
users.service.ts
: Business logic for managing users. -
entities/: Contains the user-related entities.
-
user.entity.ts
: TypeORM entity for theUser
model.
-
1.5 payments/
Handles payment processing and integration with third-party payment gateways like Stripe or PayPal.
-
payments.controller.ts
: API routes for payments. -
payments.module.ts
: Encapsulates payment services and controllers. -
payments.service.ts
: Business logic for payment processing.
1.6 common/
Contains reusable modules, decorators, guards, interceptors, and filters that can be used across the app.
-
decorators/: Custom decorators like
@Roles()
.-
roles.decorator.ts
: Handles role-based access control.
-
-
guards/: Guards like
JWTGuard
to protect routes.-
jwt-auth.guard.ts
: Guard to verify JWT token validity.
-
-
interceptors/: HTTP interceptors.
-
logging.interceptor.ts
: Example of logging interceptor for requests.
-
-
filters/: Exception filters.
-
http-exception.filter.ts
: Handles global HTTP exceptions.
-
1.7 database/
Manages database connection and entity definitions.
-
base.entity.ts
: Base entity that all other entities can extend (e.g., with common fields likecreatedAt
,updatedAt
). -
database.module.ts
: Encapsulates the database connection logic. -
database.service.ts
: Service for database management and connection pooling.
1.8 app.module.ts
The root module of your NestJS app that imports all other modules (auth, cars, bookings, etc.).
1.9 main.ts
The entry point of your NestJS application. This file bootstraps the application.
2. config/
This folder holds configuration files for different parts of the application, such as JWT, TypeORM, and global app configuration.
-
jwt.config.ts
: JWT configuration (secret, expiration). -
typeorm.config.ts
: Database configuration for PostgreSQL, includes database connection settings. -
app.config.ts
: General application configurations, such as CORS, global prefix, etc.
3. migrations/
This folder contains database migration files generated by TypeORM.
-
1682570845798-CreateCarEntity.ts
: A migration to create theCar
entity. -
1682570845799-CreateBookingEntity.ts
: A migration to create theBooking
entity.
Root-Level Files
-
.env
: Environment variables (e.g., database connection, JWT secret, API keys). -
.gitignore
: Specifies files to ignore in version control (e.g.,node_modules/
,.env
). -
nest-cli.json
: NestJS CLI configuration file. -
tsconfig.json
: TypeScript configuration file. -
package.json
: Lists project dependencies and scripts. -
README.md
: A readme file for documenting the project setup, purpose, and usage.
Example Code Snippets
src/cars/entities/car.entity.ts
import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm';
@Entity()
export class Car {
@PrimaryGeneratedColumn()
id: number;
@Column()
name: string;
@Column()
brand: string;
@Column('decimal')
pricePerDay: number;
@Column()
availability: boolean;
}
src/bookings/entities/booking.entity.ts
import { Entity, PrimaryGeneratedColumn, ManyToOne, Column } from 'typeorm';
import { Car } from '../../cars/entities/car.entity';
import { User } from '../../users/entities/user.entity';
@Entity()
export class Booking {
@PrimaryGeneratedColumn()
id: number;
@ManyToOne(() => Car)
car: Car;
@ManyToOne(() => User)
user: User;
@Column('date')
rentalDate: string;
@Column('date')
returnDate: string;
@Column('decimal')
totalAmount: number;
}
src/auth/jwt.strategy.ts
import { Injectable } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { ExtractJwt, Strategy } from 'passport-jwt';
import { JwtPayload } from './jwt-payload.interface';
@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor() {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
ignoreExpiration: false,
secretOrKey: process.env.JWT_SECRET,
});
}
async validate(payload: JwtPayload) {
return { userId: payload.sub, username: payload.username };
}
}
This structure will help you keep your backend clean, modular, and scalable, allowing each module (e.g., cars, bookings, payments) to handle its own logic independently. Let me know if you need further details or help setting up specific parts!
Here is the full code for the specified folder structure, including the components, pages, and API route for a basic Car Rental System using Next.js and Tailwind CSS. This setup includes components like CarCard
, CarFilter
, Navbar
, and Footer
, as well as pages for car listings, booking management, and authentication API routes.
Project Structure
car-rental-frontend/
├── public/
│ ├── images/
│ └── favicon.ico
├── src/
│ ├── components/
│ │ ├── CarCard.tsx
│ │ ├── CarFilter.tsx
│ │ ├── Navbar.tsx
│ │ └── Footer.tsx
│ ├── pages/
│ │ ├── api/
│ │ │ └── auth.ts
│ │ ├── cars/
│ │ │ ├── index.tsx
│ │ │ └── [id].tsx
│ │ ├── bookings/
│ │ │ └── index.tsx
│ │ └── index.tsx
1. components/CarCard.tsx
This component represents a single car card in a list, displaying the car's details.
import React from 'react';
interface CarCardProps {
id: number;
name: string;
brand: string;
pricePerDay: number;
imageUrl: string;
}
const CarCard: React.FC<CarCardProps> = ({ id, name, brand, pricePerDay, imageUrl }) => {
return (
<div className="bg-white shadow-md rounded-lg overflow-hidden">
<img src={imageUrl} alt={name} className="w-full h-48 object-cover" />
<div className="p-4">
<h3 className="text-lg font-bold">{name}</h3>
<p className="text-sm text-gray-600">{brand}</p>
<p className="text-md font-semibold text-indigo-600">${pricePerDay} / day</p>
</div>
</div>
);
};
export default CarCard;
2. components/CarFilter.tsx
A filter component to allow users to search for cars by brand, price, and availability.
import React, { useState } from 'react';
interface CarFilterProps {
onFilterChange: (filters: { brand: string; price: number }) => void;
}
const CarFilter: React.FC<CarFilterProps> = ({ onFilterChange }) => {
const [brand, setBrand] = useState('');
const [price, setPrice] = useState(100);
const handleFilterChange = () => {
onFilterChange({ brand, price });
};
return (
<div className="flex space-x-4 mb-4">
<input
type="text"
placeholder="Brand"
value={brand}
onChange={(e) => setBrand(e.target.value)}
className="border p-2 rounded"
/>
<input
type="number"
placeholder="Max Price"
value={price}
onChange={(e) => setPrice(Number(e.target.value))}
className="border p-2 rounded"
/>
<button
onClick={handleFilterChange}
className="bg-indigo-600 text-white p-2 rounded"
>
Apply Filters
</button>
</div>
);
};
export default CarFilter;
3. components/Navbar.tsx
A simple navigation bar component.
import React from 'react';
import Link from 'next/link';
const Navbar: React.FC = () => {
return (
<nav className="bg-gray-800 p-4">
<div className="container mx-auto flex justify-between">
<Link href="/">
<a className="text-white font-bold text-lg">Car Rental</a>
</Link>
<div>
<Link href="/cars">
<a className="text-white mr-4">Cars</a>
</Link>
<Link href="/bookings">
<a className="text-white">Bookings</a>
</Link>
</div>
</div>
</nav>
);
};
export default Navbar;
4. components/Footer.tsx
A basic footer component.
import React from 'react';
const Footer: React.FC = () => {
return (
<footer className="bg-gray-800 p-4 mt-8 text-center text-white">
<p>© 2024 Car Rental System. All rights reserved.</p>
</footer>
);
};
export default Footer;
5. pages/api/auth.ts
An API route that handles user authentication (simplified).
import type { NextApiRequest, NextApiResponse } from 'next';
export default (req: NextApiRequest, res: NextApiResponse) => {
if (req.method === 'POST') {
const { username, password } = req.body;
if (username === 'user' && password === 'password') {
return res.status(200).json({ token: 'fake-jwt-token' });
}
return res.status(401).json({ message: 'Invalid credentials' });
}
return res.status(405).json({ message: 'Method not allowed' });
};
6. pages/cars/index.tsx
The car listing page with filtering capabilities.
import React, { useState } from 'react';
import CarCard from '../../components/CarCard';
import CarFilter from '../../components/CarFilter';
import MainLayout from '../../layouts/MainLayout';
const carsData = [
{ id: 1, name: 'Tesla Model S', brand: 'Tesla', pricePerDay: 100, imageUrl: '/images/tesla.jpg' },
{ id: 2, name: 'BMW i8', brand: 'BMW', pricePerDay: 150, imageUrl: '/images/bmw.jpg' },
{ id: 3, name: 'Audi A8', brand: 'Audi', pricePerDay: 120, imageUrl: '/images/audi.jpg' },
];
const CarsPage: React.FC = () => {
const [filteredCars, setFilteredCars] = useState(carsData);
const handleFilterChange = (filters: { brand: string; price: number }) => {
const filtered = carsData.filter(
(car) =>
car.brand.toLowerCase().includes(filters.brand.toLowerCase()) &&
car.pricePerDay <= filters.price
);
setFilteredCars(filtered);
};
return (
<MainLayout>
<div className="container mx-auto p-4">
<h1 className="text-2xl font-bold mb-4">Available Cars</h1>
<CarFilter onFilterChange={handleFilterChange} />
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-4">
{filteredCars.map((car) => (
<CarCard key={car.id} {...car} />
))}
</div>
</div>
</MainLayout>
);
};
export default CarsPage;
7. pages/cars/[id].tsx
The car details page (dynamic route for each car).
import React from 'react';
import { GetServerSideProps } from 'next';
import MainLayout from '../../layouts/MainLayout';
interface CarDetailsProps {
car: {
id: number;
name: string;
brand: string;
pricePerDay: number;
imageUrl: string;
description: string;
};
}
const CarDetailsPage: React.FC<CarDetailsProps> = ({ car }) => {
return (
<MainLayout>
<div className="container mx-auto p-4">
<img src={car.imageUrl} alt={car.name} className="w-full h-64 object-cover rounded-md mb-4" />
<h1 className="text-3xl font-bold mb-2">{car.name}</h1>
<p className="text-lg mb-2">Brand: {car.brand}</p>
<p className="text-lg mb-2 text-indigo-600">Price: ${car.pricePerDay} / day</p>
<p>{car.description}</p>
</div>
</MainLayout>
);
};
export const getServerSideProps: GetServerSideProps = async (context) => {
const { id } = context.params!;
const car = {
id,
name: `Car ${id}`,
brand: 'Brand',
pricePerDay: 100,
imageUrl: '/images/car.jpg',
description: 'A detailed description of the car.',
};
return {
props: {
car,
},
};
};
export default CarDetailsPage;
8. pages/bookings/index.tsx
The bookings page where users can view their past and upcoming bookings.
import React from 'react';
import MainLayout from '../../layouts/MainLayout';
const bookingsData = [
{ id: 1, car: 'Tesla Model S', date: '2024-09-15' },
{ id: 2, car: 'BMW i8', date: '2024-09-20' },
];
const BookingsPage: React.FC = () => {
return (
<MainLayout>
<div className="container mx-auto p-4">
<h1 className="text-2xl font-bold mb-4">Your Bookings</h1>
<ul className="list-disc pl-5">
{bookingsData.map((booking) => (
<li key={booking.id} className="mb-2">
<span className="font-bold">{booking.car}</span> - Booking Date: {booking.date}
</li>
))}
</ul>
</div>
</MainLayout>
);
};
export default BookingsPage;
9. pages/index.tsx
The homepage.
import React from 'react';
import Link from 'next/link';
import MainLayout from '../layouts/MainLayout';
const HomePage: React.FC = () => {
return (
<MainLayout>
<div className="container mx-auto p-4 text-center">
<h1 className="text-4xl font-bold mb-6">Welcome to Car Rental</h1>
<p className="text-lg mb-4">Browse through our wide range of cars available for rental.</p>
<Link href="/cars">
<a className="bg-indigo-600 text-white px-4 py-2 rounded">View Cars</a>
</Link>
</div>
</MainLayout>
);
};
export default HomePage;
This setup provides a well-structured Next.js project with car listings, filtering, bookings, and car details. You can easily expand it by adding more functionality such as authentication, booking management, or integration with a backend API. Let me know if you need help with further customizations!
Here's the full code for the additional parts of your project, including styles, utils, hooks, contexts, services, and layouts for your Car Rental System built using Next.js, Tailwind CSS, and TypeScript.
1. styles/globals.css
This file contains your global CSS and Tailwind configuration.
@tailwind base;
@tailwind components;
@tailwind utilities;
html,
body {
padding: 0;
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
}
* {
box-sizing: border-box;
}
2. styles/tailwind.css
This is your entry point for Tailwind-specific styles. It enables the Tailwind CSS classes to be used across your app.
@tailwind base;
@tailwind components;
@tailwind utilities;
3. utils/axios.ts
Here, you create an Axios instance that can be reused throughout your application for API requests. This is where you can configure base URLs, headers, and other options.
import axios from 'axios';
const axiosInstance = axios.create({
baseURL: process.env.NEXT_PUBLIC_API_BASE_URL || 'http://localhost:3000/api',
headers: {
'Content-Type': 'application/json',
},
});
export default axiosInstance;
4. utils/helpers.ts
Utility functions that can be reused across the app.
// Utility to format currency values
export const formatPrice = (price: number): string => {
return `$${price.toFixed(2)}`;
};
// Utility to format dates
export const formatDate = (date: string): string => {
const options: Intl.DateTimeFormatOptions = { year: 'numeric', month: 'long', day: 'numeric' };
return new Date(date).toLocaleDateString(undefined, options);
};
5. hooks/useAuth.ts
This custom hook helps manage the authentication state in your application. It checks if the user is logged in, handles login/logout, and manages tokens.
import { useContext } from 'react';
import { AuthContext } from '../contexts/AuthContext';
export const useAuth = () => {
const { user, login, logout } = useContext(AuthContext);
return { user, login, logout };
};
6. contexts/AuthContext.tsx
The authentication context manages global state for the user's authentication status, providing login
, logout
, and user
across the app.
import React, { createContext, useState, useEffect } from 'react';
import axios from '../utils/axios';
interface AuthContextProps {
user: any;
login: (username: string, password: string) => Promise<void>;
logout: () => void;
}
export const AuthContext = createContext<AuthContextProps>({
user: null,
login: async () => {},
logout: () => {},
});
export const AuthProvider: React.FC = ({ children }) => {
const [user, setUser] = useState(null);
useEffect(() => {
const storedUser = localStorage.getItem('user');
if (storedUser) {
setUser(JSON.parse(storedUser));
}
}, []);
const login = async (username: string, password: string) => {
const response = await axios.post('/auth', { username, password });
setUser(response.data.user);
localStorage.setItem('user', JSON.stringify(response.data.user));
};
const logout = () => {
setUser(null);
localStorage.removeItem('user');
};
return (
<AuthContext.Provider value={{ user, login, logout }}>
{children}
</AuthContext.Provider>
);
};
7. services/carService.ts
This service interacts with the backend API to fetch car data, manage bookings, etc.
import axios from '../utils/axios';
// Fetch all cars
export const fetchCars = async () => {
const response = await axios.get('/cars');
return response.data;
};
// Fetch a single car by ID
export const fetchCarById = async (id: string | number) => {
const response = await axios.get(`/cars/${id}`);
return response.data;
};
// Book a car
export const bookCar = async (carId: string | number, userId: string | number, rentalDate: string, returnDate: string) => {
const response = await axios.post(`/bookings`, { carId, userId, rentalDate, returnDate });
return response.data;
};
8. layouts/MainLayout.tsx
This layout wraps all your pages to provide a consistent structure across your app. It includes the Navbar
, Footer
, and slots for the main content.
import React from 'react';
import Navbar from '../components/Navbar';
import Footer from '../components/Footer';
const MainLayout: React.FC<{ children: React.ReactNode }> = ({ children }) => {
return (
<div className="min-h-screen flex flex-col">
<Navbar />
<main className="flex-grow">{children}</main>
<Footer />
</div>
);
};
export default MainLayout;
Putting It All Together
Adding the AuthContext to _app.tsx
In pages/_app.tsx
, make sure to wrap your application in the AuthProvider
context to make authentication available throughout the app.
import { AuthProvider } from '../src/contexts/AuthContext';
import '../src/styles/globals.css';
function MyApp({ Component, pageProps }) {
return (
<AuthProvider>
<Component {...pageProps} />
</AuthProvider>
);
}
export default MyApp;
Example Next.js
Environment Configuration
Ensure you have an .env.local
file (or .env
) at the root of your project with your environment variables:
NEXT_PUBLIC_API_BASE_URL=http://localhost:3000/api
Conclusion
This structure and code provide you with a solid foundation for building a scalable and maintainable Car Rental System using Next.js. The combination of Tailwind CSS, custom hooks, contexts, Axios for API requests, and services allows you to separate concerns and maintain clean, reusable code. Let me know if you need further customizations or assistance!
Here’s the full code for the authentication and cars modules in your backend NestJS project, with controllers, services, DTOs, and entity files. This structure is based on the NestJS framework, TypeScript, and TypeORM for database management (PostgreSQL is assumed here).
Project Structure
car-rental-backend/
├── src/
│ ├── auth/
│ │ ├── dto/
│ │ │ ├── login.dto.ts
│ │ │ ├── register.dto.ts
│ │ ├── auth.controller.ts
│ │ ├── auth.module.ts
│ │ ├── auth.service.ts
│ │ ├── jwt.strategy.ts
│ │ └── local.strategy.ts
│ ├── cars/
│ │ ├── dto/
│ │ │ ├── create-car.dto.ts
│ │ │ ├── update-car.dto.ts
│ │ ├── entities/
│ │ │ └── car.entity.ts
│ │ ├── cars.controller.ts
│ │ ├── cars.module.ts
│ │ ├── cars.service.ts
1. auth/dto/login.dto.ts
This DTO validates login request data (username and password).
import { IsString, IsNotEmpty } from 'class-validator';
export class LoginDto {
@IsString()
@IsNotEmpty()
username: string;
@IsString()
@IsNotEmpty()
password: string;
}
2. auth/dto/register.dto.ts
This DTO handles registration by validating the incoming request data for new users.
import { IsString, IsNotEmpty, IsEmail } from 'class-validator';
export class RegisterDto {
@IsString()
@IsNotEmpty()
username: string;
@IsEmail()
@IsNotEmpty()
email: string;
@IsString()
@IsNotEmpty()
password: string;
}
3. auth/auth.controller.ts
This controller handles routes related to authentication, such as login and registration.
import { Controller, Post, Body } from '@nestjs/common';
import { AuthService } from './auth.service';
import { LoginDto } from './dto/login.dto';
import { RegisterDto } from './dto/register.dto';
@Controller('auth')
export class AuthController {
constructor(private readonly authService: AuthService) {}
@Post('login')
async login(@Body() loginDto: LoginDto) {
return this.authService.login(loginDto);
}
@Post('register')
async register(@Body() registerDto: RegisterDto) {
return this.authService.register(registerDto);
}
}
4. auth/auth.module.ts
The authentication module brings together the controller, service, and strategies for authentication functionality.
import { Module } from '@nestjs/common';
import { JwtModule } from '@nestjs/jwt';
import { PassportModule } from '@nestjs/passport';
import { AuthController } from './auth.controller';
import { AuthService } from './auth.service';
import { JwtStrategy } from './jwt.strategy';
import { LocalStrategy } from './local.strategy';
@Module({
imports: [
PassportModule,
JwtModule.register({
secret: process.env.JWT_SECRET || 'secretKey',
signOptions: { expiresIn: '60m' },
}),
],
controllers: [AuthController],
providers: [AuthService, JwtStrategy, LocalStrategy],
})
export class AuthModule {}
5. auth/auth.service.ts
This service contains the logic for user authentication, including login and registration functionality.
import { Injectable } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import { LoginDto } from './dto/login.dto';
import { RegisterDto } from './dto/register.dto';
@Injectable()
export class AuthService {
constructor(private readonly jwtService: JwtService) {}
// Mock user storage (replace with real database later)
private users = [];
async login(loginDto: LoginDto) {
const user = this.users.find(
(user) => user.username === loginDto.username && user.password === loginDto.password,
);
if (!user) {
throw new Error('Invalid credentials');
}
const payload = { username: user.username, sub: user.id };
return {
access_token: this.jwtService.sign(payload),
};
}
async register(registerDto: RegisterDto) {
const user = {
id: this.users.length + 1,
...registerDto,
};
this.users.push(user);
return user;
}
}
6. auth/jwt.strategy.ts
JWT strategy to verify the JWT tokens sent with API requests.
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 || 'secretKey',
});
}
async validate(payload: any) {
return { userId: payload.sub, username: payload.username };
}
}
7. auth/local.strategy.ts
Local authentication strategy using username and password.
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 readonly authService: AuthService) {
super();
}
async validate(username: string, password: string): Promise<any> {
const user = await this.authService.login({ username, password });
if (!user) {
throw new UnauthorizedException();
}
return user;
}
}
8. cars/dto/create-car.dto.ts
DTO for creating a new car record.
import { IsString, IsNotEmpty, IsNumber } from 'class-validator';
export class CreateCarDto {
@IsString()
@IsNotEmpty()
name: string;
@IsString()
@IsNotEmpty()
brand: string;
@IsNumber()
pricePerDay: number;
}
9. cars/dto/update-car.dto.ts
DTO for updating an existing car record.
import { IsOptional, IsString, IsNumber } from 'class-validator';
export class UpdateCarDto {
@IsOptional()
@IsString()
name?: string;
@IsOptional()
@IsString()
brand?: string;
@IsOptional()
@IsNumber()
pricePerDay?: number;
}
10. cars/entities/car.entity.ts
The TypeORM entity representing a car in the database.
import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm';
@Entity()
export class Car {
@PrimaryGeneratedColumn()
id: number;
@Column()
name: string;
@Column()
brand: string;
@Column('decimal')
pricePerDay: number;
@Column({ default: true })
availability: boolean;
}
11. cars/cars.controller.ts
This controller handles routes related to cars, such as listing all cars, getting a car by ID, creating, updating, and deleting a car.
import { Controller, Get, Post, Put, Delete, Param, Body } from '@nestjs/common';
import { CarsService } from './cars.service';
import { CreateCarDto } from './dto/create-car.dto';
import { UpdateCarDto } from './dto/update-car.dto';
@Controller('cars')
export class CarsController {
constructor(private readonly carsService: CarsService) {}
@Get()
findAll() {
return this.carsService.findAll();
}
@Get(':id')
findOne(@Param('id') id: number) {
return this.carsService.findOne(id);
}
@Post()
create(@Body() createCarDto: CreateCarDto) {
return this.carsService.create(createCarDto);
}
@Put(':id')
update(@Param('id') id: number, @Body() updateCarDto: UpdateCarDto) {
return this.carsService.update(id, updateCarDto);
}
@Delete(':id')
remove(@Param('id') id: number) {
return this.carsService.remove(id);
}
}
12. cars/cars.module.ts
The cars module that brings together the cars controller, service, and entity.
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { CarsController } from './cars.controller';
import { CarsService } from './cars.service';
import { Car } from './entities/car.entity';
@Module({
imports: [TypeOrmModule.forFeature([Car])],
controllers: [CarsController],
providers: [CarsService],
})
export class CarsModule {}
13. cars/cars.service.ts
This service contains the business logic for managing cars, such
as CRUD operations.
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { Car } from './entities/car.entity';
import { CreateCarDto } from './dto/create-car.dto';
import { UpdateCarDto } from './dto/update-car.dto';
@Injectable()
export class CarsService {
constructor(
@InjectRepository(Car)
private carsRepository: Repository<Car>,
) {}
findAll(): Promise<Car[]> {
return this.carsRepository.find();
}
findOne(id: number): Promise<Car> {
return this.carsRepository.findOneBy({ id });
}
create(createCarDto: CreateCarDto): Promise<Car> {
const car = this.carsRepository.create(createCarDto);
return this.carsRepository.save(car);
}
async update(id: number, updateCarDto: UpdateCarDto): Promise<Car> {
await this.carsRepository.update(id, updateCarDto);
return this.carsRepository.findOneBy({ id });
}
async remove(id: number): Promise<void> {
await this.carsRepository.delete(id);
}
}
Conclusion
This code structure provides the authentication and cars modules for your NestJS backend. It handles user authentication with JWT, basic CRUD operations for car management, and uses TypeORM for database interaction with PostgreSQL (or another relational database).
Let me know if you need any further customizations or explanations for specific parts!
Here’s the full code for the bookings and users modules, including DTOs, entities, controllers, and services for your NestJS backend. These modules cover the handling of user creation, booking creation, and updates.
Updated Project Structure
car-rental-backend/
├── src/
│ ├── bookings/
│ │ ├── dto/
│ │ │ ├── create-booking.dto.ts
│ │ │ ├── update-booking.dto.ts
│ │ ├── entities/
│ │ │ └── booking.entity.ts
│ │ ├── bookings.controller.ts
│ │ ├── bookings.module.ts
│ │ ├── bookings.service.ts
│ ├── users/
│ │ ├── dto/
│ │ │ └── create-user.dto.ts
│ │ ├── entities/
│ │ │ └── user.entity.ts
│ │ ├── users.controller.ts
│ │ ├── users.module.ts
│ │ ├── users.service.ts
Bookings Module
1. bookings/dto/create-booking.dto.ts
DTO for creating a new booking.
import { IsDateString, IsNumber, IsNotEmpty } from 'class-validator';
export class CreateBookingDto {
@IsNumber()
@IsNotEmpty()
carId: number;
@IsNumber()
@IsNotEmpty()
userId: number;
@IsDateString()
@IsNotEmpty()
rentalDate: string;
@IsDateString()
@IsNotEmpty()
returnDate: string;
}
2. bookings/dto/update-booking.dto.ts
DTO for updating an existing booking.
import { IsOptional, IsDateString, IsNumber } from 'class-validator';
export class UpdateBookingDto {
@IsOptional()
@IsNumber()
carId?: number;
@IsOptional()
@IsNumber()
userId?: number;
@IsOptional()
@IsDateString()
rentalDate?: string;
@IsOptional()
@IsDateString()
returnDate?: string;
}
3. bookings/entities/booking.entity.ts
The TypeORM entity representing a booking.
import { Entity, PrimaryGeneratedColumn, ManyToOne, Column } from 'typeorm';
import { Car } from '../../cars/entities/car.entity';
import { User } from '../../users/entities/user.entity';
@Entity()
export class Booking {
@PrimaryGeneratedColumn()
id: number;
@ManyToOne(() => Car)
car: Car;
@ManyToOne(() => User)
user: User;
@Column('date')
rentalDate: string;
@Column('date')
returnDate: string;
@Column('decimal')
totalAmount: number;
}
4. bookings/bookings.controller.ts
This controller handles routes related to bookings, such as creating, updating, and retrieving bookings.
import { Controller, Get, Post, Put, Delete, Param, Body } from '@nestjs/common';
import { BookingsService } from './bookings.service';
import { CreateBookingDto } from './dto/create-booking.dto';
import { UpdateBookingDto } from './dto/update-booking.dto';
@Controller('bookings')
export class BookingsController {
constructor(private readonly bookingsService: BookingsService) {}
@Get()
findAll() {
return this.bookingsService.findAll();
}
@Get(':id')
findOne(@Param('id') id: number) {
return this.bookingsService.findOne(id);
}
@Post()
create(@Body() createBookingDto: CreateBookingDto) {
return this.bookingsService.create(createBookingDto);
}
@Put(':id')
update(@Param('id') id: number, @Body() updateBookingDto: UpdateBookingDto) {
return this.bookingsService.update(id, updateBookingDto);
}
@Delete(':id')
remove(@Param('id') id: number) {
return this.bookingsService.remove(id);
}
}
5. bookings/bookings.module.ts
The bookings module combines the bookings controller, service, and entity.
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { BookingsService } from './bookings.service';
import { BookingsController } from './bookings.controller';
import { Booking } from './entities/booking.entity';
@Module({
imports: [TypeOrmModule.forFeature([Booking])],
controllers: [BookingsController],
providers: [BookingsService],
})
export class BookingsModule {}
6. bookings/bookings.service.ts
This service contains the business logic for managing bookings.
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { Booking } from './entities/booking.entity';
import { CreateBookingDto } from './dto/create-booking.dto';
import { UpdateBookingDto } from './dto/update-booking.dto';
@Injectable()
export class BookingsService {
constructor(
@InjectRepository(Booking)
private bookingsRepository: Repository<Booking>,
) {}
findAll(): Promise<Booking[]> {
return this.bookingsRepository.find({ relations: ['car', 'user'] });
}
findOne(id: number): Promise<Booking> {
return this.bookingsRepository.findOneBy({ id });
}
create(createBookingDto: CreateBookingDto): Promise<Booking> {
const booking = this.bookingsRepository.create(createBookingDto);
return this.bookingsRepository.save(booking);
}
async update(id: number, updateBookingDto: UpdateBookingDto): Promise<Booking> {
await this.bookingsRepository.update(id, updateBookingDto);
return this.bookingsRepository.findOneBy({ id });
}
async remove(id: number): Promise<void> {
await this.bookingsRepository.delete(id);
}
}
Users Module
1. users/dto/create-user.dto.ts
DTO for creating a new user.
import { IsEmail, IsString, IsNotEmpty } from 'class-validator';
export class CreateUserDto {
@IsString()
@IsNotEmpty()
username: string;
@IsEmail()
@IsNotEmpty()
email: string;
@IsString()
@IsNotEmpty()
password: string;
}
2. users/entities/user.entity.ts
The TypeORM entity representing a user.
import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm';
@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number;
@Column()
username: string;
@Column()
email: string;
@Column()
password: string;
}
3. users/users.controller.ts
This controller handles routes related to users, such as creating users and fetching user data.
import { Controller, Get, Post, Param, Body } from '@nestjs/common';
import { UsersService } from './users.service';
import { CreateUserDto } from './dto/create-user.dto';
@Controller('users')
export class UsersController {
constructor(private readonly usersService: UsersService) {}
@Get()
findAll() {
return this.usersService.findAll();
}
@Get(':id')
findOne(@Param('id') id: number) {
return this.usersService.findOne(id);
}
@Post()
create(@Body() createUserDto: CreateUserDto) {
return this.usersService.create(createUserDto);
}
}
4. users/users.module.ts
The users module integrates the users controller, service, and entity.
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],
})
export class UsersModule {}
5. users/users.service.ts
This service handles the business logic for user management, such as user creation and retrieval.
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { User } from './entities/user.entity';
import { CreateUserDto } from './dto/create-user.dto';
@Injectable()
export class UsersService {
constructor(
@InjectRepository(User)
private usersRepository: Repository<User>,
) {}
findAll(): Promise<User[]> {
return this.usersRepository.find();
}
findOne(id: number): Promise<User> {
return this.usersRepository.findOneBy({ id });
}
create(createUserDto: CreateUserDto): Promise<User> {
const user = this.usersRepository.create(createUserDto);
return this.usersRepository.save(user);
}
}
Conclusion
This setup includes the Bookings and Users modules with the full code for creating, updating, and retrieving bookings and users using **NestJS
** with TypeORM for database interaction. The code supports CRUD operations for both modules, making it easy to expand with further functionality.
Let me know if you need more information or help with other modules!
Here's the full code for the payments module and the common utilities like decorators, guards, interceptors, and filters. This structure is for a NestJS backend application.
Updated Project Structure
car-rental-backend/
├── src/
│ ├── payments/
│ │ ├── dto/
│ │ │ └── create-payment.dto.ts
│ │ ├── payments.controller.ts
│ │ ├── payments.module.ts
│ │ ├── payments.service.ts
│ ├── common/
│ │ ├── decorators/
│ │ │ └── roles.decorator.ts
│ │ ├── guards/
│ │ │ └── jwt-auth.guard.ts
│ │ ├── interceptors/
│ │ │ └── logging.interceptor.ts
│ │ ├── filters/
│ │ │ └── http-exception.filter.ts
│ │ └── constants.ts
Payments Module
The Payments module handles payment processing in your system, such as creating a payment for a car rental. This might integrate with payment gateways like Stripe or PayPal.
1. payments/dto/create-payment.dto.ts
This DTO defines the data structure for creating a new payment.
import { IsNumber, IsNotEmpty, IsString } from 'class-validator';
export class CreatePaymentDto {
@IsNumber()
@IsNotEmpty()
bookingId: number;
@IsNumber()
@IsNotEmpty()
amount: number;
@IsString()
@IsNotEmpty()
paymentMethod: string; // e.g., 'credit_card', 'paypal'
}
2. payments/payments.controller.ts
This controller manages the HTTP routes related to payments.
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()
create(@Body() createPaymentDto: CreatePaymentDto) {
return this.paymentsService.processPayment(createPaymentDto);
}
}
3. payments/payments.module.ts
The Payments module groups together the payments controller and service.
import { Module } from '@nestjs/common';
import { PaymentsService } from './payments.service';
import { PaymentsController } from './payments.controller';
@Module({
controllers: [PaymentsController],
providers: [PaymentsService],
})
export class PaymentsModule {}
4. payments/payments.service.ts
The PaymentsService handles the business logic for payment processing, potentially integrating with third-party services like Stripe.
import { Injectable } from '@nestjs/common';
import { CreatePaymentDto } from './dto/create-payment.dto';
@Injectable()
export class PaymentsService {
processPayment(createPaymentDto: CreatePaymentDto) {
// This is where you'd integrate with a payment gateway (e.g., Stripe, PayPal)
// For demonstration, we'll just return a successful payment object.
return {
id: Math.floor(Math.random() * 1000),
bookingId: createPaymentDto.bookingId,
amount: createPaymentDto.amount,
paymentMethod: createPaymentDto.paymentMethod,
status: 'success',
transactionId: `txn_${Math.random().toString(36).substr(2, 9)}`,
};
}
}
Common Module
This module contains shared utilities such as decorators, guards, interceptors, and filters that can be reused throughout the application.
1. common/decorators/roles.decorator.ts
This decorator is used to restrict routes to specific roles.
import { SetMetadata } from '@nestjs/common';
export const ROLES_KEY = 'roles';
export const Roles = (...roles: string[]) => SetMetadata(ROLES_KEY, roles);
2. common/guards/jwt-auth.guard.ts
This guard protects routes by verifying the validity of a JWT token.
import { Injectable } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
@Injectable()
export class JwtAuthGuard extends AuthGuard('jwt') {}
To use this guard, ensure that your JWT strategy is implemented (as shown earlier).
3. common/interceptors/logging.interceptor.ts
This interceptor logs requests and responses, which is useful for debugging and monitoring.
import {
Injectable,
NestInterceptor,
ExecutionContext,
CallHandler,
} from '@nestjs/common';
import { Observable } from 'rxjs';
import { tap } from 'rxjs/operators';
@Injectable()
export class LoggingInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
console.log('Before handling request:', context.switchToHttp().getRequest().url);
const now = Date.now();
return next.handle().pipe(
tap(() =>
console.log(
`After handling request in ${Date.now() - now}ms`,
context.switchToHttp().getResponse().statusCode,
),
),
);
}
}
4. common/filters/http-exception.filter.ts
This filter is used for handling and formatting HTTP exceptions (errors).
import {
ExceptionFilter,
Catch,
ArgumentsHost,
HttpException,
HttpStatus,
} from '@nestjs/common';
import { Request, Response } from 'express';
@Catch()
export class HttpExceptionFilter implements ExceptionFilter {
catch(exception: HttpException, 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 errorResponse = {
statusCode: status,
timestamp: new Date().toISOString(),
path: request.url,
message: exception.message || null,
};
response.status(status).json(errorResponse);
}
}
5. common/constants.ts
This file contains constants that can be reused across the application, such as role definitions, API keys, or status codes.
export const JWT_SECRET = process.env.JWT_SECRET || 'secretKey';
export const USER_ROLES = {
ADMIN: 'admin',
USER: 'user',
};
export const STATUS_CODES = {
SUCCESS: 'success',
FAILURE: 'failure',
};
Putting It All Together
You can apply these common utilities across your application. For instance:
-
Role-based Access Control: You can use the
@Roles()
decorator andJwtAuthGuard
to restrict certain routes. -
Logging: Apply the
LoggingInterceptor
globally or at the controller level to monitor request handling. -
Error Handling: Use the
HttpExceptionFilter
to standardize error responses across your application.
Example: Securing a Controller with Role-Based Access Control
import { Controller, Get, UseGuards } from '@nestjs/common';
import { Roles } from '../common/decorators/roles.decorator';
import { JwtAuthGuard } from '../common/guards/jwt-auth.guard';
@Controller('admin')
@UseGuards(JwtAuthGuard)
export class AdminController {
@Get()
@Roles('admin')
getAdminData() {
return { message: 'This is secured admin data' };
}
}
This ensures that only authenticated users with the role of 'admin'
can access this route.
Conclusion
This provides the full code for the Payments module and shared Common utilities like decorators, guards, interceptors, and filters. These utilities are essential for securing and standardizing your NestJS application, making it more maintainable and scalable. If you need further adjustments or explanation for specific parts, feel free to ask!
Here is the full code for the database module, app entry files, config files, and migrations for your NestJS backend application. This includes the setup for TypeORM, JWT, and global application configurations.
Database Module
The database module is responsible for managing the connection to the database, including the base entity and service files for database interaction.
1. database/entities/base.entity.ts
This is a base entity that can be extended by other entities to include common fields like id
, createdAt
, and updatedAt
.
import {
PrimaryGeneratedColumn,
CreateDateColumn,
UpdateDateColumn,
BaseEntity,
} from 'typeorm';
export abstract class Base extends BaseEntity {
@PrimaryGeneratedColumn()
id: number;
@CreateDateColumn({ type: 'timestamp' })
createdAt: Date;
@UpdateDateColumn({ type: 'timestamp' })
updatedAt: Date;
}
2. database/database.module.ts
The DatabaseModule imports and provides the TypeORM connection to the rest of the application.
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { TypeOrmConfigService } from '../config/typeorm.config';
@Module({
imports: [
TypeOrmModule.forRootAsync({
imports: [ConfigModule],
useClass: TypeOrmConfigService,
}),
],
})
export class DatabaseModule {}
3. database/database.service.ts
The DatabaseService can be used for managing database connections and performing additional database-related operations if needed.
import { Injectable } from '@nestjs/common';
@Injectable()
export class DatabaseService {
// Any additional database utilities or services can be added here.
}
App Entry Files
These are the core entry points of your NestJS application: app.module.ts
and main.ts
.
1. app.module.ts
The AppModule imports all necessary modules and serves as the root module for the application.
import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { DatabaseModule } from './database/database.module';
import { AuthModule } from './auth/auth.module';
import { CarsModule } from './cars/cars.module';
import { BookingsModule } from './bookings/bookings.module';
import { PaymentsModule } from './payments/payments.module';
import { UsersModule } from './users/users.module';
import { APP_FILTER, APP_INTERCEPTOR } from '@nestjs/core';
import { LoggingInterceptor } from './common/interceptors/logging.interceptor';
import { HttpExceptionFilter } from './common/filters/http-exception.filter';
@Module({
imports: [
ConfigModule.forRoot({
isGlobal: true,
envFilePath: '.env',
}),
DatabaseModule,
AuthModule,
CarsModule,
BookingsModule,
PaymentsModule,
UsersModule,
],
providers: [
{
provide: APP_INTERCEPTOR,
useClass: LoggingInterceptor,
},
{
provide: APP_FILTER,
useClass: HttpExceptionFilter,
},
],
})
export class AppModule {}
2. main.ts
The main.ts file bootstraps the NestJS application.
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { ValidationPipe } from '@nestjs/common';
import { HttpExceptionFilter } from './common/filters/http-exception.filter';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalPipes(new ValidationPipe());
app.useGlobalFilters(new HttpExceptionFilter());
app.enableCors();
await app.listen(3000);
}
bootstrap();
Config Module
The config module manages all configuration settings, including JWT and TypeORM configurations.
1. config/jwt.config.ts
This file contains the configuration for JWT authentication.
import { Injectable } from '@nestjs/common';
import { JwtModuleOptions, JwtOptionsFactory } from '@nestjs/jwt';
@Injectable()
export class JwtConfigService implements JwtOptionsFactory {
createJwtOptions(): JwtModuleOptions {
return {
secret: process.env.JWT_SECRET || 'defaultSecret',
signOptions: { expiresIn: '60m' },
};
}
}
2. config/typeorm.config.ts
This file contains the configuration for TypeORM.
import { Injectable } from '@nestjs/common';
import { TypeOrmOptionsFactory, TypeOrmModuleOptions } from '@nestjs/typeorm';
import { ConfigService } from '@nestjs/config';
@Injectable()
export class TypeOrmConfigService implements TypeOrmOptionsFactory {
constructor(private configService: ConfigService) {}
createTypeOrmOptions(): TypeOrmModuleOptions {
return {
type: 'postgres',
host: this.configService.get<string>('DB_HOST'),
port: this.configService.get<number>('DB_PORT'),
username: this.configService.get<string>('DB_USERNAME'),
password: this.configService.get<string>('DB_PASSWORD'),
database: this.configService.get<string>('DB_NAME'),
autoLoadEntities: true,
synchronize: true, // In production, use migrations instead of synchronize: true
};
}
}
3. config/app.config.ts
Global application settings, such as CORS configuration or app-wide constants, can be defined here.
export const APP_CONFIG = {
cors: {
origin: '*',
methods: 'GET,HEAD,PUT,PATCH,POST,DELETE',
preflightContinue: false,
optionsSuccessStatus: 204,
},
};
Migrations
Migrations allow you to manage changes to the database schema over time. TypeORM generates these migrations.
1. migrations/1682570845798-CreateCarEntity.ts
A migration file to create the Car
entity.
import { MigrationInterface, QueryRunner, Table } from 'typeorm';
export class CreateCarEntity1682570845798 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.createTable(
new Table({
name: 'car',
columns: [
{
name: 'id',
type: 'int',
isPrimary: true,
isGenerated: true,
generationStrategy: 'increment',
},
{
name: 'name',
type: 'varchar',
},
{
name: 'brand',
type: 'varchar',
},
{
name: 'pricePerDay',
type: 'decimal',
},
{
name: 'availability',
type: 'boolean',
default: true,
},
{
name: 'createdAt',
type: 'timestamp',
default: 'now()',
},
{
name: 'updatedAt',
type: 'timestamp',
default: 'now()',
},
],
}),
);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.dropTable('car');
}
}
2. migrations/1682570845799-CreateBookingEntity.ts
A migration file to create the Booking
entity.
import { MigrationInterface, QueryRunner, Table } from 'typeorm';
export class CreateBookingEntity1682570845799 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.createTable(
new Table({
name: 'booking',
columns: [
{
name: 'id',
type: 'int',
isPrimary: true,
isGenerated: true,
generationStrategy: 'increment',
},
{
name: 'carId',
type: 'int',
},
{
name: 'userId',
type: 'int',
},
{
name: 'rentalDate',
type: 'date',
},
{
name: 'returnDate',
type: 'date',
},
{
name: 'totalAmount',
type: 'decimal',
},
{
name: 'createdAt',
type: 'timestamp',
default: 'now()',
},
{
name: 'updatedAt',
type: 'timestamp',
default: 'now()',
},
],
}),
);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.dropTable('booking');
}
}
Conclusion
This complete structure includes:
- Database setup with TypeORM and base entity definitions.
- AppModule that imports all other modules and configures the application globally.
- Configuration for JWT, TypeORM, and global application settings.
- Migrations for managing database schema changes.
This structure ensures that your NestJS application is modular, scalable, and easy to manage with clear separation between different concerns. Let me know if you need further clarifications or help with specific parts!
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)