๐Ÿš€ Building a Comprehensive Job Portal with Next.js, ShadcnUI, Prisma & MongodB, and Google Generative AI

Syed Muhammad Kaif Bukhari - Aug 18 - - Dev Community

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

๐Ÿ› ๏ธ 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. |
Enter fullscreen mode Exit fullscreen mode

๐Ÿ› ๏ธ 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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode
  • 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')],
}
Enter fullscreen mode Exit fullscreen mode
  • Update globals.css:
@tailwind base;
@tailwind components;
@tailwind utilities;
Enter fullscreen mode Exit fullscreen mode

4. Set Up Prisma with MongoDB

Prisma ORM is used for interacting with the MongoDB database.

  • Initialize Prisma:
npx prisma init
Enter fullscreen mode Exit fullscreen mode
  • Update the .env file with your MongoDB connection string:
DATABASE_URL="mongodb+srv://username:password@cluster.mongodb.net/myFirstDatabase?retryWrites=true&w=majority"
Enter fullscreen mode Exit fullscreen mode
  • 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")
}   
Enter fullscreen mode Exit fullscreen mode
  • Generate the Prisma client:
npx prisma generate
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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

npm install @google/generative-ai
Enter fullscreen mode Exit fullscreen mode
  • 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();
}
Enter fullscreen mode Exit fullscreen mode

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,
  },
});
Enter fullscreen mode Exit fullscreen mode

9. Set Up Email Notifications with Nodemailer

  • Install Nodemailer:
npm install nodemailer
Enter fullscreen mode Exit fullscreen mode
  • 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,
  });
};
Enter fullscreen mode Exit fullscreen mode
  • 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
Enter fullscreen mode Exit fullscreen mode

2. Install Dependencies

npm install
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

5. Run the Development Server

Now you can start the development server:

npm run dev
Enter fullscreen mode Exit fullscreen mode

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! ๐Ÿ‘จโ€๐Ÿ’ป

. . .
Terabox Video Player