Introduction
Hello, Dev.to community! π I'm Syed Muhammad Kaif Bukhari, a MERN Stack Developer and Graphic Designer. Recently, I had the incredible opportunity to work on an extensive full-stack job portal during my internship at Devsinz. This project allowed me to explore and implement a wide range of technologies, from modern UI frameworks to AI integrations.
In this blog post, I'll walk you through the features, tech stack, and key components that make this job portal a robust and scalable solution. Whether you're a developer looking for inspiration or someone interested in building a similar platform, this guide will provide valuable insights.
π Features Overview
π Advanced Job Search & Application System
- Dynamic Filters: Users can filter jobs by role, experience level, work schedule, and more.
- One-Click Applications: Seamless job applications with real-time status updates.
π§βπΌ User Profile Management
- Resume Upload: Users can upload and manage multiple resumes.
- Application History: Track your job applications and follow companies.
π’ Company Profiles & Job Postings
- AI-Powered Profiles: Automatically generate comprehensive company profiles.
- Rich Text Editor: Create detailed job postings with a modern editor.
π οΈ Admin Dashboard
- Job Management: Easily create, manage, and publish job listings.
- Performance Tracking: Monitor job views, applications, and user engagement.
π Tech Stack
The project leverages a combination of frontend, backend, and AI technologies to deliver a seamless experience. Here's a breakdown of the key technologies used:
Frontend
- Next.js: A powerful React framework for building server-rendered applications.
- ShadCN UI: A set of customizable components built on Radix UI primitives.
Backend
- Prisma: An ORM that simplifies database access and schema management.
- MongoDB: A NoSQL database for flexible and scalable data storage.
Authentication
- Clerk: A complete authentication solution with support for Google, GitHub, and LinkedIn.
AI Integration
- Google Generative AI: For enhanced content generation in job and company profiles.
π οΈ Packages Used
Here's a list of the packages used in this project:
| Package | Description |
| :--------------------------------------: | :----------------------------------: |
| next | React framework for server-side rendering and static site generation. |
| shadcn/UI | UI components built on Radix UI. |
| @radix-ui/react-checkbox | Radix UI checkbox component. |
| @radix-ui/react-dialog | Radix UI dialog component. |
| @radix-ui/react-dropdown-menu | Radix UI dropdown menu component. |
| @radix-ui/react-hover-card | Radix UI hover card component. |
| @radix-ui/react-label | Radix UI label component. |
| @radix-ui/react-popover | Radix UI popover component. |
| @radix-ui/react-select | Radix UI select component. |
| @radix-ui/react-separator | Radix UI separator component. |
| @radix-ui/react-slot | Radix UI slot component. |
| @radix-ui/react-tabs | Radix UI tabs component. |
| @tanstack/react-table | Headless UI for building tables. |
| class-variance-authority | Utility for managing CSS class variance. |
| lodash | JavaScript utility library. |
| @types/lodash | TypeScript definitions for Lodash. |
| nodemailer | Email sending library. |
| @types/nodemailer | TypeScript definitions for Nodemailer. |
| axios | Promise-based HTTP client. |
| clsx | Utility for conditionally joining CSS classes. |
| cmdk | Command menu component. |
| date-fns | Modern JavaScript date utility library. |
| firebase | Backend-as-a-Service (BaaS) platform. |
| @clerk/clerk-sdk-node | Clerk SDK for Node.js. |
| @clerk/nextjs | Clerk SDK for Next.js. |
| @google/generative-ai | Google Generative AI SDK. |
| @hookform/resolvers | Resolvers for react-hook-form. |
| @prisma/client | Prisma client for database access. |
| prisma | Prisma ORM for TypeScript and Node.js. |
| query-string | Library for parsing and stringifying URL query strings. |
| mongodb | MongoDB database driver. |
| zod | TypeScript-first schema declaration and validation library. |
| react | JavaScript library for building user interfaces. |
| react-day-picker | Date picker component for React. |
| react-dom | Entry point to the DOM and server renderers for React. |
| react-hook-form | Performant, flexible, and extensible forms with easy-to-use validation. |
| react-hot-toast | React notifications library. |
| react-quill | A rich text editor for React. |
| recharts | A composable charting library built on React components. |
| framer-motion | A production-ready motion library for React. |
| handlebars | Minimal templating on steroids. |
| lucide-icons | Beautiful, consistent, and open-source icon set. |
| react-toastify | React notifications library. |
| tailwindcss | Utility-first CSS framework. |
| tailwind-merge | Merges Tailwind CSS classes. |
| tailwind-scrollbar | Scrollbar styling for Tailwind CSS. |
| tailwindcss-animate | Animation utilities for Tailwind CSS. |
π οΈ Step-by-Step Guide to Building the Job Portal
Now that you have an overview of the project, let's dive into the step-by-step guide to building this job portal from scratch.
Method 1: Setting Up the Project from Scratch
1. Setting Up the Project
First, you'll need to set up your development environment.
- Install Node.js and npm Make sure you have Node.js and npm installed on your system. You can download them from Node.js.
- Create a New Next.js Project Use the following command to create a new Next.js project:
npx create-next-app job-portal
cd job-portal
2. Install Dependencies
Next, you'll need to install the necessary packages. Here's how you can do it:
npm install next react react-dom shadcn/ui @radix-ui/react-checkbox @radix-ui/react-dialog @radix-ui/react-dropdown-menu @radix-ui/react-hover-card @radix-ui/react-label @radix-ui/react-popover @radix-ui/react-select @radix-ui/react-separator @radix-ui/react-slot @radix-ui/react-tabs @tanstack/react-table class-variance-authority lodash @types/lodash nodemailer @types/nodemailer axios clsx cmdk date-fns firebase @clerk/clerk-sdk-node @clerk/nextjs @google/generative-ai @hookform/resolvers @prisma/client prisma query-string mongodb zod react react-day-picker react-hook-form react-hot-toast react-quill recharts framer-motion handlebars lucide-icons react-toastify tailwindcss tailwind-merge tailwind-scrollbar tailwindcss-animate
3. Configure Tailwind CSS
Tailwind CSS is essential for styling your application. Follow these steps to configure it:
- Install Tailwind CSS:
npm install -D tailwindcss@latest postcss@latest autoprefixer@latest
- Initialize Tailwind CSS:
npx tailwindcss init -p```
{% endraw %}
- Update the tailwind.config.js file:
{% raw %}
```json
module.exports = {
content: ['./pages/**/*.{js,ts,jsx,tsx}', './components/**/*.{js,ts,jsx,tsx}'],
theme: {
extend: {},
},
plugins: [require('tailwind-scrollbar'), require('tailwindcss-animate')],
}
- Update globals.css:
@tailwind base;
@tailwind components;
@tailwind utilities;
4. Set Up Prisma with MongoDB
Prisma ORM is used for interacting with the MongoDB database.
- Initialize Prisma:
npx prisma init
- Update the .env file with your MongoDB connection string:
DATABASE_URL="mongodb+srv://username:password@cluster.mongodb.net/myFirstDatabase?retryWrites=true&w=majority"
- Define your database schema in prisma/schema.prisma:
generator client {
provider = "prisma-client-js"
previewFeatures = ["fulltextIndex", "fullTextSearch"]
}
datasource db {
provider = "mongodb"
url = env("DATABASE_URL")
relationMode = "prisma"
}
model Job {
id String @id @default(auto()) @map("_id") @db.ObjectId
userId String
title String
description String?
short_description String?
imageUrl String?
isPublished Boolean @default(false)
tags String[]
savedUsers String[]
shiftTiming String?
hourlyRate String?
yearsOfExperience String?
workMode String?
categoryId String? @db.ObjectId
category Category? @relation(fields: [categoryId], references: [id])
companyId String? @db.ObjectId
company Company? @relation(fields: [companyId], references: [id])
attachments Attachments[]
createdAt DateTime @default(now())
updatedAt DateTime @default(now())
@@index([categoryId])
@@index([companyId])
@@fulltext([title])
}
model Company {
id String @id @default(auto()) @map("_id") @db.ObjectId
userId String
name String
description String?
industry String?
logo String?
coverImage String?
mail String?
website String?
linkedIn String?
address_line_1 String?
address_line_2 String?
city String?
state String?
zipcode String?
jobs Job[]
followers String[]
overview String?
whyJoinUs String?
createdAt DateTime @default(now())
updatedAt DateTime @default(now())
}
model Category {
id String @id @default(auto()) @map("_id") @db.ObjectId
name String @unique
jobs Job[]
createdAt DateTime @default(now())
updatedAt DateTime @default(now())
}
model Attachments {
id String @id @default(auto()) @map("_id") @db.ObjectId
url String
name String
jobId String? @db.ObjectId
job Job? @relation(fields: [jobId], references: [id], onDelete: Cascade)
createdAt DateTime @default(now())
updatedAt DateTime @default(now())
}
model UserProfile {
userId String @id @map("_id")
fullName String? //done
email String? //done
roleId String? @db.ObjectId //done
role Role? @relation(fields: [roleId], references: [id]) //done
biography String? //done
website String? //done
city String? //done
country String? //done
contact String? //done
linkedIn String? //done
github String? //done
twitter String? //done
facebook String? //done
instagram String? //done
skillLevelId String? @db.ObjectId //done
skillLevel SkillLevel? @relation(fields: [skillLevelId], references: [id]) //done
skills String[] //done
appliedJobs AppliedJob[]
resumes Resumes[] //done
activeResumeId String? //done
userBanner String?
jobExperience JobExperience[] //done
education Education[] //done
portfolio Portfolio[]
portfolioDescription String?
connections String[]
}
model Role {
id String @id @default(auto()) @map("_id") @db.ObjectId
name String @unique
users UserProfile[]
createdAt DateTime @default(now())
updatedAt DateTime @default(now())
}
model SkillLevel {
id String @id @default(auto()) @map("_id") @db.ObjectId
name String @unique
users UserProfile[]
createdAt DateTime @default(now())
updatedAt DateTime @default(now())
}
model JobExperience {
id String @id @default(auto()) @map("_id") @db.ObjectId
userProfileId String
userProfile UserProfile @relation(fields: [userProfileId], references: [userId], onDelete: Cascade)
jobTitle String
employmentType String
companyName String
location String
startDate DateTime?
currentlyWorking Boolean?
endDate DateTime?
description String?
createdAt DateTime @default(now())
updatedAt DateTime @default(now())
@@index([userProfileId], name: "idj_userProfileId")
}
model Education {
id String @id @default(auto()) @map("_id") @db.ObjectId
userProfileId String
userProfile UserProfile @relation(fields: [userProfileId], references: [userId], onDelete: Cascade)
university String
degree String
fieldOfStudy String
grade String?
startDate DateTime?
currentlyStudying Boolean? @default(false)
endDate DateTime?
description String?
createdAt DateTime @default(now())
updatedAt DateTime @default(now())
@@index([userProfileId], name: "ide_userProfileId")
}
type AppliedJob {
jobId String
appliedAt DateTime @default(now())
}
model Resumes {
id String @id @default(auto()) @map("_id") @db.ObjectId
name String
url String
userProfileId String
userProfile UserProfile @relation(fields: [userProfileId], references: [userId], onDelete: Cascade)
createdAt DateTime @default(now())
updatedAt DateTime @default(now())
@@index([userProfileId], name: "idr_userProfileId")
}
model Portfolio {
id String @id @default(auto()) @map("_id") @db.ObjectId
name String
url String
userProfileId String
userProfile UserProfile @relation(fields: [userProfileId], references: [userId], onDelete: Cascade)
createdAt DateTime @default(now())
updatedAt DateTime @default(now())
@@index([userProfileId], name: "idp_userProfileId")
}
- Generate the Prisma client:
npx prisma generate
5. Implement Authentication with Clerk
Clerk provides an easy way to implement authentication with support for multiple providers.
- Sign up for a Clerk account at Clerk.com.
- Follow the Next.js integration guide to set up Clerk in your project.
- Update your .env with the necessary Clerk environment variables:
NEXT_PUBLIC_CLERK_FRONTEND_API=<Your Clerk Frontend API>
CLERK_API_KEY=<Your Clerk API Key>
CLERK_API_URL=<Your Clerk API URL>
CLERK_JWT_KEY=<Your Clerk JWT Key>
6. Create the User Interface
Using ShadCN UI and Tailwind CSS, create a sleek and modern UI for your job portal.
- Set up your layout components, such as Navbar, Footer, and Sidebar.
- Create pages for key features like job listings, profile management, and the admin dashboard.
7. Integrate AI Features with Google Generative AI
- Sign up for access to Google Generative AI.
- Install the SDK:
npm install @google/generative-ai
- Use the API to generate job descriptions and company profiles:
import { generateText } from '@google/generative-ai';
const generateJobDescription = async (role) => {
const response = await generateText({
model: 'text-davinci-003',
prompt: `Generate a detailed job description for a ${role}.`,
});
return response.data.choices[0].text.trim();
}
8. Implement Job Search and Filtering
Leverage Prisma and React to create dynamic filters for job search:
- Create a JobFilter component that allows users to filter jobs by various criteria.
- Implement search functionality using Prisma queries, like:
const jobs = await prisma.job.findMany({
where: {
title: { contains: searchTerm, mode: 'insensitive' },
location: { contains: location, mode: 'insensitive' },
type: jobType,
},
});
9. Set Up Email Notifications with Nodemailer
- Install Nodemailer:
npm install nodemailer
- Create a utility function to send emails:
import nodemailer from 'nodemailer';
const transporter = nodemailer.createTransport({
service: 'Gmail',
auth: {
user: process.env.EMAIL,
pass: process.env.PASSWORD,
},
});
export const sendEmail = async (to, subject, html) => {
await transporter.sendMail({
from: process.env.EMAIL,
to,
subject,
html,
});
};
- Trigger email notifications for events like job application submissions and updates.
10. Deploy the Application
Finally, deploy your application to Vercel or any other hosting platform.
- Push your code to GitHub.
- Connect your repository to Vercel and deploy it.
- Monitor the deployment and ensure everything is running smoothly.
Method 2: Setting Up the Project by Cloning
If you prefer to start with a pre-configured project, you can clone the GitHub repository directly.
1. Clone the Repository
https://github.com/SMKBukhari/VenDect.git
cd VenDect
2. Install Dependencies
npm install
3. Set Up Environment Variables
Update the .env.local file with your specific environment variables, such as database connection strings and API keys.
4. Generate Prisma Client
If you haven't already, generate the Prisma client:
npx prisma generate
5. Run the Development Server
Now you can start the development server:
npm run dev
6. Open in Your Browser
Navigate to http://localhost:3000 to see the application in action.
π― Conclusion
Building a job portal of this scale requires careful planning, a solid tech stack, and a focus on user experience. By following this step-by-step guide or by cloning the repository, you can create a robust and scalable job portal that meets modern demands.
This project has been a remarkable journey, from ideation to implementation, and I'm excited to share it with the developer community. The combination of modern frameworks, powerful tools, and AI integration has resulted in a job portal that is not only feature-rich but also highly scalable.
I'm grateful to Devsinz for providing me with this opportunity to hone my skills and create something impactful. I hope this post inspires you to build, experiment, and push the boundaries of whatβs possible with modern web development.
Feel free to connect with me on LinkedIn or check my work to stay updated on my latest projects.
Happy coding! π¨βπ»
Top comments (1)
Heyo!
Just a reminder that you likely want to add tags to your post to help with discoverability. Folks follow tags to discover posts in their "relevant" feed on the DEV homepage, so tags are really important for getting your posts seen.
You can add popular, established tags, more niche tags, or create a brand new tag if there's one that has yet to be created. Some tags are centered around a technology (#python, #javascript) and others are more functional (#discuss, #help, #tutorial)... you can use a combination of 4 tags on your post, so choose wisely.
Always make sure that the tags you choose fit the subject matter of your post. Different tags have submission guidelines set up for them which you can view on a tag's landing page. For example, view the landing page for #career - dev.to/t/career and you'll see that the submission guidelines read:
Note that we recruit Tag Moderators to help us create submission guidelines and ensure that posts are properly tagged.
Those are some of the basics of tags! Feel free to visit DEV Help for other helpful information!