To Develop a SaaS (Software as a Service) application designed to assist recruiters in streamlining their email crafting and recruitment processes. The platform offers advanced features such as personalized email templates, AI-powered candidate outreach, job management, interview scheduling, and recruitment analytics. This software aims to enhance recruiter productivity by automating repetitive tasks and providing insights into candidate engagement, making the recruitment process more efficient.To Develop
Key aspects of the PDF:
- Target Audience: Recruiters and HR professionals.
-
Main Features:
- Email Crafting: AI-powered tools to write personalized and effective emails to candidates.
- Networking and Engagement: Tools to expand professional networks and engage with potential candidates.
- Interview Questions: AI-generated tailored interview questions based on job descriptions.
- Candidate Personas: Automatic generation of candidate personas from job descriptions.
- Recruitment Metrics and Analysis: Insights and recommendations based on recruitment performance data.
- Affordable Pricing: It offers free and paid plans with different levels of access to features.
Purpose: It promotes the features of the TEST platform, emphasizing how it can optimize recruitment efforts by improving communication, engagement, and decision-making for recruiters.
It seems to be primarily a marketing document aimed at convincing recruiters to try the product, outlining both the features and benefits of the software.
To develop a project like TEST using Next.js 14 App Router and Tailwind CSS, here is a breakdown of functionality and the recommended file and folder structure.
Core Functionality Details
-
Authentication and Authorization:
- OAuth/Social Logins (Google, LinkedIn, etc.)
- Email/Password Authentication
- Role-based access control (Admin, Recruiter, etc.)
-
Dashboard:
- Display key recruitment metrics (Jobs created, Candidates contacted, Open Positions)
- Recruiter tools such as job creation, candidate management, email crafting, etc.
-
Job Management:
- Create New Job: Form for recruiters to input job details (title, description, requirements, etc.)
- Job Listing: Display jobs with filters (status: open/closed, urgency, etc.)
- Job Editing: Ability to modify job details.
-
Candidate Outreach:
- Email Crafting: AI-generated email templates for initial reach-outs, interview invites, etc.
- Email Sending: Integration with an email service (SendGrid, AWS SES) for delivering emails.
-
Interview Question Generator:
- Generate relevant interview questions based on job descriptions.
-
Candidate Persona Creation:
- Auto-generate candidate personas by analyzing job descriptions.
-
Metrics and Analytics:
- Display of recruitment performance metrics: open jobs, candidate responses, email open rates, etc.
-
Pricing Plans:
- Subscription with features gated by free, day pass, and premium plans.
- Payment gateway integration (Stripe, PayPal, etc.)
File and Folder Structure
Here is the ideal structure using Next.js 14 App Router and Tailwind CSS for scalability and modularity:
/project-root
│
├── /app
│ ├── /api
│ │ ├── /auth
│ │ │ └── route.ts # API routes for authentication (login, signup)
│ │ ├── /jobs
│ │ │ ├── route.ts # API route to create and list jobs
│ │ │ └── [id].ts # API route to get/edit/delete specific job
│ │ ├── /emails
│ │ │ ├── route.ts # API to send emails (SendGrid, SES integration)
│ │ └── /metrics
│ │ └── route.ts # API route for recruitment analytics
│ │
│ ├── /dashboard
│ │ ├── /page.tsx # Main dashboard page
│ │ └── /metrics
│ │ └── page.tsx # Page to display recruitment analytics
│ │
│ ├── /jobs
│ │ ├── /create
│ │ │ └── page.tsx # Page to create a new job
│ │ ├── /[id]
│ │ │ ├── /edit
│ │ │ │ └── page.tsx # Edit a job
│ │ │ └── page.tsx # View a specific job
│ │ └── page.tsx # List of jobs
│ │
│ ├── /auth
│ │ ├── /login
│ │ │ └── page.tsx # Login page
│ │ ├── /register
│ │ │ └── page.tsx # Register page
│ │ └── /forgot-password
│ │ └── page.tsx # Forgot password page
│ │
│ ├── /settings
│ │ ├── /account
│ │ │ └── page.tsx # Account settings page
│ │ ├── /subscription
│ │ │ └── page.tsx # Subscription and pricing page
│ │ └── /profile
│ │ └── page.tsx # User profile page
│ │
│ └── /emails
│ └── page.tsx # Email crafting page
│
├── /components
│ ├── JobCard.tsx # Component to display job summary
│ ├── CandidateCard.tsx # Component to display candidate summary
│ ├── MetricsCard.tsx # Component to show analytics
│ └── EmailForm.tsx # Component to craft and send email
│
├── /lib
│ ├── db.ts # Prisma/PostgreSQL configuration
│ ├── auth.ts # NextAuth.js configuration (OAuth, JWT, etc.)
│ └── email.ts # Email utility functions
│
├── /styles
│ ├── globals.css # Global CSS and Tailwind imports
│ └── tailwind.config.js # Tailwind CSS configuration
│
├── /public
│ └── /images # Static assets (logo, icons)
│
├── prisma
│ └── schema.prisma # Prisma schema for DB structure
│
├── .env # Environment variables (DB connection, API keys)
├── package.json # Project dependencies
└── tsconfig.json # TypeScript configuration
Functional Breakdown
Authentication
- NextAuth.js for OAuth/Social logins (Google, LinkedIn).
- JWT-based authentication for secured API routes.
Dashboard
- React components with Tailwind CSS for metrics and job listings.
-
SSR/SSG using
getServerSideProps
to fetch job and user data.
Job Management
- CRUD operations using Prisma for PostgreSQL.
- Job creation form with input fields for title, description, and skills.
Email Crafting
- React form for writing emails and a button to send via a connected email service (e.g., SendGrid).
Interview Question Generator
- Use OpenAI API to generate relevant interview questions from job descriptions.
Candidate Persona Creation
- Implement NLP analysis on job descriptions to create candidate personas.
Subscription Plans
- Stripe integration for managing payments and subscription models (free, day pass, monthly).
Tech Stack
- Next.js 14: For server-side rendering (SSR) and API routes.
- Tailwind CSS: For responsive and custom styling.
- Prisma ORM: For database management (PostgreSQL).
- NextAuth.js: For authentication.
- SendGrid/AWS SES: For email sending.
- Stripe: For payment processing.
- OpenAI API: For AI-generated email templates and interview questions.
With this structure and functionality, you can develop a scalable recruiter assistance platform similar to TEST, leveraging modern web technologies.
To create the backend of the TEST-like project using NestJS, Prisma, and PostgreSQL, here is the recommended folder structure along with key details for the project:
Folder Structure for NestJS Backend with Prisma and PostgreSQL
/project-backend
├── /src
│ ├── /auth # Authentication module (OAuth, JWT)
│ │ ├── /dto
│ │ │ └── login.dto.ts # DTO for login payload
│ │ ├── auth.controller.ts # Authentication controller
│ │ ├── auth.service.ts # Authentication service (JWT, OAuth)
│ │ └── auth.module.ts # Auth module declaration
│ │
│ ├── /users # Users module
│ │ ├── /dto
│ │ │ └── create-user.dto.ts # DTO for creating a new user
│ │ ├── users.controller.ts # Controller for user-related routes
│ │ ├── users.service.ts # Service for user business logic
│ │ ├── users.entity.ts # User entity definition for Prisma
│ │ └── users.module.ts # User module declaration
│ │
│ ├── /jobs # Jobs module
│ │ ├── /dto
│ │ │ ├── create-job.dto.ts # DTO for creating a job
│ │ │ └── update-job.dto.ts # DTO for updating a job
│ │ ├── jobs.controller.ts # Controller for job routes (CRUD)
│ │ ├── jobs.service.ts # Business logic for managing jobs
│ │ ├── jobs.entity.ts # Prisma schema for Job entity
│ │ └── jobs.module.ts # Job module declaration
│ │
│ ├── /emails # Email module (for crafting and sending emails)
│ │ ├── /dto
│ │ │ └── send-email.dto.ts # DTO for sending email
│ │ ├── emails.controller.ts # Controller for email sending APIs
│ │ ├── emails.service.ts # Service for email logic (using SendGrid or AWS SES)
│ │ └── emails.module.ts # Email module declaration
│ │
│ ├── /metrics # Metrics and analytics module
│ │ ├── metrics.controller.ts # Controller for retrieving recruitment metrics
│ │ ├── metrics.service.ts # Service to handle metric calculations
│ │ └── metrics.module.ts # Metrics module declaration
│ │
│ ├── /interviews # Interviews module
│ │ ├── /dto
│ │ │ └── schedule-interview.dto.ts # DTO for scheduling interviews
│ │ ├── interviews.controller.ts # Controller for interview-related routes
│ │ ├── interviews.service.ts # Business logic for interview scheduling
│ │ └── interviews.module.ts # Interview module declaration
│ │
│ ├── /payments # Payments module for Stripe integration
│ │ ├── /dto
│ │ │ └── payment.dto.ts # DTO for handling payment payloads
│ │ ├── payments.controller.ts # Controller for handling subscription payments
│ │ ├── payments.service.ts # Service to integrate Stripe for subscription management
│ │ └── payments.module.ts # Payments module declaration
│ │
│ ├── /prisma # Prisma module for database management
│ │ ├── prisma.module.ts # Prisma module for database connection
│ │ ├── prisma.service.ts # Prisma service (initialize and handle DB operations)
│ │ └── schema.prisma # Prisma schema file for defining DB structure
│ │
│ ├── /common # Common module (utilities, guards, interceptors)
│ │ ├── /guards # Authorization guards (e.g., RoleGuard)
│ │ ├── /interceptors # Custom interceptors for logging or transformation
│ │ └── /utils # Shared utility functions
│ │
│ ├── app.module.ts # Root module that imports and registers all other modules
│ ├── main.ts # Application entry point
│ └── env.validation.ts # Environment variable validation schema
│
├── prisma
│ └── schema.prisma # Prisma schema file for database models
│
├── .env # Environment variables (DB connection, API keys, etc.)
├── .env.example # Example environment file for developers
├── .gitignore # Git ignore rules
├── package.json # Node.js dependencies and scripts
└── tsconfig.json # TypeScript configuration
Breakdown of the Folder Structure
-
Auth Module (
auth/
):- Manages user authentication and authorization using JWT or OAuth (e.g., Google, LinkedIn).
- Controllers for login, signup, and token refresh.
- Integration with Next.js frontend for user authentication flow.
-
Users Module (
users/
):- Manages user-related actions like creating, updating, or retrieving user data.
- Works with Prisma to query and update the users table in the database.
- Roles such as "Admin" or "Recruiter" are defined here.
-
Jobs Module (
jobs/
):- Implements job-related functionality like job creation, job listing, and editing.
- Integrates with Prisma to store jobs in PostgreSQL.
- REST API to fetch jobs, filter them, and perform CRUD operations.
-
Emails Module (
emails/
):- Handles email-related functionality such as sending personalized recruitment emails.
- Integrates with email services like SendGrid or AWS SES to send emails.
- AI-powered email generation (potentially integrated with OpenAI).
-
Metrics Module (
metrics/
):- Collects and analyzes data about job performance, email engagement, and candidate responses.
- Provides APIs to return metrics such as open job counts, email open rates, etc.
-
Interviews Module (
interviews/
):- Handles the scheduling of interviews and management of interview sessions.
- Provides APIs to schedule, update, or cancel interviews.
- Integration with Google Calendar or Outlook for scheduling.
-
Payments Module (
payments/
):- Manages subscription payments using Stripe.
- Handles different pricing plans and enforces feature limits based on the plan.
- Provides subscription management APIs to manage upgrades, downgrades, and renewals.
-
Prisma Module (
prisma/
):- The Prisma ORM is configured here.
- schema.prisma defines all the database models (e.g., users, jobs, subscriptions).
- The PrismaService is injected into other services to handle database interactions.
-
Common Module (
common/
):- Houses shared utilities, guards, interceptors, and other components used across the project.
- For example, a
RoleGuard
can ensure certain routes are accessible only by specific roles (Admin/Recruiter).
Example Prisma Schema for Key Entities
Here’s an example Prisma schema (schema.prisma
) to illustrate how models like User
, Job
, and Subscription
might look:
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
generator client {
provider = "prisma-client-js"
}
model User {
id Int @id @default(autoincrement())
email String @unique
password String
role String // Admin, Recruiter, etc.
jobs Job[] // A user can post multiple jobs
createdAt DateTime @default(now())
}
model Job {
id Int @id @default(autoincrement())
title String
description String
status String // Open, Closed, etc.
createdBy Int
recruiter User @relation(fields: [createdBy], references: [id])
createdAt DateTime @default(now())
}
model Subscription {
id Int @id @default(autoincrement())
userId Int @unique
plan String // Free, Day Pass, Monthly, etc.
createdAt DateTime @default(now())
user User @relation(fields: [userId], references: [id])
}
model EmailHistory {
id Int @id @default(autoincrement())
userId Int
jobId Int
status String // Sent, Opened, Clicked, etc.
createdAt DateTime @default(now())
user User @relation(fields: [userId], references: [id])
job Job @relation(fields: [jobId], references: [id])
}
Sure! Here's an example of how to create a dashboard with a metrics page in Next.js 14 using Tailwind CSS for styling. I'll provide basic components, routing, and styling for a recruiter’s dashboard and the metrics page.
Folder Structure for Dashboard
/project-root
├── /app
│ ├── /dashboard
│ │ ├── /page.tsx # Main dashboard page
│ │ └── /metrics
│ │ └── page.tsx # Page to display recruitment analytics
│ └── /components # Reusable components
│ ├── Navbar.tsx # Navbar component for dashboard navigation
│ └── MetricCard.tsx # Metric card component for displaying metrics
├── /styles
│ └── globals.css # Tailwind CSS imports and global styles
├── /public
│ └── /icons # Any icons or images used in the UI
└── tailwind.config.js # Tailwind CSS configuration
Main Dashboard Page (dashboard/page.tsx
)
This page will serve as the recruiter’s dashboard, displaying key metrics and options for navigating the app.
// src/app/dashboard/page.tsx
import Navbar from '@/components/Navbar';
import Link from 'next/link';
export default function DashboardPage() {
return (
<div className="min-h-screen bg-gray-100">
<Navbar />
<div className="container mx-auto p-6">
<h1 className="text-3xl font-semibold text-gray-800 mb-4">Recruiter Dashboard</h1>
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
{/* Card for Metrics Page */}
<Link href="/dashboard/metrics">
<div className="bg-white shadow-lg p-6 rounded-lg hover:shadow-2xl transition-shadow duration-200 cursor-pointer">
<h2 className="text-2xl font-medium text-gray-700">Metrics</h2>
<p className="mt-2 text-gray-600">
View detailed recruitment analytics and insights.
</p>
</div>
</Link>
{/* Other dashboard items can go here */}
<div className="bg-white shadow-lg p-6 rounded-lg">
<h2 className="text-2xl font-medium text-gray-700">Job Management</h2>
<p className="mt-2 text-gray-600">Create, edit, and manage job listings.</p>
</div>
<div className="bg-white shadow-lg p-6 rounded-lg">
<h2 className="text-2xl font-medium text-gray-700">Candidate Outreach</h2>
<p className="mt-2 text-gray-600">Send AI-generated emails to candidates.</p>
</div>
</div>
</div>
</div>
);
}
Metrics Page (dashboard/metrics/page.tsx
)
The metrics page shows key analytics like job performance, email outreach, and interview scheduling.
// src/app/dashboard/metrics/page.tsx
import Navbar from '@/components/Navbar';
import MetricCard from '@/components/MetricCard';
export default function MetricsPage() {
return (
<div className="min-h-screen bg-gray-100">
<Navbar />
<div className="container mx-auto p-6">
<h1 className="text-3xl font-semibold text-gray-800 mb-4">Recruitment Metrics</h1>
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
<MetricCard title="Open Jobs" value="25" description="Jobs currently open" />
<MetricCard title="Closed Jobs" value="12" description="Jobs closed successfully" />
<MetricCard title="Emails Sent" value="45" description="Total outreach emails sent" />
<MetricCard title="Interviews Scheduled" value="8" description="Upcoming interviews" />
<MetricCard title="Response Rate" value="70%" description="Email response rate" />
</div>
</div>
</div>
);
}
Reusable Components
Navbar Component (components/Navbar.tsx
)
// src/components/Navbar.tsx
import Link from 'next/link';
export default function Navbar() {
return (
<nav className="bg-blue-600 p-4 shadow-lg">
<div className="container mx-auto flex justify-between items-center">
<Link href="/dashboard" className="text-white text-xl font-bold">
Recruiter Dashboard
</Link>
<div className="space-x-4">
<Link href="/dashboard" className="text-white hover:text-blue-300">
Dashboard
</Link>
<Link href="/dashboard/metrics" className="text-white hover:text-blue-300">
Metrics
</Link>
<Link href="/settings" className="text-white hover:text-blue-300">
Settings
</Link>
</div>
</div>
</nav>
);
}
Metric Card Component (components/MetricCard.tsx
)
// src/components/MetricCard.tsx
interface MetricCardProps {
title: string;
value: string;
description: string;
}
export default function MetricCard({ title, value, description }: MetricCardProps) {
return (
<div className="bg-white shadow-lg p-6 rounded-lg">
<h2 className="text-xl font-medium text-gray-700">{title}</h2>
<p className="text-3xl font-bold text-gray-900">{value}</p>
<p className="mt-2 text-gray-600">{description}</p>
</div>
);
}
Tailwind CSS Configuration (tailwind.config.js
)
Make sure Tailwind is configured correctly by adding it to your project:
// tailwind.config.js
module.exports = {
content: [
'./app/**/*.{js,ts,jsx,tsx}',
'./components/**/*.{js,ts,jsx,tsx}',
],
theme: {
extend: {},
},
plugins: [],
};
Global Styles (styles/globals.css
)
Ensure Tailwind CSS is imported into your global styles:
/* styles/globals.css */
@tailwind base;
@tailwind components;
@tailwind utilities;
Resulting Pages and Styling
- The dashboard page contains cards linking to different sections such as job management, candidate outreach, and metrics.
- The metrics page displays various key metrics about recruitment activities like open jobs, emails sent, and interview scheduling.
- Both pages use Tailwind CSS for styling, providing a clean, responsive layout.
- The Navbar component provides easy navigation across the dashboard.
With this setup, you have a basic recruiter dashboard and metrics page using Next.js and Tailwind CSS that can be further extended with additional features and data integrations.
To implement the Jobs feature with pages to create, edit, view, and list jobs, here's the full code with proper structure and styling using Next.js 14 and Tailwind CSS.
Folder Structure for Jobs Module
/project-root
├── /app
│ ├── /jobs
│ │ ├── /create
│ │ │ └── page.tsx # Page to create a new job
│ │ ├── /[id]
│ │ │ ├── /edit
│ │ │ │ └── page.tsx # Page to edit a job
│ │ │ └── page.tsx # Page to view a specific job
│ │ └── page.tsx # Page to list all jobs
├── /components
│ ├── JobCard.tsx # Component to display job summary
│ └── JobForm.tsx # Form for creating or editing a job
├── /styles
│ └── globals.css # Tailwind CSS imports and global styles
└── tailwind.config.js # Tailwind CSS configuration
Job List Page (jobs/page.tsx
)
This page will list all jobs with basic information and links to view or edit them.
// src/app/jobs/page.tsx
import Link from 'next/link';
import JobCard from '@/components/JobCard';
const jobs = [
{ id: 1, title: 'Frontend Developer', description: 'React, Tailwind CSS', status: 'Open' },
{ id: 2, title: 'Backend Developer', description: 'Node.js, PostgreSQL', status: 'Closed' },
{ id: 3, title: 'UI/UX Designer', description: 'Figma, Sketch', status: 'Open' },
];
export default function JobListPage() {
return (
<div className="min-h-screen bg-gray-100 p-6">
<h1 className="text-3xl font-semibold text-gray-800 mb-6">Job Listings</h1>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{jobs.map((job) => (
<JobCard key={job.id} job={job} />
))}
</div>
<div className="mt-8">
<Link href="/jobs/create" className="bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-700">
Create New Job
</Link>
</div>
</div>
);
}
Job Creation Page (jobs/create/page.tsx
)
This page is for creating a new job, using a reusable job form.
// src/app/jobs/create/page.tsx
import JobForm from '@/components/JobForm';
export default function CreateJobPage() {
const handleSubmit = (data: any) => {
// Logic to submit form data to the server
console.log('Job Created', data);
};
return (
<div className="min-h-screen bg-gray-100 p-6">
<h1 className="text-3xl font-semibold text-gray-800 mb-6">Create New Job</h1>
<JobForm onSubmit={handleSubmit} />
</div>
);
}
Job Edit Page (jobs/[id]/edit/page.tsx
)
This page is for editing an existing job. The form is pre-filled with the job’s existing data.
// src/app/jobs/[id]/edit/page.tsx
import JobForm from '@/components/JobForm';
const jobData = { title: 'Backend Developer', description: 'Node.js, PostgreSQL', status: 'Open' };
export default function EditJobPage() {
const handleSubmit = (data: any) => {
// Logic to submit form data to the server
console.log('Job Updated', data);
};
return (
<div className="min-h-screen bg-gray-100 p-6">
<h1 className="text-3xl font-semibold text-gray-800 mb-6">Edit Job</h1>
<JobForm onSubmit={handleSubmit} job={jobData} />
</div>
);
}
View Job Page (jobs/[id]/page.tsx
)
This page displays the details of a specific job.
// src/app/jobs/[id]/page.tsx
const jobData = { title: 'Frontend Developer', description: 'React, Tailwind CSS', status: 'Open' };
export default function ViewJobPage() {
return (
<div className="min-h-screen bg-gray-100 p-6">
<h1 className="text-3xl font-semibold text-gray-800 mb-6">{jobData.title}</h1>
<p className="text-gray-700 mb-4">Status: {jobData.status}</p>
<p className="text-gray-600">{jobData.description}</p>
</div>
);
}
Reusable Components
Job Card Component (components/JobCard.tsx
)
This component displays job summaries in a card format.
// src/components/JobCard.tsx
import Link from 'next/link';
interface JobCardProps {
job: {
id: number;
title: string;
description: string;
status: string;
};
}
export default function JobCard({ job }: JobCardProps) {
return (
<div className="bg-white shadow-lg p-6 rounded-lg">
<h2 className="text-xl font-medium text-gray-700">{job.title}</h2>
<p className="text-gray-600">{job.description}</p>
<p className={`mt-2 ${job.status === 'Open' ? 'text-green-500' : 'text-red-500'}`}>{job.status}</p>
<div className="mt-4">
<Link href={`/jobs/${job.id}`} className="text-blue-500 hover:underline">View</Link>
<Link href={`/jobs/${job.id}/edit`} className="ml-4 text-blue-500 hover:underline">Edit</Link>
</div>
</div>
);
}
Job Form Component (components/JobForm.tsx
)
This form component is used for both job creation and editing.
// src/components/JobForm.tsx
import { useState } from 'react';
interface JobFormProps {
onSubmit: (data: any) => void;
job?: {
title: string;
description: string;
status: string;
};
}
export default function JobForm({ onSubmit, job }: JobFormProps) {
const [title, setTitle] = useState(job?.title || '');
const [description, setDescription] = useState(job?.description || '');
const [status, setStatus] = useState(job?.status || 'Open');
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
onSubmit({ title, description, status });
};
return (
<form onSubmit={handleSubmit} className="bg-white p-6 rounded-lg shadow-lg">
<div className="mb-4">
<label className="block text-gray-700">Job Title</label>
<input
type="text"
className="mt-2 w-full px-3 py-2 border rounded"
value={title}
onChange={(e) => setTitle(e.target.value)}
required
/>
</div>
<div className="mb-4">
<label className="block text-gray-700">Description</label>
<textarea
className="mt-2 w-full px-3 py-2 border rounded"
rows={4}
value={description}
onChange={(e) => setDescription(e.target.value)}
required
></textarea>
</div>
<div className="mb-4">
<label className="block text-gray-700">Status</label>
<select
className="mt-2 w-full px-3 py-2 border rounded"
value={status}
onChange={(e) => setStatus(e.target.value)}
required
>
<option value="Open">Open</option>
<option value="Closed">Closed</option>
</select>
</div>
<button type="submit" className="bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-700">
{job ? 'Update Job' : 'Create Job'}
</button>
</form>
);
}
Tailwind CSS Configuration
Ensure you have Tailwind CSS configured and working:
- tailwind.config.js:
module.exports = {
content: [
'./app/**/*.{js,ts,jsx,tsx}',
'./components/**/*.{js,ts,jsx,tsx}',
],
theme: {
extend: {},
},
plugins: [],
};
-
Global CSS (
styles/globals.css
):
@tailwind base;
@tailwind components;
@tailwind utilities;
Resulting Features
- Job List Page: Lists all
jobs with options to view or edit them.
- Create Job Page: A form for recruiters to create new job listings.
- Edit Job Page: Pre-filled form for recruiters to update an existing job.
- View Job Page: Displays the details of a specific job.
This implementation uses Tailwind CSS for responsive styling and Next.js 14 app router for dynamic routing. You can further integrate API calls to fetch and manage jobs from the backend (e.g., with NestJS and Prisma).
Here’s the full implementation for the Authentication pages in Next.js 14 using Tailwind CSS. The folder structure includes login, register, and forgot password pages.
Folder Structure for Authentication
/project-root
├── /app
│ ├── /auth
│ │ ├── /login
│ │ │ └── page.tsx # Login page
│ │ ├── /register
│ │ │ └── page.tsx # Register page
│ │ └── /forgot-password
│ │ └── page.tsx # Forgot password page
├── /components
│ └── AuthForm.tsx # Reusable form component for authentication
├── /styles
│ └── globals.css # Tailwind CSS imports and global styles
└── tailwind.config.js # Tailwind CSS configuration
Login Page (auth/login/page.tsx
)
This page allows users to login using their email and password.
// src/app/auth/login/page.tsx
import AuthForm from '@/components/AuthForm';
export default function LoginPage() {
const handleSubmit = (data: any) => {
// Logic to authenticate user
console.log('Login Data', data);
};
return (
<div className="min-h-screen flex items-center justify-center bg-gray-100">
<AuthForm
type="login"
onSubmit={handleSubmit}
heading="Login to Your Account"
buttonLabel="Login"
footerMessage="Don't have an account?"
footerLink="/auth/register"
footerLinkText="Sign Up"
/>
</div>
);
}
Register Page (auth/register/page.tsx
)
This page allows new users to register for an account.
// src/app/auth/register/page.tsx
import AuthForm from '@/components/AuthForm';
export default function RegisterPage() {
const handleSubmit = (data: any) => {
// Logic to register user
console.log('Register Data', data);
};
return (
<div className="min-h-screen flex items-center justify-center bg-gray-100">
<AuthForm
type="register"
onSubmit={handleSubmit}
heading="Create a New Account"
buttonLabel="Sign Up"
footerMessage="Already have an account?"
footerLink="/auth/login"
footerLinkText="Login"
/>
</div>
);
}
Forgot Password Page (auth/forgot-password/page.tsx
)
This page allows users to request a password reset.
// src/app/auth/forgot-password/page.tsx
import { useState } from 'react';
export default function ForgotPasswordPage() {
const [email, setEmail] = useState('');
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
// Logic to handle forgot password
console.log('Reset Password for:', email);
};
return (
<div className="min-h-screen flex items-center justify-center bg-gray-100">
<div className="bg-white p-8 rounded-lg shadow-md w-full max-w-md">
<h2 className="text-3xl font-semibold text-gray-800 mb-6 text-center">Forgot Password</h2>
<form onSubmit={handleSubmit}>
<div className="mb-4">
<label className="block text-gray-700">Email Address</label>
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
className="mt-2 w-full px-3 py-2 border rounded"
required
/>
</div>
<button type="submit" className="w-full bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-700">
Send Reset Link
</button>
</form>
<div className="mt-4 text-center">
<a href="/auth/login" className="text-blue-500 hover:underline">
Back to Login
</a>
</div>
</div>
</div>
);
}
Reusable Authentication Form Component (components/AuthForm.tsx
)
This is a reusable form component for both login and register pages.
// src/components/AuthForm.tsx
import { useState } from 'react';
import Link from 'next/link';
interface AuthFormProps {
type: 'login' | 'register';
onSubmit: (data: { email: string; password: string }) => void;
heading: string;
buttonLabel: string;
footerMessage: string;
footerLink: string;
footerLinkText: string;
}
export default function AuthForm({
type,
onSubmit,
heading,
buttonLabel,
footerMessage,
footerLink,
footerLinkText,
}: AuthFormProps) {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
onSubmit({ email, password });
};
return (
<div className="bg-white p-8 rounded-lg shadow-md w-full max-w-md">
<h2 className="text-3xl font-semibold text-gray-800 mb-6 text-center">{heading}</h2>
<form onSubmit={handleSubmit}>
<div className="mb-4">
<label className="block text-gray-700">Email Address</label>
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
className="mt-2 w-full px-3 py-2 border rounded"
required
/>
</div>
<div className="mb-4">
<label className="block text-gray-700">Password</label>
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
className="mt-2 w-full px-3 py-2 border rounded"
required
/>
</div>
<button type="submit" className="w-full bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-700">
{buttonLabel}
</button>
</form>
{type === 'login' && (
<div className="mt-4 text-right">
<Link href="/auth/forgot-password" className="text-blue-500 hover:underline">
Forgot Password?
</Link>
</div>
)}
<div className="mt-6 text-center">
<p className="text-gray-600">
{footerMessage}{' '}
<Link href={footerLink} className="text-blue-500 hover:underline">
{footerLinkText}
</Link>
</p>
</div>
</div>
);
}
Tailwind CSS Configuration
Ensure you have Tailwind CSS configured correctly in your project:
- tailwind.config.js:
// tailwind.config.js
module.exports = {
content: [
'./app/**/*.{js,ts,jsx,tsx}',
'./components/**/*.{js,ts,jsx,tsx}',
],
theme: {
extend: {},
},
plugins: [],
};
-
Global CSS (
styles/globals.css
):
@tailwind base;
@tailwind components;
@tailwind utilities;
Resulting Pages
- Login Page: Users can log in with email and password. There’s also a link to the Forgot Password page.
- Register Page: New users can create an account by providing their email and password.
- Forgot Password Page: Users can request a password reset link by providing their email.
All pages use Tailwind CSS for styling and share a consistent layout using the AuthForm component. You can integrate these forms with your NestJS backend to handle actual authentication and password recovery logic.
Here’s the implementation for the Settings module in Next.js 14 using Tailwind CSS, which includes:
- Account Settings Page: For changing password and managing account-related details.
- Subscription and Pricing Page: For managing user subscriptions and plans.
- User Profile Page: For updating profile information such as name, email, and avatar.
Folder Structure for Settings Module
/project-root
├── /app
│ ├── /settings
│ │ ├── /account
│ │ │ └── page.tsx # Account settings page
│ │ ├── /subscription
│ │ │ └── page.tsx # Subscription and pricing page
│ │ └── /profile
│ │ └── page.tsx # User profile page
├── /components
│ ├── AccountForm.tsx # Form for changing password
│ ├── ProfileForm.tsx # Form for updating profile information
│ ├── SubscriptionCard.tsx # Card component for subscription plans
│ └── SubscriptionForm.tsx # Form for managing subscription changes
├── /styles
│ └── globals.css # Tailwind CSS imports and global styles
└── tailwind.config.js # Tailwind CSS configuration
Account Settings Page (settings/account/page.tsx
)
This page allows the user to change their password and update account-related settings.
// src/app/settings/account/page.tsx
import AccountForm from '@/components/AccountForm';
export default function AccountSettingsPage() {
const handleAccountUpdate = (data: any) => {
// Logic to handle account update (change password)
console.log('Account updated:', data);
};
return (
<div className="min-h-screen bg-gray-100 p-6">
<h1 className="text-3xl font-semibold text-gray-800 mb-6">Account Settings</h1>
<AccountForm onSubmit={handleAccountUpdate} />
</div>
);
}
Subscription and Pricing Page (settings/subscription/page.tsx
)
This page displays different subscription plans and allows users to upgrade or manage their plan.
// src/app/settings/subscription/page.tsx
import SubscriptionCard from '@/components/SubscriptionCard';
const subscriptionPlans = [
{ id: 1, name: 'Free Plan', price: '$0', features: ['Basic features', 'Limited access'] },
{ id: 2, name: 'Pro Plan', price: '$29/month', features: ['Full access', 'Premium support'] },
{ id: 3, name: 'Enterprise Plan', price: '$99/month', features: ['All features', 'Dedicated support'] },
];
export default function SubscriptionPage() {
return (
<div className="min-h-screen bg-gray-100 p-6">
<h1 className="text-3xl font-semibold text-gray-800 mb-6">Subscription and Pricing</h1>
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
{subscriptionPlans.map((plan) => (
<SubscriptionCard key={plan.id} plan={plan} />
))}
</div>
</div>
);
}
User Profile Page (settings/profile/page.tsx
)
This page allows users to update their profile information like name, email, and avatar.
// src/app/settings/profile/page.tsx
import ProfileForm from '@/components/ProfileForm';
export default function ProfilePage() {
const handleProfileUpdate = (data: any) => {
// Logic to handle profile update
console.log('Profile updated:', data);
};
return (
<div className="min-h-screen bg-gray-100 p-6">
<h1 className="text-3xl font-semibold text-gray-800 mb-6">User Profile</h1>
<ProfileForm onSubmit={handleProfileUpdate} />
</div>
);
}
Reusable Components
Account Form Component (components/AccountForm.tsx
)
This form allows users to change their password.
// src/components/AccountForm.tsx
import { useState } from 'react';
interface AccountFormProps {
onSubmit: (data: { currentPassword: string; newPassword: string }) => void;
}
export default function AccountForm({ onSubmit }: AccountFormProps) {
const [currentPassword, setCurrentPassword] = useState('');
const [newPassword, setNewPassword] = useState('');
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
onSubmit({ currentPassword, newPassword });
};
return (
<form onSubmit={handleSubmit} className="bg-white p-6 rounded-lg shadow-lg w-full max-w-lg">
<div className="mb-4">
<label className="block text-gray-700">Current Password</label>
<input
type="password"
className="mt-2 w-full px-3 py-2 border rounded"
value={currentPassword}
onChange={(e) => setCurrentPassword(e.target.value)}
required
/>
</div>
<div className="mb-4">
<label className="block text-gray-700">New Password</label>
<input
type="password"
className="mt-2 w-full px-3 py-2 border rounded"
value={newPassword}
onChange={(e) => setNewPassword(e.target.value)}
required
/>
</div>
<button type="submit" className="w-full bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-700">
Update Password
</button>
</form>
);
}
Profile Form Component (components/ProfileForm.tsx
)
This form allows users to update their profile information like name, email, and avatar.
// src/components/ProfileForm.tsx
import { useState } from 'react';
interface ProfileFormProps {
onSubmit: (data: { name: string; email: string }) => void;
}
export default function ProfileForm({ onSubmit }: ProfileFormProps) {
const [name, setName] = useState('');
const [email, setEmail] = useState('');
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
onSubmit({ name, email });
};
return (
<form onSubmit={handleSubmit} className="bg-white p-6 rounded-lg shadow-lg w-full max-w-lg">
<div className="mb-4">
<label className="block text-gray-700">Name</label>
<input
type="text"
className="mt-2 w-full px-3 py-2 border rounded"
value={name}
onChange={(e) => setName(e.target.value)}
required
/>
</div>
<div className="mb-4">
<label className="block text-gray-700">Email</label>
<input
type="email"
className="mt-2 w-full px-3 py-2 border rounded"
value={email}
onChange={(e) => setEmail(e.target.value)}
required
/>
</div>
<button type="submit" className="w-full bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-700">
Update Profile
</button>
</form>
);
}
Subscription Card Component (components/SubscriptionCard.tsx
)
This component displays a subscription plan with its features and price.
// src/components/SubscriptionCard.tsx
interface SubscriptionCardProps {
plan: {
name: string;
price: string;
features: string[];
};
}
export default function SubscriptionCard({ plan }: SubscriptionCardProps) {
return (
<div className="bg-white shadow-lg p-6 rounded-lg">
<h2 className="text-2xl font-medium text-gray-700">{plan.name}</h2>
<p className="text-4xl font-bold text-gray-900 mt-2">{plan.price}</p>
<ul className="mt-4 space-y-2">
{plan.features.map((feature, index) => (
<li key={index} className="text-gray-600">
- {feature}
</li>
))}
</ul>
<button className="mt-6 w-full bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-700">
Choose Plan
</button>
</div>
);
}
Tailwind CSS Configuration
Ensure Tailwind CSS is configured correctly:
- tailwind.config.js:
module.exports = {
content: [
'./app/**/*.{js,ts,jsx,tsx}',
'./components/**/*.{js,ts,jsx,tsx}',
],
theme: {
extend: {},
},
plugins: [],
};
-
Global CSS (
styles/globals.css
):
@tailwind base;
@tailwind components;
@tailwind utilities;
Resulting Pages
- Account Settings Page: Users can change their password. 2.
Subscription and Pricing Page: Displays available subscription plans with a pricing structure.
- User Profile Page: Users can update their profile information.
This setup uses Tailwind CSS for responsive and consistent styling across pages. Each form and card component can be easily extended to integrate with your backend services (e.g., NestJS with Prisma for account management and subscription handling).
Here’s the complete implementation for the Emails Page and the components involved in crafting and sending emails, along with the utility functions and configuration files for Prisma/PostgreSQL and NextAuth.js.
Folder Structure for Emails Feature
/project-root
├── /app
│ └── /emails
│ └── page.tsx # Email crafting page
├── /components
│ ├── JobCard.tsx # Component to display job summary
│ ├── CandidateCard.tsx # Component to display candidate summary
│ ├── MetricsCard.tsx # Component to show analytics
│ └── EmailForm.tsx # Component to craft and send emails
├── /lib
│ ├── db.ts # Prisma/PostgreSQL configuration
│ ├── auth.ts # NextAuth.js configuration (OAuth, JWT, etc.)
│ └── email.ts # Email utility functions
└── prisma
└── schema.prisma # Prisma schema
Emails Page (emails/page.tsx
)
This page allows users to craft and send emails to candidates.
// src/app/emails/page.tsx
import EmailForm from '@/components/EmailForm';
export default function EmailsPage() {
const handleSendEmail = (data: any) => {
// Logic to send email using the email utility function
console.log('Email Sent:', data);
};
return (
<div className="min-h-screen bg-gray-100 p-6">
<h1 className="text-3xl font-semibold text-gray-800 mb-6">Craft and Send Emails</h1>
<EmailForm onSubmit={handleSendEmail} />
</div>
);
}
Email Form Component (components/EmailForm.tsx
)
This form allows users to compose an email with fields for recipient, subject, and message.
// src/components/EmailForm.tsx
import { useState } from 'react';
interface EmailFormProps {
onSubmit: (data: { recipient: string; subject: string; message: string }) => void;
}
export default function EmailForm({ onSubmit }: EmailFormProps) {
const [recipient, setRecipient] = useState('');
const [subject, setSubject] = useState('');
const [message, setMessage] = useState('');
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
onSubmit({ recipient, subject, message });
};
return (
<form onSubmit={handleSubmit} className="bg-white p-6 rounded-lg shadow-lg w-full max-w-lg">
<div className="mb-4">
<label className="block text-gray-700">Recipient</label>
<input
type="email"
className="mt-2 w-full px-3 py-2 border rounded"
value={recipient}
onChange={(e) => setRecipient(e.target.value)}
required
/>
</div>
<div className="mb-4">
<label className="block text-gray-700">Subject</label>
<input
type="text"
className="mt-2 w-full px-3 py-2 border rounded"
value={subject}
onChange={(e) => setSubject(e.target.value)}
required
/>
</div>
<div className="mb-4">
<label className="block text-gray-700">Message</label>
<textarea
className="mt-2 w-full px-3 py-2 border rounded"
rows={6}
value={message}
onChange={(e) => setMessage(e.target.value)}
required
></textarea>
</div>
<button type="submit" className="w-full bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-700">
Send Email
</button>
</form>
);
}
Prisma/PostgreSQL Configuration (lib/db.ts
)
This file sets up Prisma and PostgreSQL for database interactions.
// src/lib/db.ts
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
export default prisma;
Prisma Schema (prisma/schema.prisma
)
You can define tables for users, emails, and other entities in the Prisma schema:
// prisma/schema.prisma
datasource db {
provider = "postgresql"
url = env("DATABASE_URL") // Set this in the .env file
}
generator client {
provider = "prisma-client-js"
}
model User {
id Int @id @default(autoincrement())
email String @unique
name String?
createdAt DateTime @default(now())
}
model Email {
id Int @id @default(autoincrement())
recipient String
subject String
message String
sentAt DateTime @default(now())
}
NextAuth.js Configuration (lib/auth.ts
)
Here’s how to configure NextAuth.js for OAuth and JWT-based authentication.
// src/lib/auth.ts
import NextAuth from 'next-auth';
import Providers from 'next-auth/providers';
import prisma from './db'; // Import Prisma client
export default NextAuth({
providers: [
Providers.Google({
clientId: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
}),
// Add more providers here
],
database: process.env.DATABASE_URL,
callbacks: {
async session(session, token) {
session.user.id = token.sub;
return session;
},
},
session: {
jwt: true,
},
jwt: {
secret: process.env.JWT_SECRET,
},
});
Email Utility Functions (lib/email.ts
)
This utility function sends emails via a service like SendGrid or AWS SES.
// src/lib/email.ts
import nodemailer from 'nodemailer';
export const sendEmail = async (recipient: string, subject: string, message: string) => {
const transporter = nodemailer.createTransport({
service: 'gmail', // You can also use SMTP service like SendGrid, AWS SES, etc.
auth: {
user: process.env.EMAIL_USER,
pass: process.env.EMAIL_PASSWORD,
},
});
const mailOptions = {
from: process.env.EMAIL_USER,
to: recipient,
subject: subject,
text: message,
};
await transporter.sendMail(mailOptions);
};
Other Components
JobCard Component (components/JobCard.tsx
)
Displays job summaries in a card format.
// src/components/JobCard.tsx
interface JobCardProps {
job: {
title: string;
description: string;
status: string;
};
}
export default function JobCard({ job }: JobCardProps) {
return (
<div className="bg-white shadow-lg p-6 rounded-lg">
<h2 className="text-xl font-medium text-gray-700">{job.title}</h2>
<p className="text-gray-600">{job.description}</p>
<p className={`mt-2 ${job.status === 'Open' ? 'text-green-500' : 'text-red-500'}`}>{job.status}</p>
</div>
);
}
CandidateCard Component (components/CandidateCard.tsx
)
Displays candidate details in a card format.
// src/components/CandidateCard.tsx
interface CandidateCardProps {
candidate: {
name: string;
skills: string[];
experience: number;
};
}
export default function CandidateCard({ candidate }: CandidateCardProps) {
return (
<div className="bg-white shadow-lg p-6 rounded-lg">
<h2 className="text-xl font-medium text-gray-700">{candidate.name}</h2>
<p className="text-gray-600">Skills: {candidate.skills.join(', ')}</p>
<p className="text-gray-600">Experience: {candidate.experience} years</p>
</div>
);
}
MetricsCard Component (components/MetricsCard.tsx
)
Displays metrics in a card format.
// src/components/MetricsCard.tsx
interface MetricsCardProps {
title: string;
value: string | number;
description: string;
}
export default function MetricsCard({ title, value, description }: MetricsCardProps) {
return (
<div className="bg-white shadow-lg p-6 rounded-lg">
<h2 className="text-xl font-medium text-gray-700">{title}</h2>
<p className="text-3xl font-bold text-gray-900">{value}</p>
<p className="mt-2 text-gray-600">{description}</p>
</div>
);
}
Tailwind CSS Configuration
Ensure Tailwind CSS is configured properly.
- tailwind.config.js:
module.exports = {
content: [
'./app/**/*.{js,ts,jsx,tsx}',
'./components/**/*.{js,ts,jsx,tsx}',
],
theme: {
extend: {},
},
plugins: [],
};
- **Global CSS (`
styles/globals.css`)**:
@tailwind base;
@tailwind components;
@tailwind utilities;
Resulting Features
- Emails Page: Users can craft and send emails using the EmailForm component.
- Email Utility: Uses Nodemailer (or other services) to send emails to recipients.
- Prisma & PostgreSQL: Configured for database interactions.
- NextAuth.js: OAuth and JWT authentication for handling user sessions.
- JobCard, CandidateCard, MetricsCard: Reusable components for displaying data in a card format.
This setup can be extended to integrate with the backend (e.g., NestJS, Prisma) for full functionality.
Here’s the full code for the NestJS backend with Prisma and PostgreSQL for both authentication and users modules. It includes the folder structure, DTOs, controllers, services, and Prisma integration for handling authentication and user management.
Folder Structure for NestJS Backend with Prisma and PostgreSQL
/project-backend
├── /prisma
│ └── schema.prisma # Prisma schema for database models
├── /src
│ ├── /auth # Authentication module (OAuth, JWT)
│ │ ├── /dto
│ │ │ └── login.dto.ts # DTO for login payload
│ │ ├── auth.controller.ts # Authentication controller
│ │ ├── auth.service.ts # Authentication service (JWT, OAuth)
│ │ └── auth.module.ts # Auth module declaration
│ │
│ ├── /users # Users module
│ │ ├── /dto
│ │ │ └── create-user.dto.ts # DTO for creating a new user
│ │ ├── users.controller.ts # Controller for user-related routes
│ │ ├── users.service.ts # Service for user business logic
│ │ ├── users.entity.ts # User entity definition for Prisma
│ │ └── users.module.ts # User module declaration
│ │
│ ├── /common # Common module (guards, interceptors, utilities)
│ │ └── jwt.guard.ts # JWT guard for protected routes
│ ├── /app.module.ts # Root application module
│ ├── /main.ts # Main entry point of the application
├── /lib
│ └── prisma.service.ts # Prisma client for database interaction
├── .env # Environment variables (DB connection, JWT secret)
└── package.json # Project dependencies
Prisma Schema (prisma/schema.prisma
)
This defines the database models for users and other related entities.
// prisma/schema.prisma
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
generator client {
provider = "prisma-client-js"
}
model User {
id Int @id @default(autoincrement())
email String @unique
password String
name String?
createdAt DateTime @default(now())
}
Prisma Service (lib/prisma.service.ts
)
This is the Prisma service that provides access to the database.
// src/lib/prisma.service.ts
import { Injectable, OnModuleInit, OnModuleDestroy } from '@nestjs/common';
import { PrismaClient } from '@prisma/client';
@Injectable()
export class PrismaService extends PrismaClient implements OnModuleInit, OnModuleDestroy {
async onModuleInit() {
await this.$connect();
}
async onModuleDestroy() {
await this.$disconnect();
}
}
Authentication Module
DTO (auth/dto/login.dto.ts
)
Data transfer object for login payload.
// src/auth/dto/login.dto.ts
import { IsEmail, IsNotEmpty, IsString } from 'class-validator';
export class LoginDto {
@IsEmail()
email: string;
@IsString()
@IsNotEmpty()
password: string;
}
Auth Controller (auth/auth.controller.ts
)
Handles authentication-related requests like login.
// src/auth/auth.controller.ts
import { Controller, Post, Body } from '@nestjs/common';
import { AuthService } from './auth.service';
import { LoginDto } from './dto/login.dto';
@Controller('auth')
export class AuthController {
constructor(private readonly authService: AuthService) {}
@Post('login')
async login(@Body() loginDto: LoginDto) {
return this.authService.login(loginDto);
}
}
Auth Service (auth/auth.service.ts
)
Contains business logic for authentication (JWT handling).
// src/auth/auth.service.ts
import { Injectable, UnauthorizedException } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import { PrismaService } from 'src/lib/prisma.service';
import { LoginDto } from './dto/login.dto';
import * as bcrypt from 'bcrypt';
@Injectable()
export class AuthService {
constructor(private prisma: PrismaService, private jwtService: JwtService) {}
async login(loginDto: LoginDto) {
const user = await this.prisma.user.findUnique({
where: { email: loginDto.email },
});
if (!user || !(await bcrypt.compare(loginDto.password, user.password))) {
throw new UnauthorizedException('Invalid credentials');
}
const payload = { email: user.email, sub: user.id };
return {
access_token: this.jwtService.sign(payload),
};
}
}
Auth Module (auth/auth.module.ts
)
The authentication module declaration that ties everything together.
// src/auth/auth.module.ts
import { Module } from '@nestjs/common';
import { JwtModule } from '@nestjs/jwt';
import { AuthService } from './auth.service';
import { AuthController } from './auth.controller';
import { PrismaService } from 'src/lib/prisma.service';
@Module({
imports: [
JwtModule.register({
secret: process.env.JWT_SECRET,
signOptions: { expiresIn: '1h' },
}),
],
controllers: [AuthController],
providers: [AuthService, PrismaService],
})
export class AuthModule {}
Users Module
DTO (users/dto/create-user.dto.ts
)
DTO for creating a new user.
// src/users/dto/create-user.dto.ts
import { IsEmail, IsNotEmpty, IsString } from 'class-validator';
export class CreateUserDto {
@IsEmail()
email: string;
@IsString()
@IsNotEmpty()
password: string;
@IsString()
name?: string;
}
Users Controller (users/users.controller.ts
)
Handles routes related to user management.
// src/users/users.controller.ts
import { Controller, Post, 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) {}
@Post('create')
async createUser(@Body() createUserDto: CreateUserDto) {
return this.usersService.createUser(createUserDto);
}
}
Users Service (users/users.service.ts
)
Contains business logic for managing users (creating users, retrieving users).
// src/users/users.service.ts
import { Injectable } from '@nestjs/common';
import { PrismaService } from 'src/lib/prisma.service';
import { CreateUserDto } from './dto/create-user.dto';
import * as bcrypt from 'bcrypt';
@Injectable()
export class UsersService {
constructor(private prisma: PrismaService) {}
async createUser(createUserDto: CreateUserDto) {
const hashedPassword = await bcrypt.hash(createUserDto.password, 10);
return this.prisma.user.create({
data: {
email: createUserDto.email,
password: hashedPassword,
name: createUserDto.name,
},
});
}
}
Users Module (users/users.module.ts
)
The users module declaration.
// src/users/users.module.ts
import { Module } from '@nestjs/common';
import { UsersService } from './users.service';
import { UsersController } from './users.controller';
import { PrismaService } from 'src/lib/prisma.service';
@Module({
controllers: [UsersController],
providers: [UsersService, PrismaService],
})
export class UsersModule {}
JWT Guard (common/jwt.guard.ts
)
A guard to protect routes that require authentication via JWT.
// src/common/jwt.guard.ts
import { Injectable, ExecutionContext, UnauthorizedException } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
import { JwtService } from '@nestjs/jwt';
@Injectable()
export class JwtAuthGuard extends AuthGuard('jwt') {
constructor(private jwtService: JwtService) {
super();
}
canActivate(context: ExecutionContext) {
const request = context.switchToHttp().getRequest();
const token = request.headers.authorization?.split(' ')[1];
if (!token) {
throw new UnauthorizedException('Missing token');
}
try {
this.jwtService.verify(token);
} catch (error) {
throw new UnauthorizedException('Invalid token');
}
return super.canActivate(context);
}
}
Root Module (app.module.ts
)
The root application module that imports all other modules.
// src/app.module.ts
import { Module } from '@nestjs/common';
import { AuthModule } from './auth/auth.module';
import { UsersModule } from './users/users.module';
@Module({
imports: [AuthModule, UsersModule],
})
export class AppModule {}
Main Entry Point (main.ts
)
The main entry point of the application.
// src/main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
await
app.listen(3000);
}
bootstrap();
Environment Variables (.env
)
Set up environment variables for PostgreSQL and JWT secret.
DATABASE_URL=postgresql://user:password@localhost:5432/mydatabase
JWT_SECRET=my_super_secret_jwt_key
Project Dependencies (package.json
)
Include the necessary dependencies for NestJS, Prisma, bcrypt, and JWT.
{
"name": "project-backend",
"version": "1.0.0",
"main": "dist/main.js",
"scripts": {
"start": "nest start",
"start:dev": "nest start --watch",
"prisma:generate": "prisma generate",
"prisma:migrate": "prisma migrate dev"
},
"dependencies": {
"@nestjs/common": "^8.0.0",
"@nestjs/core": "^8.0.0",
"@nestjs/jwt": "^8.0.0",
"@nestjs/passport": "^8.0.0",
"bcrypt": "^5.0.0",
"prisma": "^3.0.0",
"@prisma/client": "^3.0.0",
"passport-jwt": "^4.0.0",
"jsonwebtoken": "^8.0.0",
"reflect-metadata": "^0.1.13",
"rxjs": "^7.0.0"
}
}
Conclusion
This setup provides a NestJS backend integrated with Prisma and PostgreSQL, handling:
- Authentication with JWT (login).
- User management (creating users).
- Protected routes using a JWT guard.
- Prisma ORM to interact with the PostgreSQL database.
You can extend this with more features such as registration, password resets, or additional user roles.
Here's the complete implementation for the Jobs module and Emails module in a NestJS project using Prisma and PostgreSQL for job management and email sending. This includes DTOs, controllers, services, and Prisma schema for both modules.
Folder Structure for Jobs and Emails Module
/project-backend
├── /src
│ ├── /jobs # Jobs module
│ │ ├── /dto
│ │ │ ├── create-job.dto.ts # DTO for creating a job
│ │ │ └── update-job.dto.ts # DTO for updating a job
│ │ ├── jobs.controller.ts # Controller for job routes (CRUD)
│ │ ├── jobs.service.ts # Business logic for managing jobs
│ │ ├── jobs.entity.ts # Prisma schema for Job entity
│ │ └── jobs.module.ts # Job module declaration
│ │
│ ├── /emails # Email module (for crafting and sending emails)
│ │ ├── /dto
│ │ │ └── send-email.dto.ts # DTO for sending email
│ │ ├── emails.controller.ts # Controller for email sending APIs
│ │ ├── emails.service.ts # Service for email logic (using SendGrid or AWS SES)
│ │ └── emails.module.ts # Email module declaration
│
├── /lib
│ └── prisma.service.ts # Prisma client for database interaction
├── /prisma
│ └── schema.prisma # Prisma schema for Job entity
└── .env # Environment variables (DB connection, Email credentials)
Jobs Module
DTOs for Jobs
Create Job DTO (jobs/dto/create-job.dto.ts
)
This DTO defines the data required to create a job.
// src/jobs/dto/create-job.dto.ts
import { IsString, IsNotEmpty, IsOptional } from 'class-validator';
export class CreateJobDto {
@IsString()
@IsNotEmpty()
title: string;
@IsString()
@IsNotEmpty()
description: string;
@IsString()
@IsOptional()
status?: string; // Open, Closed, etc.
}
Update Job DTO (jobs/dto/update-job.dto.ts
)
This DTO defines the data required to update an existing job.
// src/jobs/dto/update-job.dto.ts
import { IsString, IsOptional } from 'class-validator';
export class UpdateJobDto {
@IsString()
@IsOptional()
title?: string;
@IsString()
@IsOptional()
description?: string;
@IsString()
@IsOptional()
status?: string;
}
Jobs Controller (jobs/jobs.controller.ts
)
The controller handles job-related CRUD operations like creating, updating, and fetching jobs.
// src/jobs/jobs.controller.ts
import { Controller, Get, Post, Body, Param, Patch, Delete } from '@nestjs/common';
import { JobsService } from './jobs.service';
import { CreateJobDto } from './dto/create-job.dto';
import { UpdateJobDto } from './dto/update-job.dto';
@Controller('jobs')
export class JobsController {
constructor(private readonly jobsService: JobsService) {}
@Post()
async createJob(@Body() createJobDto: CreateJobDto) {
return this.jobsService.createJob(createJobDto);
}
@Get()
async findAll() {
return this.jobsService.findAllJobs();
}
@Get(':id')
async findOne(@Param('id') id: string) {
return this.jobsService.findJobById(+id);
}
@Patch(':id')
async updateJob(@Param('id') id: string, @Body() updateJobDto: UpdateJobDto) {
return this.jobsService.updateJob(+id, updateJobDto);
}
@Delete(':id')
async deleteJob(@Param('id') id: string) {
return this.jobsService.deleteJob(+id);
}
}
Jobs Service (jobs/jobs.service.ts
)
The service contains business logic for job management, including database interactions via Prisma.
// src/jobs/jobs.service.ts
import { Injectable, NotFoundException } from '@nestjs/common';
import { PrismaService } from 'src/lib/prisma.service';
import { CreateJobDto } from './dto/create-job.dto';
import { UpdateJobDto } from './dto/update-job.dto';
@Injectable()
export class JobsService {
constructor(private prisma: PrismaService) {}
async createJob(createJobDto: CreateJobDto) {
return this.prisma.job.create({
data: createJobDto,
});
}
async findAllJobs() {
return this.prisma.job.findMany();
}
async findJobById(id: number) {
const job = await this.prisma.job.findUnique({ where: { id } });
if (!job) throw new NotFoundException('Job not found');
return job;
}
async updateJob(id: number, updateJobDto: UpdateJobDto) {
return this.prisma.job.update({
where: { id },
data: updateJobDto,
});
}
async deleteJob(id: number) {
return this.prisma.job.delete({ where: { id } });
}
}
Jobs Module (jobs/jobs.module.ts
)
The module declaration for jobs, which imports all the necessary parts.
// src/jobs/jobs.module.ts
import { Module } from '@nestjs/common';
import { JobsService } from './jobs.service';
import { JobsController } from './jobs.controller';
import { PrismaService } from 'src/lib/prisma.service';
@Module({
controllers: [JobsController],
providers: [JobsService, PrismaService],
})
export class JobsModule {}
Prisma Schema for Job (prisma/schema.prisma
)
The Prisma schema defines the Job
model in PostgreSQL.
// prisma/schema.prisma
model Job {
id Int @id @default(autoincrement())
title String
description String
status String @default("Open") // Open or Closed
createdAt DateTime @default(now())
}
Emails Module
DTO for Sending Email (emails/dto/send-email.dto.ts
)
This DTO defines the data required to send an email.
// src/emails/dto/send-email.dto.ts
import { IsEmail, IsString, IsNotEmpty } from 'class-validator';
export class SendEmailDto {
@IsEmail()
@IsNotEmpty()
recipient: string;
@IsString()
@IsNotEmpty()
subject: string;
@IsString()
@IsNotEmpty()
message: string;
}
Emails Controller (emails/emails.controller.ts
)
The controller handles email sending requests using an external email service like SendGrid or AWS SES.
// src/emails/emails.controller.ts
import { Controller, Post, Body } from '@nestjs/common';
import { EmailsService } from './emails.service';
import { SendEmailDto } from './dto/send-email.dto';
@Controller('emails')
export class EmailsController {
constructor(private readonly emailsService: EmailsService) {}
@Post('send')
async sendEmail(@Body() sendEmailDto: SendEmailDto) {
return this.emailsService.sendEmail(sendEmailDto);
}
}
Emails Service (emails/emails.service.ts
)
The service contains business logic for sending emails using SendGrid, AWS SES, or Nodemailer.
// src/emails/emails.service.ts
import { Injectable } from '@nestjs/common';
import { SendEmailDto } from './dto/send-email.dto';
import * as nodemailer from 'nodemailer';
@Injectable()
export class EmailsService {
async sendEmail(sendEmailDto: SendEmailDto) {
const transporter = nodemailer.createTransport({
service: 'gmail', // Or use an SMTP provider like SendGrid
auth: {
user: process.env.EMAIL_USER,
pass: process.env.EMAIL_PASSWORD,
},
});
const mailOptions = {
from: process.env.EMAIL_USER,
to: sendEmailDto.recipient,
subject: sendEmailDto.subject,
text: sendEmailDto.message,
};
return transporter.sendMail(mailOptions);
}
}
Emails Module (emails/emails.module.ts
)
The module declaration for the email module.
// src/emails/emails.module.ts
import { Module } from '@nestjs/common';
import { EmailsService } from './emails.service';
import { EmailsController } from './emails.controller';
@Module({
controllers: [EmailsController],
providers: [EmailsService],
})
export class EmailsModule {}
Environment Variables (.env
)
Include environment variables for the email service configuration (Gmail or SMTP).
DATABASE_URL=postgresql://user:password@localhost:5432/mydatabase
EMAIL_USER=your-email@gmail.com
EMAIL_PASSWORD=your-email-password
Conclusion
This setup provides a fully functional Jobs module for job management and an Emails module for sending emails. You can extend this by integrating it with NestJS Guards for authentication and role-based access control, or by using
other email services like SendGrid or AWS SES.
Here’s the full code for the Metrics and Analytics module and the Interviews module in NestJS, including DTOs, controllers, services, and module declarations.
Folder Structure for Metrics and Interviews Modules
/project-backend
├── /src
│ ├── /metrics # Metrics and analytics module
│ │ ├── metrics.controller.ts # Controller for retrieving recruitment metrics
│ │ ├── metrics.service.ts # Service to handle metric calculations
│ │ └── metrics.module.ts # Metrics module declaration
│ │
│ ├── /interviews # Interviews module
│ │ ├── /dto
│ │ │ └── schedule-interview.dto.ts # DTO for scheduling interviews
│ │ ├── interviews.controller.ts # Controller for interview-related routes
│ │ ├── interviews.service.ts # Business logic for interview scheduling
│ │ └── interviews.module.ts # Interview module declaration
├── /lib
│ └── prisma.service.ts # Prisma client for database interaction
├── /prisma
│ └── schema.prisma # Prisma schema for Job and Interview models
└── .env # Environment variables (DB connection)
Metrics and Analytics Module
The Metrics module will retrieve data from the database and perform calculations on job listings, candidates, emails sent, and more.
Metrics Controller (metrics/metrics.controller.ts
)
The controller handles routes to fetch recruitment metrics such as job counts, email success rates, and more.
// src/metrics/metrics.controller.ts
import { Controller, Get } from '@nestjs/common';
import { MetricsService } from './metrics.service';
@Controller('metrics')
export class MetricsController {
constructor(private readonly metricsService: MetricsService) {}
@Get('recruitment')
async getRecruitmentMetrics() {
return this.metricsService.getRecruitmentMetrics();
}
@Get('email-performance')
async getEmailPerformanceMetrics() {
return this.metricsService.getEmailPerformanceMetrics();
}
}
Metrics Service (metrics/metrics.service.ts
)
The service contains the logic to fetch metrics from the database, including job-related data, email performance, and interview statistics.
// src/metrics/metrics.service.ts
import { Injectable } from '@nestjs/common';
import { PrismaService } from 'src/lib/prisma.service';
@Injectable()
export class MetricsService {
constructor(private prisma: PrismaService) {}
// Fetch job-related metrics
async getRecruitmentMetrics() {
const openJobs = await this.prisma.job.count({ where: { status: 'Open' } });
const closedJobs = await this.prisma.job.count({ where: { status: 'Closed' } });
const totalJobs = await this.prisma.job.count();
return {
openJobs,
closedJobs,
totalJobs,
};
}
// Fetch email performance metrics
async getEmailPerformanceMetrics() {
// Assume we have an Email table in the Prisma schema
const sentEmails = await this.prisma.email.count();
const openedEmails = await this.prisma.email.count({ where: { status: 'Opened' } });
const clickedEmails = await this.prisma.email.count({ where: { status: 'Clicked' } });
return {
sentEmails,
openedEmails,
clickedEmails,
};
}
}
Metrics Module (metrics/metrics.module.ts
)
This module declaration ties everything together.
// src/metrics/metrics.module.ts
import { Module } from '@nestjs/common';
import { MetricsService } from './metrics.service';
import { MetricsController } from './metrics.controller';
import { PrismaService } from 'src/lib/prisma.service';
@Module({
controllers: [MetricsController],
providers: [MetricsService, PrismaService],
})
export class MetricsModule {}
Interviews Module
The Interviews module allows users to schedule interviews, manage them, and retrieve interview-related data.
Schedule Interview DTO (interviews/dto/schedule-interview.dto.ts
)
This DTO defines the data required to schedule an interview.
// src/interviews/dto/schedule-interview.dto.ts
import { IsString, IsNotEmpty, IsDateString } from 'class-validator';
export class ScheduleInterviewDto {
@IsString()
@IsNotEmpty()
candidateId: string;
@IsString()
@IsNotEmpty()
jobId: string;
@IsDateString()
@IsNotEmpty()
interviewDate: string;
@IsString()
@IsNotEmpty()
interviewType: string; // e.g., Technical, Behavioral
}
Interviews Controller (interviews/interviews.controller.ts
)
The controller handles routes for interview-related tasks such as scheduling and retrieving interview details.
// src/interviews/interviews.controller.ts
import { Controller, Post, Get, Param, Body } from '@nestjs/common';
import { InterviewsService } from './interviews.service';
import { ScheduleInterviewDto } from './dto/schedule-interview.dto';
@Controller('interviews')
export class InterviewsController {
constructor(private readonly interviewsService: InterviewsService) {}
@Post('schedule')
async scheduleInterview(@Body() scheduleInterviewDto: ScheduleInterviewDto) {
return this.interviewsService.scheduleInterview(scheduleInterviewDto);
}
@Get(':id')
async getInterviewById(@Param('id') id: string) {
return this.interviewsService.getInterviewById(+id);
}
}
Interviews Service (interviews/interviews.service.ts
)
The service contains business logic for scheduling interviews, managing interview data, and fetching scheduled interviews.
// src/interviews/interviews.service.ts
import { Injectable } from '@nestjs/common';
import { PrismaService } from 'src/lib/prisma.service';
import { ScheduleInterviewDto } from './dto/schedule-interview.dto';
@Injectable()
export class InterviewsService {
constructor(private prisma: PrismaService) {}
async scheduleInterview(scheduleInterviewDto: ScheduleInterviewDto) {
return this.prisma.interview.create({
data: {
candidateId: scheduleInterviewDto.candidateId,
jobId: scheduleInterviewDto.jobId,
interviewDate: new Date(scheduleInterviewDto.interviewDate),
interviewType: scheduleInterviewDto.interviewType,
},
});
}
async getInterviewById(id: number) {
return this.prisma.interview.findUnique({ where: { id } });
}
}
Interviews Module (interviews/interviews.module.ts
)
This module declaration ties everything together for the Interviews module.
// src/interviews/interviews.module.ts
import { Module } from '@nestjs/common';
import { InterviewsService } from './interviews.service';
import { InterviewsController } from './interviews.controller';
import { PrismaService } from 'src/lib/prisma.service';
@Module({
controllers: [InterviewsController],
providers: [InterviewsService, PrismaService],
})
export class InterviewsModule {}
Prisma Schema (prisma/schema.prisma
)
Here’s how the Prisma schema would look for defining jobs, emails, and interviews.
// prisma/schema.prisma
model Job {
id Int @id @default(autoincrement())
title String
description String
status String @default("Open")
createdAt DateTime @default(now())
interviews Interview[]
}
model Interview {
id Int @id @default(autoincrement())
candidateId String
jobId Int
interviewDate DateTime
interviewType String
job Job @relation(fields: [jobId], references: [id])
}
model Email {
id Int @id @default(autoincrement())
recipient String
subject String
message String
status String @default("Sent") // Status: Sent, Opened, Clicked
sentAt DateTime @default(now())
}
Conclusion
This setup provides fully functional Metrics and Interviews modules in NestJS, integrated with Prisma and PostgreSQL. The Metrics module can calculate and return various recruitment-related metrics, while the Interviews module allows scheduling and managing job interviews. You can further extend this implementation by adding additional analytics, reports, and interview notifications.
Here's the complete implementation for the Payments module with Stripe integration and Prisma module for database management in a NestJS application. This includes DTOs, controllers, services, and Prisma configuration for both payments and database management.
Folder Structure for Payments and Prisma Modules
/project-backend
├── /src
│ ├── /payments # Payments module for Stripe integration
│ │ ├── /dto
│ │ │ └── payment.dto.ts # DTO for handling payment payloads
│ │ ├── payments.controller.ts # Controller for handling subscription payments
│ │ ├── payments.service.ts # Service to integrate Stripe for subscription management
│ │ └── payments.module.ts # Payments module declaration
│ │
│ ├── /prisma # Prisma module for database management
│ │ ├── prisma.module.ts # Prisma module for database connection
│ │ ├── prisma.service.ts # Prisma service (initialize and handle DB operations)
│ │ └── schema.prisma # Prisma schema file for defining DB structure
├── /prisma
│ └── schema.prisma # Prisma schema for database models (Users, Payments, Subscriptions)
└── .env # Environment variables (Stripe keys, DB connection)
Payments Module with Stripe Integration
The Payments module will handle subscription management via Stripe. This module will allow users to make payments, subscribe to plans, and manage their subscriptions.
Payment DTO (payments/dto/payment.dto.ts
)
This DTO defines the data required to process a payment.
// src/payments/dto/payment.dto.ts
import { IsString, IsNotEmpty, IsNumber } from 'class-validator';
export class PaymentDto {
@IsString()
@IsNotEmpty()
token: string; // Stripe token or payment method ID
@IsNumber()
@IsNotEmpty()
amount: number;
@IsString()
@IsNotEmpty()
currency: string; // e.g., 'usd'
}
Payments Controller (payments/payments.controller.ts
)
The controller handles payment-related routes, such as creating payments and managing subscriptions.
// src/payments/payments.controller.ts
import { Controller, Post, Body, HttpException, HttpStatus } from '@nestjs/common';
import { PaymentsService } from './payments.service';
import { PaymentDto } from './dto/payment.dto';
@Controller('payments')
export class PaymentsController {
constructor(private readonly paymentsService: PaymentsService) {}
@Post('create')
async createPayment(@Body() paymentDto: PaymentDto) {
try {
const payment = await this.paymentsService.createPayment(paymentDto);
return payment;
} catch (error) {
throw new HttpException('Payment Failed', HttpStatus.BAD_REQUEST);
}
}
@Post('subscribe')
async createSubscription(@Body('email') email: string, @Body('planId') planId: string) {
try {
return this.paymentsService.createSubscription(email, planId);
} catch (error) {
throw new HttpException('Subscription Failed', HttpStatus.BAD_REQUEST);
}
}
}
Payments Service (payments/payments.service.ts
)
The service contains the logic to handle Stripe payments and subscriptions.
// src/payments/payments.service.ts
import { Injectable } from '@nestjs/common';
import { PaymentDto } from './dto/payment.dto';
import Stripe from 'stripe';
@Injectable()
export class PaymentsService {
private stripe: Stripe;
constructor() {
this.stripe = new Stripe(process.env.STRIPE_SECRET_KEY, {
apiVersion: '2022-08-01',
});
}
// Create a Stripe payment charge
async createPayment(paymentDto: PaymentDto) {
const { token, amount, currency } = paymentDto;
const charge = await this.stripe.charges.create({
amount: amount * 100, // Stripe uses smallest currency unit
currency: currency,
source: token,
description: 'Payment for subscription',
});
if (charge.status !== 'succeeded') {
throw new Error('Payment failed');
}
return {
success: true,
message: 'Payment successful',
chargeId: charge.id,
};
}
// Create a subscription for a customer
async createSubscription(email: string, planId: string) {
const customer = await this.stripe.customers.create({
email: email,
});
const subscription = await this.stripe.subscriptions.create({
customer: customer.id,
items: [
{
plan: planId, // Plan ID from Stripe dashboard
},
],
});
return {
success: true,
message: 'Subscription created successfully',
subscriptionId: subscription.id,
};
}
}
Payments Module (payments/payments.module.ts
)
The module declaration ties everything together for the Payments module.
// src/payments/payments.module.ts
import { Module } from '@nestjs/common';
import { PaymentsService } from './payments.service';
import { PaymentsController } from './payments.controller';
@Module({
controllers: [PaymentsController],
providers: [PaymentsService],
})
export class PaymentsModule {}
Prisma Module for Database Management
The Prisma module is responsible for managing the database connection and operations for all the models (e.g., Users, Payments, Subscriptions).
Prisma Module (prisma/prisma.module.ts
)
The Prisma module sets up the Prisma service that handles database connections and operations.
// src/prisma/prisma.module.ts
import { Module } from '@nestjs/common';
import { PrismaService } from './prisma.service';
@Module({
providers: [PrismaService],
exports: [PrismaService],
})
export class PrismaModule {}
Prisma Service (prisma/prisma.service.ts
)
The Prisma service contains the logic to initialize and handle the database connection.
// src/prisma/prisma.service.ts
import { Injectable, OnModuleInit, OnModuleDestroy } from '@nestjs/common';
import { PrismaClient } from '@prisma/client';
@Injectable()
export class PrismaService extends PrismaClient implements OnModuleInit, OnModuleDestroy {
async onModuleInit() {
await this.$connect();
}
async onModuleDestroy() {
await this.$disconnect();
}
}
Prisma Schema (prisma/schema.prisma
)
This schema defines the models for Users, Payments, and Subscriptions.
// prisma/schema.prisma
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
generator client {
provider = "prisma-client-js"
}
model User {
id Int @id @default(autoincrement())
email String @unique
password String
name String?
createdAt DateTime @default(now())
subscriptions Subscription[]
}
model Subscription {
id Int @id @default(autoincrement())
userId Int
planId String
status String @default("active")
createdAt DateTime @default(now())
user User @relation(fields: [userId], references: [id])
}
model Payment {
id Int @id @default(autoincrement())
userId Int
amount Float
currency String
chargeId String
status String @default("completed")
createdAt DateTime @default(now())
user User @relation(fields: [userId], references: [id])
}
Environment Variables (.env
)
Include environment variables for Stripe API keys and PostgreSQL connection.
DATABASE_URL=postgresql://user:password@localhost:5432/mydatabase
STRIPE_SECRET_KEY=your_stripe_secret_key
Project Dependencies (package.json
)
Ensure you have the necessary dependencies for NestJS, Stripe, and Prisma in your project.
{
"name": "project-backend",
"version": "1.0.0",
"scripts": {
"start": "nest start",
"start:dev": "nest start --watch",
"prisma:generate": "prisma generate",
"prisma:migrate": "prisma migrate dev"
},
"dependencies": {
"@nestjs/common": "^8.0.0",
"@nestjs/core": "^8.0.0",
"@nestjs/jwt": "^8.0.0",
"@nestjs/passport": "^8.0.0",
"stripe": "^8.0.0",
"prisma": "^3.0.0",
"@prisma/client": "^3.0.0",
"dotenv": "^8.0.0",
"reflect-metadata": "^0.1.13",
"rxjs": "^7.0.0"
}
}
Conclusion
This setup provides a fully functional Payments module integrated with Stripe for handling subscription payments and regular payments. The Prisma module is configured to manage the database connection and handle entities such as Users, Payments, and Subscriptions. This can be extended with additional features such as handling webhook events from Stripe, managing refunds, or expanding subscription
tiers.
Here’s the complete implementation for the Common Module, which includes Guards, Interceptors, and Utility functions, along with the Root Module (app.module.ts
), Application entry point (main.ts
), and Environment variable validation (env.validation.ts
).
Folder Structure
/project-backend
├── /src
│ ├── /common # Common module (utilities, guards, interceptors)
│ │ ├── /guards # Authorization guards (e.g., RoleGuard, JwtAuthGuard)
│ │ ├── /interceptors # Custom interceptors for logging or transformation
│ │ └── /utils # Shared utility functions (e.g., helper functions)
│ │
│ ├── app.module.ts # Root module that imports and registers all other modules
│ ├── main.ts # Application entry point
│ └── env.validation.ts # Environment variable validation schema
└── .env # Environment variables (DB connection, JWT secret, etc.)
Guards
JWT Auth Guard (common/guards/jwt-auth.guard.ts
)
The JWT Auth Guard is used to protect routes that require authentication.
// src/common/guards/jwt-auth.guard.ts
import { Injectable, ExecutionContext, UnauthorizedException } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
import { JwtService } from '@nestjs/jwt';
@Injectable()
export class JwtAuthGuard extends AuthGuard('jwt') {
constructor(private jwtService: JwtService) {
super();
}
canActivate(context: ExecutionContext) {
const request = context.switchToHttp().getRequest();
const token = request.headers.authorization?.split(' ')[1];
if (!token) {
throw new UnauthorizedException('Missing token');
}
try {
this.jwtService.verify(token);
} catch (error) {
throw new UnauthorizedException('Invalid token');
}
return super.canActivate(context);
}
}
Role Guard (common/guards/role.guard.ts
)
The Role Guard checks if the authenticated user has the required role to access a particular route.
// src/common/guards/role.guard.ts
import { Injectable, CanActivate, ExecutionContext, ForbiddenException } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
@Injectable()
export class RoleGuard implements CanActivate {
constructor(private reflector: Reflector) {}
canActivate(context: ExecutionContext): boolean {
const requiredRoles = this.reflector.get<string[]>('roles', context.getHandler());
if (!requiredRoles) {
return true;
}
const request = context.switchToHttp().getRequest();
const user = request.user; // Assuming the JWT auth guard already attached the user object to the request
if (!requiredRoles.includes(user.role)) {
throw new ForbiddenException('You do not have permission to access this resource');
}
return true;
}
}
Interceptors
Logging Interceptor (common/interceptors/logging.interceptor.ts
)
The Logging Interceptor is used to log the incoming request and outgoing response times.
// src/common/interceptors/logging.interceptor.ts
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> {
const now = Date.now();
const request = context.switchToHttp().getRequest();
const method = request.method;
const url = request.url;
return next.handle().pipe(
tap(() => console.log(`${method} ${url} - ${Date.now() - now}ms`)),
);
}
}
Utility Functions
Helper Utility (common/utils/helper.util.ts
)
Utility functions for common tasks.
// src/common/utils/helper.util.ts
export function formatResponse(data: any) {
return {
success: true,
data,
};
}
export function handleError(error: any) {
console.error(error);
return {
success: false,
message: 'Something went wrong!',
};
}
Root Module
App Module (app.module.ts
)
The root module of the application, which imports and registers all other modules.
// src/app.module.ts
import { Module } from '@nestjs/common';
import { AuthModule } from './auth/auth.module';
import { UsersModule } from './users/users.module';
import { JobsModule } from './jobs/jobs.module';
import { PaymentsModule } from './payments/payments.module';
import { MetricsModule } from './metrics/metrics.module';
import { InterviewsModule } from './interviews/interviews.module';
import { PrismaModule } from './prisma/prisma.module';
@Module({
imports: [
AuthModule,
UsersModule,
JobsModule,
PaymentsModule,
MetricsModule,
InterviewsModule,
PrismaModule,
],
})
export class AppModule {}
Application Entry Point
Main File (main.ts
)
The entry point for the application, where NestFactory is used to create the app and apply global guards, interceptors, or middleware if necessary.
// src/main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { ValidationPipe } from '@nestjs/common';
import { LoggingInterceptor } from './common/interceptors/logging.interceptor';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
// Global validation pipe for handling validation via DTOs
app.useGlobalPipes(new ValidationPipe());
// Global logging interceptor for logging requests
app.useGlobalInterceptors(new LoggingInterceptor());
await app.listen(3000);
}
bootstrap();
Environment Variable Validation
Environment Validation Schema (env.validation.ts
)
This ensures that required environment variables are set, using Joi for validation.
// src/env.validation.ts
import * as Joi from 'joi';
export const envValidationSchema = Joi.object({
DATABASE_URL: Joi.string().required(),
JWT_SECRET: Joi.string().required(),
STRIPE_SECRET_KEY: Joi.string().required(),
EMAIL_USER: Joi.string().required(),
EMAIL_PASSWORD: Joi.string().required(),
});
Environment Variables (.env
)
You can define the environment variables in the .env
file:
DATABASE_URL=postgresql://user:password@localhost:5432/mydatabase
JWT_SECRET=my_super_secret_jwt_key
STRIPE_SECRET_KEY=your_stripe_secret_key
EMAIL_USER=your-email@gmail.com
EMAIL_PASSWORD=your-email-password
Conclusion
This setup provides a robust structure for common utilities (guards, interceptors, and utility functions), a root module to register all application modules, a main entry point to bootstrap the NestJS app, and environment variable validation using Joi. This structure ensures maintainability, scalability, and proper security practices with JWT authentication and role-based access control.
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)